IntervalMatch Estendido na Prática

Fala pessoal, tudo bom?
 
Semana passada abordamos o tema de intervalos e descobrimos que o QlikView possui uma função própria para se tratar desses casos. Não viu? Clique aqui!

   

Definições do IntervalMatch

Também quero aproveitar para relembrar algumas definições do IntervalMatch:

  • Antes do comando intervalmatch, o campo que contém os pontos de dados discretos (“Nota” no exemplo do post anterior) já deve ter sido lido no QlikView. O próprio comando intervalmatch não lê esse campo a partir da tabela da base de dados.
  • A tabela lida no comando intervalmatch deve sempre conter exatamente dois campos (“Min” e “Max” no exemplo do post anterior). Para estabelecer um link com outros campos, é necessário ler os campos de intervalo com campos adicionais em um comando load ou select separado.
  • Os intervalos estão sempre fechados, isto é, sempre contêm pontos de extremidade.
  • Os limites não numéricos fazem com que o intervalo seja desconsiderado (indefinido)
  • Os limites nulos (NULL) estendem o intervalo indefinidamente (ilimitado).
  • Os intervalos podem estar sobrepostos, ou seja, posso ter dois intervalos que contenham valores repetidos, esses valores estarão vinculados a todos os intervalos correspondentes.

   

Caso a utilizar IntervalMatch Estendido

Essa semana iremos tratar de um intervalo que não depende simplesmente do intervalo de valores, mas também depende de um segundo campo que define a qual intervalo estamos tratando. Podemos chamar esse segundo intervalo de campo chave.

  

Resgatando o exemplo da semana passada em que tínhamos uma tabela com a classificação das notas e outra com as notas recebidas por cada aluno, vamos adicionar uma nova coluna com o nome da escola. Imagine que o nosso professor ministre aulas em duas escolas diferentes e cada uma dessas escolas possuem a sua própria classificação das notas.

  

Na escola Puríssimo a classificação é:

  • de 0 até 34 é Nota “E”
  • de 35 até 40 é Nota “D”
  • de 41 até 60 é Nota “C”
  • de 61 até 80 é Nota “B”
  • de 81 até 100 é Nota “A”

  

Na escola Sesi a classificação é:

  • de 0 até 15 é Nota “E”
  • de 16 até 40 é Nota “D”
  • de 41 até 60 é Nota “C”
  • de 61 até 70 é Nota “B”
  • de 71 até 100 é Nota “A”

  

Como temos classificações diferentes em duas escolas (CHAVE) diferente, então a nossa tabela de notas deve possuir a qual escola (CHAVE) aquela nota (do aluno) pertence.

   

Prática

Em primeiro lugar crie a nossa tabela com os intervalos.

  

Grade:
LOAD * INLINE [
Escola, Min, Max, Grade
Escola Puríssimo, 0, 34, E
Escola Puríssimo, 35, 40, D
Escola Puríssimo, 41, 60, C
Escola Puríssimo, 61, 80, B
Escola Puríssimo, 81, 100, A
Escola Sesi, 0, 15, E
Escola Sesi, 16, 40, D
Escola Sesi, 41, 60, C
Escola Sesi, 61, 70, B
Escola Sesi, 71, 100, A
];

  

Agora vamos criar a nossa tabela das notas por escola e aluno.

Notas:
LOAD * INLINE [
Escola, Nota, Aluno
Escola Puríssimo, 30, Yuri
Escola Puríssimo, 50, Edson
Escola Puríssimo, 99, Alan
Escola Puríssimo, 10, Geraldo
Escola Puríssimo, 93, Henrique
Escola Sesi, 27, Christian
Escola Sesi, 44, Michele
Escola Sesi, 80, Roberto
Escola Sesi, 76, Ivanir
Escola Sesi, 98, Zé
];

  

Note: A coluna “Escola” é a chave que define a nota e a classificação, sem ela não saberíamos informar a classificação da nota desse aluno, pois agora temos dois intervalos diferentes na mesma tabela.

  

Agora vamos para a utilização do IntervalMatch Estendido

A utilização do IntervalMatch Estendido deve-se partir da leitura da tabela de intervalos e o comando IntervalMatch deve-se relacionar com a coluna que deve ser checada dentro do intervalo e a coluna de chave:

