Function Library

DAX

Cookbook DAX

Casos de uso reais no formato desafio, prompt, DAX e impacto.

Cookbook DAX — 10 Casos de Uso Reais

Cookbook DAX — 10 Casos de Uso Reais

Cada caso mostra o desafio de negócio, o prompt usado para gerar a fórmula com IA, o resultado em DAX e o impacto obtido. Todas as funções usadas (CALCULATE, SAMEPERIODLASTYEAR, RANKX, DATESINPERIOD, ALLEXCEPT etc.) já estão documentadas na referência de funções da Biblioteca — use os casos abaixo como exemplo de aplicação, não como fonte de definição de função.


1. Dashboard de Vendas com Análise YoY

Setor: Varejo · Complexidade: Intermediária

Desafio: comparar a performance de vendas do ano atual com o mesmo período do ano anterior, com variação percentual e indicador de tendência (▲▼).

Prompt:

"Tenho uma tabela TabVendas com colunas Data (date), Produto (text), Valor (decimal) e uma tabela Calendario já relacionada. Crie 3 medidas DAX: (1) vendas do ano atual, (2) vendas do mesmo período do ano anterior e (3) variação percentual com símbolo de tendência. Use boas práticas com VAR e DIVIDE."

// Medida 1: Vendas Ano Atual
Vendas YTD = TOTALYTD(SUM(TabVendas[Valor]), Calendario[Data])

// Medida 2: Vendas Mesmo Período Ano Anterior
Vendas YTD LY =
CALCULATE(
    [Vendas YTD],
    SAMEPERIODLASTYEAR(Calendario[Data])
)

// Medida 3: Variação % com indicador visual
Variacao YoY =
VAR VAtual = [Vendas YTD]
VAR VAnterior = [Vendas YTD LY]
VAR Variacao = DIVIDE(VAtual - VAnterior, VAnterior, BLANK())
RETURN
IF(
    ISBLANK(Variacao),
    "N/D",
    FORMAT(Variacao, "0.0%") & IF(Variacao >= 0, " ▲", " ▼")
)

Impacto relatado: redução de 3h/semana em análises manuais; identificação de queda de 12% nas vendas de março com ação corretiva em 48h; reuniões 40% mais rápidas com os indicadores visuais.


2. Análise RFM de Clientes

Setor: E-commerce · Complexidade: Avançada

Desafio: classificar clientes por Recência, Frequência e Monetário para segmentar campanhas de marketing.

Prompt:

"Tenho TabVendas (ClienteID, Data, Valor). Crie medidas DAX para uma análise RFM: (1) Recência em dias (hoje - última compra do cliente), (2) Frequência (número de compras), (3) Monetário (total gasto). Crie também uma coluna calculada que classifica cada cliente em: Campeão (R>=1, F>=3, M>=alto), Em Risco (R>=60 dias) ou Novo (F=1). Assuma que Alto Monetário = acima da mediana."

// Recência (dias desde última compra)
RFM_Recencia =
VAR UltimaCompra = CALCULATE(MAX(TabVendas[Data]), ALLEXCEPT(TabVendas, TabVendas[ClienteID]))
RETURN DATEDIFF(UltimaCompra, TODAY(), DAY)

// Frequência
RFM_Frequencia =
CALCULATE(DISTINCTCOUNT(TabVendas[PedidoID]), ALLEXCEPT(TabVendas, TabVendas[ClienteID]))

// Monetário
RFM_Monetario =
CALCULATE(SUM(TabVendas[Valor]), ALLEXCEPT(TabVendas, TabVendas[ClienteID]))

