Comparações de período (YTD, LY, Rolling 12M), Set Analysis indireto via variáveis, aninhamento e os padrões mais complexos usados em dashboards enterprise.
Comparações de período são o caso de uso mais frequente em dashboards executivos. O segredo está em combinar modificadores de Ano e Mês com dollar-sign expansion para criar expressões que funcionam independente do período selecionado pelo usuário.
YTD representa a acumulação do início do ano corrente até o mês mais recente disponível nos dados (ou até hoje). A chave é não hardcodar o ano - usar expressão dinâmica.
// YTD simples - ignora todas as seleções de período
Sum({
<Ano={$(=Year(Today()))},
Mes={"<=$(=Month(Today()))"}>
} Valor)
// YTD respeitando outras seleções (ex.: Região, Produto selecionados)
// mas ignorando seleção de Ano e Mês do usuário
Sum({
$<Ano={$(=Year(Today()))},
Mes={"<=$(=Month(Today()))"}>
} Valor)
// YTD usando campo Data diretamente (mais preciso)
Sum({
<Data={">=$(=YearStart(Today()))<=$(=Today())"}>
} Valor)
// YTD com seleção do usuário respeitada para outros campos:
Sum({
$<Data={">=$(=YearStart(Today()))<=$(=Today())"}>
} Valor)
LY (Last Year) precisa corresponder exatamente ao mesmo período do YTD, mas um ano antes. A expressão deve ser consistente: se o YTD vai até junho de 2025, o LY deve ir até junho de 2024.
// LY YTD - acumulado do ano anterior até o mesmo mês
Sum({
<Ano={$(=Year(Today())-1)},
Mes={"<=$(=Month(Today()))"}>
} Valor)
// LY com campo Data - exatamente o mesmo intervalo 1 ano atrás
Sum({
<Data={">=$(=AddYears(YearStart(Today()),-1))<=$(=AddYears(Today(),-1))"}>
} Valor)
// Variação percentual YoY - crescimento ano a ano
(
Sum({$<Ano={$(=Year(Today()))},Mes={"<=$(=Month(Today()))}"}>} Valor)
/
Sum({$<Ano={$(=Year(Today())-1)},Mes={"<=$(=Month(Today()))}"}>} Valor)
) - 1
// Formatar como percentual: num(expressao, '#0.0%')
// Comparação mês a mês (MTD vs MTD anterior)
// Mês atual:
Sum({<Ano={$(=Year(Today()))},Mes={$(=Month(Today()))}>} Valor)
// Mês anterior:
Sum({<Ano={$(=Year(AddMonths(Today(),-1)))},Mes={$(=Month(AddMonths(Today(),-1)))}>} Valor)
Rolling 12M é diferente de YTD: enquanto o YTD sempre começa em 1 de janeiro, o Rolling 12M é uma janela que desliza - cobre os 12 meses anteriores à data atual independente do ano. Elimina sazonalidade e permite comparações contínuas.
// Rolling 12M usando campo Data
Sum({
<Data={">=$(=AddMonths(Today(),-12))<=$(=Today())"}>
} Valor)
// Rolling 12M using Ano + Mes fields (common pattern)
// Requires a numeric representation: AnoMes = Year*100 + Month
Sum({
<AnoMes={">=$(=Year(AddMonths(Today(),-12))*100+Month(AddMonths(Today(),-12)))
<=$(=Year(Today())*100+Month(Today()))"}>
} Valor)
// Comparação Rolling 12M atual vs Rolling 12M anterior
// (janela de 12M que terminou 12 meses atrás)
// Período atual (R12M):
Sum({<Data={">=$(=AddMonths(Today(),-12))<=$(=Today())"}>} Valor)
// Período anterior (R12M -1 ano):
Sum({<Data={">=$(=AddMonths(Today(),-24))<=$(=AddMonths(Today(),-12))"}>} Valor)
Padrão avançado: nunca hardcode anos ou meses em expressões de período. Uma expressão com Ano={2024} envelhece e fica errada. Uma expressão com Ano={$(=Year(Today()))} funciona para sempre.
// QTD - Quarter to Date (acumulado do trimestre atual)
Sum({
<Data={">=$(=QuarterStart(Today()))<=$(=Today())"}>
} Valor)
// Trimestre anterior completo
Sum({
<Data={">=$(=QuarterStart(AddMonths(Today(),-3)))<=$(=QuarterEnd(AddMonths(Today(),-3)))"}>
} Valor)
// Mesmo trimestre do ano anterior (para comparar Q2 2025 vs Q2 2024)
Sum({
<Data={">=$(=AddYears(QuarterStart(Today()),-1))<=$(=AddYears(QuarterEnd(Today()),-1))"}>
} Valor)
Set Analysis indireto é o padrão onde o conteúdo do conjunto é definido por variáveis - e essas variáveis podem ser alteradas pelo próprio usuário via input boxes ou botões. Transforma um dashboard estático em um sistema de análise configurável sem alterar uma linha de expressão.
A ideia é separar a lógica de filtro da expressão de cálculo. A variável funciona como uma "chave" que define o comportamento do conjunto - e o desenvolvedor ou o próprio usuário controla essa chave.
// Configuração no script (ou inline)// Variáveis de período - configuráveis pelo usuário via input box SET vAnoComparacao = 2023; // usuário pode alterar para 2022, 2021 etc SET vMesInicio = 1; // início do intervalo de análise SET vMesFim = 6; // fim do intervalo de análise // Variáveis de filtro de negócio SET vSegmento = 'Enterprise'; SET vRegiaoFoco = 'Sul,Sudeste'; // múltiplos valores // Na expressão - usa as variáveis: Sum({ <Ano={$(#vAnoComparacao)}, Mes={">=$(#vMesInicio)<=$(#vMesFim)"}> } Valor) // A expressão funciona para qualquer configuração de variável. // Alterar vAnoComparacao de 2023 para 2021 // muda imediatamente o resultado sem editar a expressão.
Um padrão elegante é criar uma variável que seleciona o "modo" de análise - e as expressões respondem a esse modo automaticamente.
// Variável de modo (definida via botão ou input box pelo usuário)
// vModo = 'YTD' | 'LY' | 'R12M'
SET vModo = 'YTD';
// Expressão que responde ao modo:
// (usando If() no NÍVEL DA EXPRESSÃO - não dentro da aggregation!)
If(vModo = 'YTD',
Sum({<Data={">=$(=YearStart(Today()))<=$(=Today())"}>} Valor),
If(vModo = 'LY',
Sum({<Data={">=$(=AddYears(YearStart(Today()),-1))<=$(=AddYears(Today(),-1))"}>} Valor),
Sum({<Data={">=$(=AddMonths(Today(),-12))<=$(=Today())"}>} Valor)
))
// IMPORTANTE: o If() aqui está FORA do Sum() - ele escolre qual Sum() calcular.
// Isso é diferente de Sum(If(...)) que é lento.
// Este padrão é eficiente: apenas uma das expressões é avaliada.
// Definir o filtro completo como variável:
// (Aspas simples duplicadas para escapar dentro da string)
SET vFiltroVIP = '{<SegmentoCliente={''VIP''}>}';
SET vFiltroEletro = '{<Categoria={''Eletrônicos''}>}';
SET vFiltroQ1Atual = '{<Trimestre={''Q1''},Ano={$(=Year(Today()))}>}';
// Uso em qualquer expressão do app:
Sum($(vFiltroVIP) Valor)
Count($(vFiltroVIP) DISTINCT ClienteID)
Avg($(vFiltroEletro) TicketMedio)
// Combinando variáveis (intersecção):
// VIP E Eletrônicos
Sum($(vFiltroVIP) $(vFiltroEletro) Valor)
// Expande para: Sum({<SegmentoCliente={'VIP'}>} {<Categoria={'Eletrônicos'}>} Valor)
// Qlik interpreta múltiplos modificadores como intersecção (AND)
Cuidado com aspas: dentro de um SET em script, aspas simples dentro de strings precisam ser duplicadas. SET v = "{'VIP'}" é inválido. O correto é SET v = "{''VIP''}". Verifique sempre no console de debug do Qlik antes de ir para produção.
Set Analysis aninhado ocorre quando o modificador de um conjunto é determinado por outro conjunto. É o nível mais avançado de Set Analysis - e o padrão que responde a perguntas de negócio que seriam impossíveis de outra forma.
O padrão mais comum de aninhamento: usar P() com um conjunto interno para definir o modificador de um conjunto externo. Vimos isso brevemente na Parte 1 - agora em profundidade.
// Pergunta: "quais são as vendas dos produtos que
// tiveram pelo menos 1 venda no mês anterior?"
// Sem aninhamento: impossível sem join no script.
Sum({
<ProdutoID = P({1}<Mes={$(=Month(AddMonths(Today(),-1)))},
Ano={$(=Year(AddMonths(Today(),-1)))}> ProdutoID)>
} Valor)
// Leitura:
// P({1}<Mes=MesAnterior, Ano=AnoAnterior> ProdutoID)
// → conjunto de ProdutoIDs que venderam no mês anterior
// <ProdutoID = [esse conjunto]>
// → filtra os dados para esses produtos
// Sum(Valor)
// → soma as vendas desses produtos no período atual (seleção do usuário)
É possível aninhar mais de dois níveis, mas cada nível adiciona complexidade e potencial custo de performance. Use com moderação - e sempre documente a lógica.
// Pergunta: "vendas dos produtos que foram comprados por clientes
// que também compraram o Produto X"
// Nível 1: clientes que compraram Produto X
// Nível 2: produtos comprados por esses clientes
// Nível 3: vendas desses produtos na seleção atual
Sum({
<ProdutoID = P({
<ClienteID = P({1}<ProdutoID={'PROD-X-001'}> ClienteID)>
} ProdutoID)>
} Valor)
// Leitura de dentro para fora:
// 1. P({1}<ProdutoID={'PROD-X-001'}> ClienteID)
// → ClienteIDs que compraram PROD-X-001
// 2. P({<ClienteID=[clientes acima]>} ProdutoID)
// → ProdutoIDs comprados por esses clientes
// 3. <ProdutoID=[produtos acima]> + Sum(Valor)
// → vendas desses produtos
// Aviso de performance: aninhamento de 3+ níveis pode ser lento
// em datasets > 10M linhas. Considere pré-calcular no script.
Combinar Set Analysis com o qualifier TOTAL é o padrão correto para calcular percentuais de participação. O TOTAL calcula o denominador ignorando as dimensões do gráfico.
// Participação de mercado por categoria - % do total geral
Sum(Valor) / Sum(TOTAL Valor)
// Participação dentro de um conjunto específico:
// % que cada produto representa nas vendas de Eletrônicos
Sum({<Categoria={'Eletrônicos'}>} Valor)
/
Sum(TOTAL {<Categoria={'Eletrônicos'}>} Valor)
// Participação do cliente no total de vendas da sua região
// (respeitando a seleção de Região, mas ignorando a dimensão ClienteID do gráfico)
Sum(Valor) / Sum(TOTAL <ClienteID> Valor)
// TOTAL <ClienteID> = ignora a dimensão ClienteID,
// mas mantém outras seleções de contexto
// Comparação: participação este ano vs ano anterior
Sum({$<Ano={$(=Year(Today()))}>} Valor)
/
Sum(TOTAL {$<Ano={$(=Year(Today()))}>} Valor)
Os padrões a seguir são derivados de implementações reais em dashboards executivos. Cada um resolve um problema específico de negócio usando Set Analysis como ferramenta principal.
// KPI: vendas do período selecionado + comparação com mesmo período do ano anterior
// Período atual (respeita seleção do usuário de Ano e Mês):
LET vVendasAtual = Sum({$} Valor);
// Mesmo período no ano anterior (substitui apenas o Ano, mantém Mês selecionado):
LET vVendasAnterior = Sum({$<Ano={$(=Max({$} Ano)-1)}>} Valor);
// Variação percentual:
Num((vVendasAtual / vVendasAnterior) - 1, '+#0.0%;-#0.0%;0.0%')
// Na prática - expressão completa no KPI:
Num(
(Sum({$} Valor) / Sum({$<Ano={$(=Max({$}Ano)-1)}>} Valor)) - 1,
'+#0.0%;-#0.0%;0.0%'
)
// Formatação com sinal positivo/negativo automático e cor via expression-based color
// Definição: cliente "ativo" = comprou nos últimos 90 dias
// Definição: cliente "inativo" = não comprou nos últimos 90 dias
// Total de clientes ativos:
Count({
<ClienteID = P({1}<Data={">=$(=Today()-90)"}> ClienteID)>
} DISTINCT ClienteID)
// Total de clientes inativos (todos os clientes MENOS os ativos):
Count({
1
-
<ClienteID = P({1}<Data={">=$(=Today()-90)"}> ClienteID)>
} DISTINCT ClienteID)
// Taxa de ativação (% de clientes que compraram no período):
Count({
<ClienteID = P({1}<Data={">=$(=Today()-90)"}> ClienteID)>
} DISTINCT ClienteID)
/
Count({1} DISTINCT ClienteID)
// Identificar quais clientes estão no top 20% por receita
// e calcular o total de receita que eles representam
// Passo 1: calcular receita por cliente
// (feito no script para performance - AGGR() caro em gráfico)
ReceitaCliente:
LOAD ClienteID, Sum(Valor) AS ReceitaTotal
RESIDENT FactVendas GROUP BY ClienteID;
// Passo 2: no gráfico, usar Set Analysis para filtrar top 20%
// (usando função Rank com threshold percentual)
Sum({
<ClienteID = P({1}<ClienteID={"=Rank(Sum(Valor)) <= $(=Ceil(Count(DISTINCT ClienteID)*0.2))"}> ClienteID)>
} Valor)
// Percentual sobre o total:
Sum({
<ClienteID = P({1}<ClienteID={"=Rank(Sum(Valor)) <= $(=Ceil(Count(DISTINCT ClienteID)*0.2))"}> ClienteID)>
} Valor)
/
Sum({1} Valor)
Padrão Pareto é custoso computacionalmente porque envolve uma ordenação interna. Em datasets > 5M linhas, considere pré-calcular o rank no script e armazená-lo como um campo: RankReceita = Rank(ReceitaTotal). A expressão Set Analysis fica simples: Sum({<RankReceita={"<=$(=Ceil(Count(DISTINCT ClienteID)*0.2))"}>} Valor).
// Scenario: tabelas separadas - FactVendas e Orcamento
// Desafio: comparar os dois mantendo a seleção do usuário de Produto, Região etc.
// Realizado (seleção completa do usuário):
Sum({$} Valor)
// Orçado (mesma seleção de Produto e Região, mas forçando Ano/Mês do orçamento)
// Assume que Orcamento tem a mesma estrutura de campos:
Sum({
$<TipoRegistro={'Orcamento'},
Ano={$(=Year(Today()))}>
} Valor)
// Variação absoluta:
Sum({$<TipoRegistro={'Realizado'}>} Valor)
-
Sum({$<TipoRegistro={'Orcamento'}>} Valor)
// Variação % (Atingimento):
Num(
Sum({$<TipoRegistro={'Realizado'}>} Valor)
/
Sum({$<TipoRegistro={'Orcamento'}>} Valor),
'#0.0%'
)
Expressões Set Analysis complexas são notoriamente difíceis de debugar porque o Qlik não exibe mensagens de erro claras - ele simplesmente retorna 0 ou NULL. Conhecer as técnicas de diagnóstico é indispensável.
// Para entender o que o conjunto retorna,
// use Count() com DISTINCT antes de Sum():
Count({
<ClienteID = P({1}<Categoria={'Eletrônicos'}> ClienteID)>
} DISTINCT ClienteID)
// Se retornar 0 - o conjunto está vazio.
// Se retornar o esperado - o conjunto está correto,
// o problema pode ser na expressão agregada.
// Use uma caixa de texto com a expressão
// para ver o valor expandido antes do Set Analysis:
// Adicione um objeto de texto com:
=Year(Today()) // deve retornar 2025
=Month(Today()) // deve retornar o mês atual
=MonthStart(Today()) // data de início do mês
// Se esses valores estão incorretos,
// a dollar-sign expansion no Set Analysis
// também estará errada.
// Técnica 3 - Construção incremental// Nunca escreva uma expressão complexa de uma vez. // Construa peça por peça e valide cada parte: // Passo 1: teste o identificador Sum({1} Valor) // deve retornar o total geral // Passo 2: adicione um modificador simples Sum({1}<Ano={2024}>} Valor) // deve retornar vendas de 2024 // Passo 3: torne dinâmico Sum({1}<Ano={$(=Year(Today()))}>} Valor) // deve retornar o mesmo // Passo 4: adicione o segundo modificador Sum({1}<Ano={$(=Year(Today()))},Mes={"<=$(=Month(Today()))}"}>} Valor) // Passo 5: troque {1} por {$} se precisar respeitar seleção Sum({$}<Ano={$(=Year(Today()))},Mes={"<=$(=Month(Today()))}"}>} Valor) // Construção incremental elimina ambiguidade sobre onde está o erro.