Usando_IntervalMatch_Estendido:
IntervalMatch(Nota,Escola) //Comando IntervalMatch chamando a coluna a ser checada dentro do Intervalo e a chave para definir a qual intervalo o valor se refere.

Usando_IntervalMatch_Estendido:
IntervalMatch(Nota,Escola)  //Comando IntervalMatch chamando a coluna a ser checada dentro do Intervalo e a coluna de //chave das classificações
LOAD
Min, //Primeiro o menor valor do intervalo
Max, //depois o maior valor do intervalo
Escola //Por último a coluna chave.
RESIDENT Grade; //Tabela de Intervalos

  

Com o resultado agora teremos a tabela “Usando_IntervalMatch_Estendido” que possui as colunas: Min, Max, Nota e Escola. Essa tabela já fez a ligação entre as tabelas Nota e Grade, veja:

  

IntervalMatch Estendido 01

  

Agora é só fazer o JOIN necessário para remover essa chave sintética. Remova a parte do IntervalMatch e faça a alteração abaixo:

  

LEFT JOIN (Notas)

IntervalMatch(Nota,Escola) //Comando IntervalMatch chamando a coluna a ser checada dentro do Intervalo e a coluna de chave das classificações
LOAD
Min, //Primeiro o menor valor do intervalo
Max, //depois o maior valor do intervalo
Escola //Por último a coluna chave.
RESIDENT Grade; //Tabela de Intervalos

LEFT JOIN (Notas)

LOAD
*
RESIDENT Grade;

DROP TABLE Grade;
DROP Fields Min, Max;

   

Resultado final

IntervalMatch Estendido 02
Download da aplicação de exemplo!

   

Conclusão

Esse é um caso que mais ocorre em nosso dia a dia em relação ao caso do post anterior, pois dificilmente teremos apenas um intervalo de dados para checar.

E reforço o texto utilizado no post anterior: É uma boa prática conhecermos muitas das funções que o QlikView nos oferece, pois estas funções sempre irão nos poupar linhas e linhas de código.

  

Até a próxima semana pessoal!

IntervalMatch na prática

Fala galera, tudo bom?

Já se depararam com uma situação em que era necessário fazer um JOIN entre tabelas, porém esse JOIN não é o valor exato da segunda tabela e sim um valor que esta dentro de um intervalo dela?

Por exemplo:

Tenho uma tabela com os limites das notas de uma escolha

  • de 0 até 34 é Nota “E”
  • de 35 até 40 é Nota “D”
  • de 41 até 60 é Nota “C”
  • de 61 até 80 é Nota “B”
  • de 81 até 100 é Nota “A”

A nossa segunda tabela possui as notas dos alunos.

Nem sempre teremos uma nota com o valor dos limites do intervalo e isso impossibilita de resolvermos essa situação utilizando um comando de JOIN.

Podemos criar os registros que estão faltando no intervalo e depois é só fazer Join? Sim, para esse exemplo pode até funcionar, pois no total teremos 100 registros, mas e se a nossa tabela de “Range” possuir intervalos de milhares? milhões? Acho que para esses casos não seria uma boa solução, concordam?
   

IntervalMatch

O prefixo IntervalMatch é usado para criar uma tabela comparando valores numéricos discretos com um ou mais intervalos numéricos.
Ele deve ser colocado antes do Load ou declaração de Select (SQL) que carrega os intervalos. O campo que contém os pontos de dados discretos (Notas no exemplo abaixo) já deve ter sido carregado no QlikView antes do comando com o prefixo IntervalMatch. O prefixo não lê esse campo a partir da tabela da base de dados. O prefixo transforma a tabela carregada de intervalos em uma tabela que contém uma coluna adicional: os pontos de dados numéricos discretos Ele também expande o número de registros de forma que a nova tabela tenha um registro por combinação possível de ponto de dados discreto e intervalo.
Os intervalos podem estar sobrepostos e os valores discretos estarão vinculados a todos os intervalos
correspondentes.
   

Prática

Em primeiro lugar crie a nossa tabela com os intervalos.

Grade_Notas:
LOAD * INLINE [
Min, Max, Grade
0, 34, E
35, 40, D
41, 60, C
61, 80, B
81, 100, A
];

Agora vamos criar a nossa tabela das notas por aluno

Notas:
LOAD * INLINE [
Nota, Aluno
30, Yuri
50, Edson
99, Alan
10, Geraldo
93, Henrique
];

Agora vamos para a utilização do IntervalMatch