// Coluna Calculada: Segmento RFM
Segmento_RFM =
VAR Rec = DATEDIFF(MAX(TabVendas[Data]), TODAY(), DAY)
VAR Freq = CALCULATE(COUNTROWS(TabVendas), TabVendas[ClienteID] = EARLIER(TabVendas[ClienteID]))
VAR Mon = CALCULATE(SUM(TabVendas[Valor]), TabVendas[ClienteID] = EARLIER(TabVendas[ClienteID]))
VAR MedianaMonetario = MEDIAN(TabVendas[Valor])
RETURN
SWITCH(
    TRUE(),
    Rec <= 30 && Freq >= 3 && Mon >= MedianaMonetario, "Campeão",
    Rec >= 60, "Em Risco",
    Freq = 1, "Novo",
    "Regular"
)

Impacto relatado: segmentação de 45.000 clientes automatizada (antes: 2 semanas no Excel); campanha para "Em Risco" recuperou R$ 280.000 em 30 dias; recompra dos "Campeões" subiu de 23% para 41%.

Nota técnica: Segmento_RFM usa EARLIER porque é uma coluna calculada (contexto de linha) — RFM_Recencia/RFM_Frequencia/ RFM_Monetario são medidas e usam ALLEXCEPT porque operam em contexto de filtro. Não confundir os dois padrões.


3. Meta vs. Realizado com Semáforo Visual

Setor: Saúde e Bem-Estar · Complexidade: Básica

Desafio: exibir o atingimento de meta em verde (≥90%), amarelo (75-89%) ou vermelho (<75%).

Prompt:

"Tenho TabVendas (Valor, Data) e TabMetas (Meta, Periodo). Crie uma medida que calcula o % de atingimento da meta e outra que retorna um emoji de semáforo (verde 🟢, amarelo 🟡, vermelho 🔴) baseado no atingimento."

// % de Atingimento
Pct Atingimento =
VAR VendasReais = SUM(TabVendas[Valor])
VAR MetaPeriodo = SUM(TabMetas[Meta])
RETURN DIVIDE(VendasReais, MetaPeriodo, 0)

// Semáforo Visual
Semaforo =
VAR Pct = [Pct Atingimento]
RETURN
SWITCH(
    TRUE(),
    Pct >= 0.90, "🟢 " & FORMAT(Pct, "0.0%"),
    Pct >= 0.75, "🟡 " & FORMAT(Pct, "0.0%"),
    "🔴 " & FORMAT(Pct, "0.0%")
)

Impacto relatado: reuniões de status caíram de 45min para 15min; visibilidade imediata acelerou a ação em regiões no vermelho.


4. Previsão de Estoque com DAX

Setor: Manufatura · Complexidade: Avançada

Desafio: identificar produtos com risco de ruptura nos próximos 30 dias, a partir do consumo médio dos últimos 3 meses.

Prompt:

"Tenho TabEstoque (ProdutoID, QtdAtual) e TabConsumo (ProdutoID, Data, QtdConsumida). Crie medidas para: (1) consumo médio diário dos últimos 90 dias, (2) dias de cobertura de estoque, (3) alerta de ruptura (TRUE se cobertura < 30 dias). Use DATESINPERIOD e AVERAGEX."

// Consumo Médio Diário (últimos 90 dias)
Consumo Medio Diario =
VAR Periodo90d = DATESINPERIOD(Calendario[Data], MAX(Calendario[Data]), -90, DAY)
VAR TotalConsumo = CALCULATE(SUM(TabConsumo[QtdConsumida]), Periodo90d)
RETURN DIVIDE(TotalConsumo, 90, 0)

// Dias de Cobertura
Dias Cobertura =
VAR EstoqueAtual = SUM(TabEstoque[QtdAtual])
VAR ConsumoDiario = [Consumo Medio Diario]
RETURN DIVIDE(EstoqueAtual, ConsumoDiario, 9999)

// Alerta de Ruptura
Alerta Ruptura =
IF([Dias Cobertura] < 30, "⚠️ RISCO DE RUPTURA", "✅ Estoque OK")

Impacto relatado: identificação proativa de 47 SKUs em risco; redução de 35% nas rupturas de estoque no trimestre seguinte.


5. Análise de Cohort de Retenção

Setor: SaaS / Assinaturas · Complexidade: Avançada

Desafio: medir a retenção de clientes por coorte de aquisição — quais grupos ficam mais tempo assinando.

Prompt:

"Tenho TabAssinaturas (ClienteID, DataInicio, DataFim). Crie uma análise de cohort: para cada mês de aquisição, calcule quantos clientes ainda estavam ativos nos meses 1, 2, 3, 6 e 12 após a aquisição. Use DATE, DATEDIFF e CALCULATE."

// Mês de Aquisição do Cliente
Mes Aquisicao = FORMAT(TabAssinaturas[DataInicio], "YYYY-MM")

// Taxa de Retenção em N meses
Retencao N Meses =
VAR N = SELECTEDVALUE(TabMeses[Meses], 1)
VAR CohortData = MIN(TabAssinaturas[DataInicio])
VAR DataAlvo = EDATE(CohortData, N)
VAR ClientesAtivos =
    CALCULATE(
        DISTINCTCOUNT(TabAssinaturas[ClienteID]),
        TabAssinaturas[DataInicio] <= DataAlvo,
        TabAssinaturas[DataFim] >= DataAlvo
    )
VAR ClientesOriginais =
    CALCULATE(
        DISTINCTCOUNT(TabAssinaturas[ClienteID]),
        ALLEXCEPT(TabAssinaturas, TabAssinaturas[Mes Aquisicao])
    )
RETURN DIVIDE(ClientesAtivos, ClientesOriginais, 0)

Impacto relatado: clientes vindos de indicação retêm 2,3× mais; realocação de orçamento de marketing elevou o ROI em 180%.


6. Análise de Pareto (80/20) Dinâmica

Setor: Universal · Complexidade: Intermediária

Desafio: identificar quais produtos respondem por 80% das vendas, de forma dinâmica conforme filtros de período/região.

Prompt:

"Tenho TabVendas (Produto, Valor). Crie uma medida DAX que identifica se o produto atual é responsável por 80% acumulado das vendas (Pareto 80/20). Use RANKX para ordenar e TOPN para calcular o acumulado."

// Ranking de vendas por produto
Rank Vendas Produto =
RANKX(ALL(TabVendas[Produto]), SUM(TabVendas[Valor]), , DESC, Dense)

// Vendas acumuladas até este produto (% do total)
Pct Acumulado =
VAR RankAtual = [Rank Vendas Produto]
VAR TotalGeral = CALCULATE(SUM(TabVendas[Valor]), ALL(TabVendas[Produto]))
VAR VendasAcumuladas =
    CALCULATE(
        SUM(TabVendas[Valor]),
        FILTER(ALL(TabVendas[Produto]), [Rank Vendas Produto] <= RankAtual)
    )
RETURN DIVIDE(VendasAcumuladas, TotalGeral, 0)

// Classificação Pareto
Pareto Class =
SWITCH(
    TRUE(),
    [Pct Acumulado] <= 0.80, "🔴 Top 80%",
    [Pct Acumulado] <= 0.95, "🟡 Próximos 15%",
    "⚪ Cauda (5%)"
)

Impacto relatado: 23 de 847 SKUs respondem por 80% do faturamento; foco de promoções nesses 23 gerou +15% de margem.


7. Ticket Médio com Benchmark

Setor: Restaurante / Food Service · Complexidade: Básica

Desafio: comparar o ticket médio de cada unidade com a média da rede, destacando unidades acima/abaixo.

// Ticket Médio da Unidade
Ticket Medio Unidade = DIVIDE(SUM(Pedidos[Valor]), COUNTROWS(Pedidos), 0)

// Ticket Médio da Rede (ignora filtro de unidade)
Ticket Medio Rede =
CALCULATE(
    DIVIDE(SUM(Pedidos[Valor]), COUNTROWS(Pedidos), 0),
    ALL(Lojas[Unidade])
)