A utilização do IntervalMatch deve-se partir da leitura da tabela de intervalos e o comando IntervalMatch deve-se relacionar com a coluna que deve ser checada dentro do intervalo:

Usando_IntervalMatch:
IntervalMatch(Nota) //Comando IntervalMatch chamando a coluna a ser checada dentro do Intervalo
LOAD
Min, //Primeiro o menor valor do intervalo
Max //depois o maior valor do intervalo
RESIDENT Grade; //Tabela de Intervalos

Como resultado agora teremos a tabela “Usando_IntervalMatch” que possui as colunas: Min, Max e Nota. Essa tabela já fez a ligação entre as tabelas Nota e Grade, veja:

IntervalMatch 01

Agora é só fazer o JOIN necessário para remover essa chave sintética. Remova a parte do IntervalMatch e faça a alteração abaixo:

LEFT JOIN (Notas)

IntervalMatch(Nota) //Comando IntervalMatch chamando a coluna a ser checada dentro do Intervalo
LOAD
Min, //Primeiro o menor valor do intervalo
Max //depois o maior valor do intervalo
RESIDENT Grade; //Tabela de Intervalos

LEFT JOIN (Notas)

LOAD
*
RESIDENT Grade;

DROP TABLE Grade;
DROP Fields Min, Max;
   

Resultado final

IntervalMatch 02
A aplicação de exemplo pode ser baixada aqui!
   

Conclusão

É uma boa prática conhecermos muitas das funções que o QlikView nos oferece, pois estas funções sempre irão nos poupar linhas e linhas de código. A utilização do IntervalMatch também é o melhor recurso para essa situação, garantindo o resultado esperando com uma ótima performance na recarga do script.

Na próxima semana irei demonstrar como utilizar o IntervalMatch Estendido 😀

Até a próxima semana!

Melhores Práticas – ApplyMap

Fala pessoal, tudo bom?

 

Quero continuar comentando sobre melhores práticas para QlikView e neste capítulo vou falar um pouco sobre a função Applymap.

 

A função Applymap é nada mais, nada menos do que um De-Para, ou seja, substitui um valor por outro através de uma tabela de mapeamento.

Por que um de-para pode ser considerado uma melhor prática de desenvolvimento? Pois essa função, na maioria dos casos, pode ser utilizada para substituir a função JOIN com uma performance MUITO MAIOR. Lembrando que estamos falando no desempenho da execução de carga.
 
 
 

Explicação para utilização

  • Deve-se declarar uma tabela de mapeamento através da função Mapping;
  • A tabela de mapeamento deve conter apenas duas colunas;
  • A primeira coluna é sempre o De e segunda coluna sempre o Para;
  • A utilização deve ocorrer somente no método 1:1 (um para um);
  • Os nomes das colunas na tabela de mapeamento não são relevantes;
  • Por se tratar de uma tabela de mapeamento, ela existirá somente durante o carregamento (load);
  • Nenhuma associação (ligação) será feita nesta tabela;
  • Nenhum erro é gerado se você usar um nome de tabela de mapeamento que não existe

 
 
 

A função

A função ApplyMap possui três parâmetros, sendo:

  • Nome da tabela de mapeamento (deve ser usado entre apóstrofes) – Obrigatório
  • Nome do campo que sofrerá o De-para – Obrigatório
  • Retorno caso não seja encontrado nenhuma valor na tabela de mapeamento – Opcional – Caso não seja preenchido, será retornado o próprio valor do campo que não foi encontrado.

 
 
 

Exemplo simples de utilização

//Criando a tabela de mapeamento

MapaEstado:

Mapping LOAD

UF,

DESC_UF

From Estado.qvd (qvd);

 

//Utilizando a função

ApplyMap(‘MapaEstado’, UF, ‘Não Encontrei’) as DESCRICAO_ESTADO

 
 
 

Orientações

É muito importante utilizarmos o terceiro parâmetro da função com uma string que identifique que nenhum valor foi encontrado, pois dessa forma é possível identificar casos em que a regra utilizada não obteve valor.

Sempre utilize o terceiro parâmetro da função, para retornar algum valor nos casos em que não foi encontrado nenhum mapeamento.

 
 
 

Benefícios

O principal benefício da função é o desempenho na realização do De/Para. É sempre recomendado a utilização do ApplyMap ao invés de JOIN quando se trata da ligação entre duas tabelas grandes. Não acredita? Existem diversos cenários testados que comprovam, procure na comunidade :D!