// Classificação vs. Rede
Status vs Rede =
VAR DifPct = DIVIDE([Ticket Medio Unidade] - [Ticket Medio Rede], [Ticket Medio Rede], 0)
RETURN
SWITCH(
    TRUE(),
    DifPct >= 0.10, "🌟 Acima da Rede +" & FORMAT(DifPct, "0.0%"),
    DifPct >= -0.10, "📊 Na Média " & FORMAT(DifPct, "0.0%"),
    "⚠️ Abaixo da Rede " & FORMAT(DifPct, "0.0%")
)

8. Taxa de Conversão de Funil de Vendas

Setor: Vendas B2B / CRM · Complexidade: Intermediária

Desafio: calcular a conversão entre etapas do funil e identificar o gargalo.

// Total por Etapa
Total Etapa =
CALCULATE(
    DISTINCTCOUNT(Funil[OportunidadeID]),
    Funil[Etapa] = SELECTEDVALUE(Etapas[Nome])
)

// Taxa de Conversão Etapa N -> Etapa N+1
Taxa Conversao =
VAR EtapaAtual = SELECTEDVALUE(Etapas[Ordem])
VAR QtdAtual = [Total Etapa]
VAR EtapaAnterior = EtapaAtual - 1
VAR QtdAnterior =
    CALCULATE([Total Etapa], FILTER(Etapas, Etapas[Ordem] = EtapaAnterior))
RETURN DIVIDE(QtdAtual, QtdAnterior, 0)

// Etapa com maior taxa de abandono
Etapa Gargalo =
FIRSTNONBLANK(TOPN(1, Etapas, [Taxa Conversao], ASC), 1)

9. Churn Rate Mensal

Setor: Telecom / SaaS · Complexidade: Avançada

Desafio: calcular o churn mensal e projetar o churn anualizado.

// Clientes Ativos no Início do Período
Clientes Inicio Mes =
CALCULATE(
    DISTINCTCOUNT(TabAssinaturas[ClienteID]),
    DATESBETWEEN(
        Calendario[Data],
        STARTOFMONTH(PREVIOUSMONTH(MAX(Calendario[Data]))),
        ENDOFMONTH(PREVIOUSMONTH(MAX(Calendario[Data])))
    )
)

// Cancelamentos no Mês
Cancelamentos Mes =
CALCULATE(
    DISTINCTCOUNT(TabAssinaturas[ClienteID]),
    DATESMTD(TabAssinaturas[DataCancelamento])
)

// Churn Rate Mensal
Churn Rate Mensal = DIVIDE([Cancelamentos Mes], [Clientes Inicio Mes], 0)

// Churn Anualizado (projeção)
Churn Anualizado = 1 - POWER(1 - [Churn Rate Mensal], 12)

10. Dashboard Executivo em 10 Medidas

Setor: Universal · Complexidade: Todas

Desafio: montar um conjunto completo de KPIs executivos para acompanhamento diário.

Prompt:

"Tenho TabVendas (Data, ClienteID, ProdutoID, Valor, Custo), Calendario e TabMetas (Meta, Mes). Crie as 10 medidas essenciais para um dashboard executivo: faturamento, margem, ticket médio, meta, variação MoM, variação YoY, novos clientes, top produto, dias sem venda e forecast. Use as melhores práticas DAX."

// 1. Faturamento Período
Faturamento = SUM(TabVendas[Valor])

// 2. Margem Bruta %
Margem Bruta % =
VAR Receita = SUM(TabVendas[Valor])
VAR Custo = SUM(TabVendas[Custo])
RETURN DIVIDE(Receita - Custo, Receita, 0)

// 3. Ticket Médio
Ticket Medio = DIVIDE([Faturamento], DISTINCTCOUNT(TabVendas[PedidoID]), 0)

// 4. Atingimento de Meta
Atingimento Meta % = DIVIDE([Faturamento], SUM(TabMetas[Meta]), 0)

// 5. Crescimento MoM
Crescimento MoM =
DIVIDE(
    [Faturamento] - CALCULATE([Faturamento], PREVIOUSMONTH(Calendario[Data])),
    CALCULATE([Faturamento], PREVIOUSMONTH(Calendario[Data])),
    BLANK()
)

// 6. Crescimento YoY
Crescimento YoY =
DIVIDE(
    [Faturamento] - CALCULATE([Faturamento], SAMEPERIODLASTYEAR(Calendario[Data])),
    CALCULATE([Faturamento], SAMEPERIODLASTYEAR(Calendario[Data])),
    BLANK()
)

// 7. Novos Clientes (1ª compra no período)
Novos Clientes =
CALCULATE(
    DISTINCTCOUNT(TabVendas[ClienteID]),
    FILTER(
        VALUES(TabVendas[ClienteID]),
        CALCULATE(MIN(TabVendas[Data])) >= MIN(Calendario[Data])
    )
)

// 8. Produto Top (maior faturamento)
Top Produto =
CALCULATE(
    MAX(TabVendas[ProdutoNome]),
    TOPN(1, VALUES(TabVendas[ProdutoNome]), [Faturamento], DESC)
)

// 9. Dias desde última venda
Dias Sem Venda = DATEDIFF(MAX(TabVendas[Data]), TODAY(), DAY)

// 10. Forecast Linear (projeção do mês com base nos dias passados)
Forecast Mes =
VAR DiaAtual = DAY(TODAY())
VAR DiasNoMes = DAY(EOMONTH(TODAY(), 0))
VAR FaturamentoMTD = TOTALMTD([Faturamento], Calendario[Data])
RETURN DIVIDE(FaturamentoMTD, DiaAtual, 0) * DiasNoMes

Impacto relatado: dashboard montado em 2h (vs. 2 dias no método tradicional); 10 KPIs cobrindo 90% das perguntas da diretoria.


Nota de curadoria

Conteúdo autoral do livro-fonte (prompts e fórmulas escritos pelo autor) — sem risco de direitos autorais e sem necessidade de validação contra Microsoft Learn, já que não faz nenhuma afirmação sobre a linguagem em si. As funções DAX usadas nos 10 casos já passaram pela auditoria função-a-função dos lotes 1-15. Removida a moldura de "Dica Pro"/tom de tutorial do livro-fonte; mantidas as fórmulas DAX e os números de impacto exatamente como no original.

Padrão: Atualização Segura de Fuso Horário + Calendário Multi-idioma

Padrão: Atualização Segura de Fuso Horário + Calendário Multi-idioma

MUST

  • Usar este padrão (não TODAY()/NOW() puro) em qualquer medida de "última atualização" que precise ser confiável tanto no Power BI Desktop quanto no Service publicado — a causa raiz do bug está validada na seção 1.
  • Manter a ressalva de que o offset fixo (-3 para Brasil/BRT) só vale para o fuso do autor — Brasil tem 4 fusos (UTC-2 a UTC-5); quem publica para usuários no Amazonas/Acre precisa ajustar.

AVOID

  • Não usar USERCULTURE() em nenhum ponto que alimente, direta ou indiretamente, uma tabela ou coluna calculada — o motor bloqueia (confirmado na seção 1). Reservar USERCULTURE() só para medidas usadas diretamente em cartão/tooltip, nunca como fonte de uma tabela calendário.

1. O bug de fuso horário — causa raiz validada

TODAY()/NOW() funcionam como hora local no Power BI Desktop, mas o Power BI Service publicado sempre roda em UTC. Uma medida de "última atualização" comparando um timestamp gravado em GMT-3 direto com TODAY() acusa "desatualizado" incorretamente perto da virada do dia em horários locais fora do UTC — sem que o refresh tenha realmente falhado.