Outro benefício é que, como dito anteriormente, o ApplyMap funciona apenas em casos de 1:1 (um para um), ou seja, um determinado registro possui apenas um valor a ser mapeado. Caso a função seja utilizada em casos N:1, então será retornado o primeiro registro (De N) do mapeamento para aquele caso e não será gerado nenhum produto cartesiano dos dados.

No exemplo abaixo, o vendedor Yuri possui dois apelidos, ‘Nicolett’ e ‘Yure’. Se fosse realizado um JOIN para interligar essas tabelas, os valores de vendas do vendedor Yuri seria duplicado devido as duas possibilidades de apelido, porém com ApplyMap, somente o primeiro valor encontrado no mapeamento seria retornado (‘Nicolett’) e os valores de vendas permaneceriam inalterados.

 
 

MapaApelido:
Mapping LOAD * INLINE [
NOME, APELIDO
Yuri, Nicolett
Yuri, Yure
Paula, Paulinha
];

 
Fato:
LOAD
ID,
NOME_VEND,
VALOR,
ApplyMap(‘MapaApelido’, NOME_VEND, ‘Achei não!’) as APELIDO
INLINE [
ID, NOME_VEND, VALOR
1, Yuri, 2
2, Yuri, 4
3, Paula, 5
];

 
 

Por último, e não menos importante, é possível se utilizar desse mapeamento para outra tratativa sem a necessidade de um pré-load ou resident, pois ao fazer o mapeamento o novo valor fica disponível para utilização naquele momento.

No exemplo abaixo é calculada o valor de comissão do vendedor em apenas 1 passo:

 

MapaComissao:

Mapping LOAD

VENDEDOR,

PERC_COMISSAO

FROM Comissao.qvd (qvd);

 

Fato:

LOAD

QTD_VENDA * ApplyMap(‘MapaComissao’, VEND, 0) as VALOR_COMISSAO

RESIDENT ….

 
 
 

Casos extremos

Mas Yuri, e se precisar recuperar 4 campos de outra tabela? Valeria a pena criar 4 mapeamentos ou fazer um único join?

Para identificar qual a melhor opção nessa caso eu precisaria de mais um detalhe: O volume de registros das duas tabelas é grande? Passa de 1 milhão de linhas? Vai variar muito daqui N tempo? Se o volume de dados é grande, então não tenho receio algum de te recomentar a utilização de 4 tabelas de mapeamentos e 4 applymaps, caso contrário utilize JOIN.

 
 
 

E mais alguns detalhes

Com o ApplyMap

O terceiro parâmetro da função ApplyMap é muito poderoso, pois você pode utilizar funções ou combinações de campos, por exemplo:

 

1) Retorne um conjunto de campos

ApplyMap(‘Mapeamento’, CAMPO, CAMPO2& ‘-‘ &CAMPO3)

 

2) Caso um mapeamento não funcione, tente outro.

ApplyMap(‘Mapeamento’, CAMPO, ApplyMap(‘MapeamentoDois’, CAMPO2))

/* Sinceramente esse caso é muito interessante, já presenciaram modelagens que a ligação entre duas tabelas pode ser realizada através de duas chaves diferentes? Dependendo da situação através de uma chave ou através de outra? Esse caso pode salvar sua vida rsrs */

 

3) Funções – Verifico condição

ApplyMap(‘Mapeamento’, CAMPO, if( CAMPO2 > CAMPO3, ‘É Maior’, ‘É Menor’))

 
 
 

Conclusão

É sempre bom se utilizarmos desse manual das melhores práticas e o ApplyMap é uma das principais funções para conseguirmos obter o melhor desempenho (de carga) dentro do script das nossas aplicações. Sempre analise seu atual ambiente e em caso de dúvidas faça o teste entre JOIN e ApplyMap.

 

 

 

Até a próxima semana!

 

Função para Criar Calendário (Utilizando Data existente no modelo)

Fala pessoal, tudo beleza?

No último post aprendemos a utilizar uma função para criação de calendário passando uma data início e data fim, hoje aprenderemos como verificar esse range através de um campo existente em nosso modelo.