Solução: capturar o timestamp em UTC explicitamente (DateTimeZone.UtcNow(), em M) e converter para o fuso local de forma explícita antes de qualquer comparação:

let
    HoraUTC = DateTimeZone.UtcNow(),
    HoraLocal = DateTimeZone.SwitchZone(HoraUTC, -3, 0),
    HoraSemFuso = DateTimeZone.RemoveZone(HoraLocal)
in
    HoraSemFuso

DateTimeZone.SwitchZone converte para o offset informado; DateTimeZone.RemoveZone descarta a informação de fuso depois da conversão, deixando um datetime comum para comparar com outras colunas do modelo sem ambiguidade.

2. Restrição validada: USERCULTURE() fora de tabela calculada

O padrão usa uma medida [Idioma do Projeto] (valor manual, ex.: "pt-BR") como fonte única de verdade para idioma/hemisfério/primeiro dia da semana/formato de data — de propósito manual, porque:

USERCULTURE(), USERNAME(), USERPRINCIPALNAME() e CUSTOMDATA() não são suportados em tabela ou coluna calculada — só em medida ou em AllowedRowsExpression (RLS).

✅ Confirmado contra learn.microsoft.com/en-us/dax/userculture- function-dax, artigo da SQLBI e múltiplos threads da comunidade Fabric — a restrição é real e documentada oficialmente. Como a tabela TabDimCalendário deste padrão é uma tabela calculada, qualquer medida que ela consome (incluindo [Idioma do Projeto]) não pode conter USERCULTURE() em nenhum ponto da cadeia — daí o design manual.

Nota de atualização (maio/2026): a Microsoft introduziu recentemente uma exceção — "colunas calculadas cientes do usuário" (Expression Context = User Context), que permite USERCULTURE() em colunas sob condições específicas (avaliação em tempo de consulta, não de processamento). Não se aplica ao caso de tabela calendário completa deste padrão, mas vale registrar como desenvolvimento a observar.

Função irmã, mesma restrição, uso correto neste padrão: o modelo disponibiliza [Idioma do Usuário (Detectado)], que usa USERCULTURE() livremente — mas está isolada, não alimenta nenhuma tabela calculada, só é usada direto num cartão/tooltip. Exemplo correto de onde USERCULTURE() pode ser usada sem restrição.

3. Padrão de configuração em cascata (1 medida, 4 comportamentos)

Uma única medida manual ([Idioma do Projeto]) deriva automaticamente:

/// Hemisfério do projeto, derivado da localidade em [Idioma do Projeto].
measure 'Hemisfério do Projeto' =
    VAR vIdioma = [Idioma do Projeto]
    RETURN
        SWITCH(
            vIdioma,
            "pt-PT", "Norte", "es-ES", "Norte", "en-US", "Norte",
            "fr-FR", "Norte", "de-DE", "Norte", "it-IT", "Norte",
            "pt-BR", "Sul", "es-AR", "Sul", "en-AU", "Sul",
            "Sul"  -- fallback
        )

/// Primeiro dia da semana (1=domingo, 2=segunda), derivado da localidade.
measure 'Primeiro Dia da Semana do Projeto' =
    SWITCH([Idioma do Projeto], "en-US", 1, 2)

/// Formato de data completa, derivado da localidade.
measure 'Formato de Data' =
    SWITCH(
        [Idioma do Projeto],
        "en-US", "ddd mm/dd/yyyy",
        "de-DE", "ddd dd.mm.yyyy",
        "ddd dd/mm/yyyy"  -- padrão
    )

Por que isso é um bom padrão, não só um truque: troca de um único valor ("pt-BR""en-US") propaga idioma, hemisfério (para o cálculo de estações do ano), primeiro dia da semana (para WEEKNUM/ WEEKDAY) e formato de data — sem editar 4 lugares separados e arriscar dessincronia. Mapeamento é por localidade completa (pt-BR/pt-PT), não só idioma — importante porque português no Brasil e em Portugal ficam em hemisférios diferentes.

4. Relação com o Code Library

Este padrão resolve o mesmo problema que DataHora.Calendario (35-dax-code-library-udfs.md) — geração de tabela calendário — mas com escopo maior: multi-idioma (9 localidades), hemisfério automático, e a medida de status "Última Atualização" imune a fuso horário, que o UDF DataHora.Calendario não cobre (ele gera a tabela, não a medida de status).

Recomendação: não são versões concorrentes — são complementares. DataHora.Calendario é reutilizável como UDF chamável em qualquer modelo (FUNCTION, parâmetros tipados); este padrão é um script TMDL completo (createOrReplace) pensado para ser colado uma vez via Tabular Editor 3 ou TMDL View. Vale documentar os dois lado a lado no Code Library como duas abordagens diferentes para o mesmo problema — UDF reutilizável vs. script de scaffold completo — não como substituição.

Nota de validação

Ambas as afirmações técnicas checáveis do script (restrição de USERCULTURE() em tabela calculada; Brasil sem horário de verão desde 2019) confirmadas contra fontes oficiais/primárias em 2026-08-07. Sem correções — o script está tecnicamente correto, incluindo os comentários explicativos embutidos no próprio código.

Cookbook: Configuração em Cascata via Localidade

Cookbook: Configuração em Cascata via Localidade

MUST

  • Apresentar como padrão de arquitetura, não como "recurso de calendário" — o exemplo de calendário/idioma é o caso de uso mais rico, mas o padrão em si serve para qualquer configuração que precise propagar de um único ponto de controle para vários comportamentos derivados.
  • Manter a referência ao produto real (Background Builder) como prova de que o padrão está em produção, não é só um exercício teórico — isso é o que diferencia esta entrada de um cookbook genérico de "boas práticas de configuração".

AVOID

  • Não limitar os exemplos generalizados só a localidade/idioma — outros domínios (moeda, unidade de medida, nível de agregação padrão) usam exatamente a mesma estrutura e merecem exemplo próprio, para deixar claro que o padrão generaliza de verdade.

O problema que o padrão resolve

Um relatório frequentemente precisa de múltiplos comportamentos sincronizados que dependem da mesma decisão de configuração — mas sem um padrão, cada comportamento vira uma medida separada com o valor "hardcoded" internamente. Trocar de pt-BR para en-US, por exemplo, significa editar 4 lugares diferentes (idioma, hemisfério, primeiro dia da semana, formato de data) — e é fácil esquecer um, gerando um relatório com metade em português e metade em inglês.

O padrão: 1 medida manual → N medidas derivadas

/// Fonte única de verdade — trocar este valor propaga para tudo.
measure 'Idioma do Projeto' = "pt-BR"

/// Hemisfério, derivado da localidade — afeta cálculo de estações do ano.
measure 'Hemisfério do Projeto' =
    VAR vIdioma = [Idioma do Projeto]
    RETURN
        SWITCH(
            vIdioma,
            "pt-PT", "Norte", "es-ES", "Norte", "en-US", "Norte",
            "fr-FR", "Norte", "de-DE", "Norte", "it-IT", "Norte",
            "pt-BR", "Sul", "es-AR", "Sul", "en-AU", "Sul",
            "Sul"  -- fallback
        )

/// Primeiro dia da semana (1=domingo, 2=segunda), derivado da localidade.
measure 'Primeiro Dia da Semana do Projeto' =
    SWITCH([Idioma do Projeto], "en-US", 1, 2)

/// Formato de data completa, derivado da localidade.
measure 'Formato de Data' =
    SWITCH(
        [Idioma do Projeto],
        "en-US", "ddd mm/dd/yyyy",
        "de-DE", "ddd dd.mm.yyyy",
        "ddd dd/mm/yyyy"  -- padrão
    )