Para fazer isso utilizaremos três funções: FieldValue, FieldValueCount e While.

  • FieldValue para retornar um valor do campo de data do modelo. Lembrando que sempre será retornado apenas um valor, para o nosso caso resolve, pois utilizaremos essa função junto com as funções MAX e MIN.
  • FieldValueCount utilizaremos para percorrer todos os possíveis valores do campo de data do modelo. Lembrando que a função retorna o número de valores distintos do campo escolhido.
  • While para criar todos os registro no intervalo entre a maior e menor data do modelo.

Vamos ao código:

SUB CriaCalendario(vCampoData,vNomeCalendario,vPrefixoCalendario,vCampoChave)

LET vNomeCalendario = If(Len(‘$(vNomeCalendario)’)=0,’Calendario’,’$(vNomeCalendario)’);
LET vPrefixoCalendario = If(Len(‘$(vPrefixoCalendario)’)=0,”,’$(vPrefixoCalendario)’);
LET vCampoChave = PurgeChar(vCampoChave,'”[]’);

[$(vNomeCalendario)]:
LOAD
Distinct
Data_ as [$(vCampoChave)],
Date(Data_, ‘$(DateFormat)’ ) as [$(vPrefixoCalendario)Data],
Year(Data_) as [$(vPrefixoCalendario)Ano],
Month(Data_) as [$(vPrefixoCalendario)Mes],
Day(Data_) as [$(vPrefixoCalendario)Dia],
Date(MonthStart(Data_), ‘MMM/YYYY’) as [$(vPrefixoCalendario)MesAno];
LOAD
Date(_DataInicio+(Iterno()-1),’$(DateFormat)’ ) as Data_
WHILE (_DataInicio+(Iterno()-1)<=_DataFim); //Regra para gerar todos os registro do intervalo
LOAD
Floor(Min(Fieldvalue(‘$(vCampoData)’,RecNo()))) as _DataInicio, //Função utilizada para buscar valor do campo data do modelo.
Floor(Max(Fieldvalue(‘$(vCampoData)’,RecNo()))) as _DataFim
AUTOGENERATE FieldValueCount(‘$(vCampoData)’); //Função necessária para passar por todos os registros possíveis do campo data do modelo.

ENDSUB;

 
 
OBSERVAÇÃO: É necessário utilizar o FieldValueCount no AutoGenerate, pois precisamos passar por todos os possíveis valores do campo da data. Caso você não utilize, pode ser que o Min e o Max sejam alterados.

 

Pronto, agora basta utilizar a função Call e passar os parâmetros para que o calendário seja criado, conforme exemplo abaixo:

Call CriaCalendario(‘DATA’,’Calendario’,”,’%ChaveData’);

Neste exemplo o calendário será criado com o intervalo entre o intervalo de datas do campo DATA (esse campo esta localizado na tabela Tabela_Fato do exemplo), o nome da tabela será “Calendario”, nenhum prefixo de campos será utilizado e o campo de chave com a minha modelagem é “%ChaveData”.

Para fazer o download da aplicação de exemplo, basta clicar aqui!

 

Por enquanto é isso pessoal!

Semana que vem abordaremos outro assunto!

Função para Criar Calendário (Passando datas)

Fala pessoal!

 

Na maioria dos projetos em que iremos trabalhar é preciso criar um calendário para poder trabalhar com as datas de forma organizada (ainda não vou falar sobre um calendário principal, esse assunto vai ficar para um próximo post). Na maioria dos casos sempre opto por criar uma tabela de Calendário e trabalhar as datas nessa tabela, pois sempre tento organizar todas as datas necessárias para o projeto em uma única tabela (quando possível rsrs).

 

Hoje quero repassar para vocês uma função que utilizo para criar um calendário de uma forma com alta performance. Função? Sim, eu crio uma classe de função de script e depois utilizo uma linha passando os parâmetros necessários para criar o calendário.

 

Vamos entender um pouco mais sobre essas funções de script através do manual do QlikView:

O comando de controle sub e end sub define uma sub-rotina que pode ser chamada a partir de um comando call.

A sintaxe é:
sub nome [ ( listadeparâmetros )] comandos end sub

Onde:

  • name é o nome da sub-rotina.
  • listadeparâmetros é uma lista, separada por vírgulas, de nomes de variáveis para os parâmetros formais da subrotina. Eles podem ser usados como qualquer variável dentro da sub-rotina.
  • comandos são qualquer grupo de um ou mais comandos de script do QlikView.

 

Os argumentos são copiados na sub-rotina e, se os parâmetros reais correspondentes no comando call for o nome de uma variável, serão copiados novamente após a saída da sub-rotina.
Se uma sub-rotina tiver mais parâmetros formais que os parâmetros reais transmitidos por um comando call, os parâmetros extra serão inicializados como NULL e poderão ser utilizados como variáveis locais na subrotina.
Como o comando sub é um comando de controle e, por isso, termina com um ponto-e-vírgula ou com um fim de linha, cada uma de suas duas cláusulas (sub e end sub) não deve cruzar um limite de linha.

 

Agora vamos ao exemplo prático! No final do post existe um link para download de uma aplicação de exemplo 🙂

 
———————————————————————————————————–
 
 

Em primeiro lugar, cria um aba em seu script chamada Sub CriaCalendario nesta aba criaremos a função para criar o calendário. Segue agora o script:

 

/* Nome da função é CriaCalendario e esta recebe cinco parâmetros, são eles:

vDataInicio = Data de Início do seu relatório. O formato a ser passado pode seguir o modelo da formatação de data local, por exemplo DD/MM/YYYY.

vDataFim = Data de término do seu relatório. O formato a ser passado pode seguir o modelo da formatação de data local, por exemplo DD/MM/YYYY.

vNomeCalendario = Nome que receberá a tabela do seu calendário, caso nenhum seja digitado, então o nome Calendario será utilizado.

vPrefixoCalendario = Conjunto de caracteres que precederá todos os nomes de campos da tabela de calendário. Exemplo: “Calendario.”, então todos os campos possuirão o “Calendario.” antes do nome do campo da tabela. Caso nada seja utilizado, então nenhum prefixo será utilizado.

vCampoChave = Nome do campo que será chave entre a tabela de calendário e o seu modelo. Note que esse campo deve ser uma data completa, com dia, mês e ano.

*/

SUB CriaCalendario(vDataInicio,vDataFim,vNomeCalendario,vPrefixoCalendario,vCampoChave)

/* Algumas tratativas com os valores recebidos – Lembre-se que esses valores sempre serão tratados como variáveis. */
LET vNomeCalendario = If(Len(‘$(vNomeCalendario)’)=0,’Calendario’,’$(vNomeCalendario)’);
LET vPrefixoCalendario = If(Len(‘$(vPrefixoCalendario)’)=0,”,’$(vPrefixoCalendario)’);
LET vCampoChave = PurgeChar(vCampoChave,'”[]’);
/* Criação do calendário */
[“$(vNomeCalendario)”]:
LOAD
Distinct
Data_ as [$(vCampoChave)],
Date(Data_, ‘$(DateFormat)’ ) as [$(vPrefixoCalendario)Data],
Year(Data_) as [$(vPrefixoCalendario)Ano],
Month(Data_) as [$(vPrefixoCalendario)Mes],
Day(Data_) as [$(vPrefixoCalendario)Dia],
Date(MonthStart(Data_), ‘MMM/YYYY’) as [$(vPrefixoCalendario)MesAno];

LOAD
Date(‘$(vDataInicio)’+(Iterno()-1),’$(DateFormat)’ ) as Data_
AutoGenerate 1 While (Num(‘$(vDataInicio)’)+(Iterno()-1)<= Num(‘$(vDataFim)’));

/* Finalizando a função */
ENDSUB;

 

Pronto, agora basta utilizar a função Call e passar os parâmetros para que o calendário seja criado, conforme exemplo abaixo:

Call CriaCalendario(’01/01/2015′,’08/03/2015′,’Calendario’,”,’%ChaveData’);

 

Neste exemplo o calendário será criado com o intervalo entre 01/01/2015 e 08/03/2015, o nome da tabela será “Calendario”, nenhum prefixo de campos será utilizado e o campo de chave com a minha modelagem é “%ChaveData”.

 

IMPORTANTE: É necessário passar um valor para cada argumento criado na função, por esta razão passei um vazio para o argumento de número quatro que é o prefixo dos campos, caso contrário a função não executará corretamente.

 

Fiquem a vontade para editar e modelar da forma que preferirem para encaixar em seu modelo.

Para a próxima semana vou demonstrar como criar um calendário utilizando um intervalo de datas através de um campo de uma tabela.

 

Deixo aqui uma aplicação de exemplo para download.

 

Até a próxima semana!