Trocar "pt-BR" por "en-US" num único lugar propaga idioma, hemisfério, primeiro dia da semana e formato de data — sem risco de dessincronia entre eles.

Confirmação em produção

Este é o mecanismo real por trás do seletor de idioma do Background Builder (backgroundbuilder.officetuning.com.br) — a UI ao vivo expõe 6 locales (pt-BR, en-US, es-ES, fr-FR, de-DE, it-IT) como dropdown de idioma, subconjunto dos 9 mapeados no script TMDL original entregue pelo produto (que também cobre pt-PT, es-AR, en-AU). A motivação de negócio, segundo o autor: formatos de data, nomes de mês/dia da semana localizados, e principalmente estações do ano com ocorrência invertida conforme o hemisfério — um relatório configurado para es-AR (Argentina, hemisfério sul) e outro para es-ES (Espanha, hemisfério norte) têm o mesmo idioma (espanhol) mas precisam de lógica de estação oposta, o que só o mapeamento por localidade completa (não só idioma) resolve corretamente.

Generalização: o mesmo padrão em outros domínios

O padrão não é exclusivo de idioma/calendário — qualquer configuração que precise propagar de um ponto único para vários comportamentos segue a mesma estrutura:

Moeda de exibição:

measure 'Moeda do Projeto' = "BRL"

measure 'Símbolo da Moeda' =
    SWITCH([Moeda do Projeto], "USD", "$", "EUR", "€", "R$")

measure 'Casas Decimais da Moeda' =
    SWITCH([Moeda do Projeto], "JPY", 0, 2)  -- Iene não usa casas decimais

Unidade de medida (métrico/imperial):

measure 'Sistema de Unidades' = "Métrico"

measure 'Rótulo de Distância' =
    SWITCH([Sistema de Unidades], "Imperial", "mi", "km")

measure 'Fator de Conversão de Distância' =
    SWITCH([Sistema de Unidades], "Imperial", 0.621371, 1)

Nível de agregação padrão de um relatório:

measure 'Granularidade do Relatório' = "Mensal"

measure 'Coluna de Agrupamento Ativa' =
    SWITCH(
        [Granularidade do Relatório],
        "Diário", "Data Referência",
        "Semanal", "Ano-Semana ISO",
        "Trimestral", "Ano/Trimestre",
        "Ano/Mês"  -- padrão mensal
    )

Em todos os casos: uma medida manual como fonte única de verdade + N medidas SWITCH derivadas — o mesmo esqueleto do padrão de idioma, aplicado a um domínio diferente.

Quando usar este padrão vs. um parâmetro do Power BI

Parâmetros nativos do Power BI (o que-se "What-if parameters") já resolvem casos simples de configuração interativa pelo usuário final. Este padrão de medida-cascata é preferível quando:

  • A configuração é decidida na fase de design/publicação, não interativamente pelo usuário final (ex.: qual template regional este relatório específico usa).
  • Múltiplos comportamentos (3 ou mais) precisam ficar sincronizados — com 1 ou 2 comportamentos derivados, um parâmetro nativo simples já resolve sem precisar do padrão.
  • O valor de configuração precisa ser reutilizado em contexto que parâmetros nativos não alcançam (ex.: dentro de uma tabela calculada — lembrando da restrição de USERCULTURE() documentada em 43-dax-padrao-atualizacao-fuso-calendario.md, que é exatamente o motivo de este padrão usar uma medida manual em vez de detecção automática).

Relação com os outros documentos do projeto

  • 43-dax-padrao-atualizacao-fuso-calendario.md — mantém o detalhamento técnico do bug de fuso horário Desktop/Service e a restrição de USERCULTURE() em tabela calculada; esta entrada assume esse conteúdo como pré-requisito e foca na generalização do padrão de cascata.
  • 50-dax-code-library-business-datetime-novidades.md (DateTime.Calendar) — UDF reutilizável para gerar a tabela calendário em si; complementar, não concorrente, com este padrão de configuração.