Estou tentando escrever o seguinte para obter um total em execução de NumUsers distintos, como:
NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth])
O estúdio de gerenciamento não parece muito feliz com isso. O erro desaparece quando eu removo a DISTINCT
palavra - chave, mas não será uma contagem distinta.
DISTINCT
não parece ser possível dentro das funções de partição. Como faço para encontrar a contagem distinta? Devo usar um método mais tradicional , como uma subconsulta correlacionada?
Analisando um pouco mais a fundo, talvez essas OVER
funções funcionem de maneira diferente do Oracle, de modo que não podem ser usadas SQL-Server
para calcular totais corridos.
Eu adicionei um exemplo ao vivo aqui no SQLfiddle onde tento usar uma função de partição para calcular um total em execução.
sql
sql-server
tsql
sql-server-2008-r2
sql-server-2014
porque theq
fonte
fonte
COUNT
com emORDER BY
vez dePARTITION BY
está mal definido em 2008. Estou surpreso que esteja permitindo que você tenha. Pela documentação , você não tem permissãoORDER BY
para uma função agregada.Respostas:
Existe uma solução muito simples usando
dense_rank()
Isso lhe dará exatamente o que você estava pedindo: o número de UserAccountKeys distintas em cada mês.
fonte
dense_rank()
que ele contará NULLs, ao passoCOUNT(field) OVER
que não. Não posso empregá-lo em minha solução por causa disso, mas ainda acho que é muito inteligente.NULL
valores noUserAccountKey
, então você precisa adicionar esse termo:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
. A ideia foi retirada da resposta de LarsRönnbäck a seguir. Essencialmente, seUserAccountKey
tiverNULL
valores, você precisará subtrair o extra1
do resultado, porqueDENSE_RANK
conta NULLs.dense_rank
solução quando a função de janela tem um quadro. O SQL Server não permite odense_rank
uso com uma moldura de janela: stackoverflow.com/questions/63527035/…Necromante:
É relativamente simples emular COUNT DISTINCT em vez de PARTITION BY com MAX via DENSE_RANK:
;WITH baseTable AS ( SELECT 'RM1' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR ) ,CTE AS ( SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr FROM baseTable ) SELECT RM ,ADR ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1 ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2 -- Not supported --,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist ,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu FROM CTE
Nota:
Isso pressupõe que os campos em questão são campos NÃO anuláveis.
Se houver uma ou mais entradas NULL nos campos, você precisará subtrair 1.
fonte
Eu uso uma solução semelhante à de David acima, mas com um toque adicional se algumas linhas forem excluídas da contagem. Isso pressupõe que [UserAccountKey] nunca é nulo.
-- subtract an extra 1 if null was ranked within the partition, -- which only happens if there were rows where [Include] <> 'Y' dense_rank() over ( partition by [Mth] order by case when [Include] = 'Y' then [UserAccountKey] else null end asc ) + dense_rank() over ( partition by [Mth] order by case when [Include] = 'Y' then [UserAccountKey] else null end desc ) - max(case when [Include] = 'Y' then 0 else 1 end) over (partition by [Mth]) - 1
Um SQL Fiddle com um exemplo estendido pode ser encontrado aqui.
fonte
[Include]
que você está falando em sua resposta) comdense_rank()
trabalho quandoUserAccountKey
puderNULL
. Adicionar este termo com a fórmula:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
.Acho que a única maneira de fazer isso no SQL-Server 2008R2 é usar uma subconsulta correlacionada ou um aplicativo externo:
SELECT datekey, COALESCE(RunningTotal, 0) AS RunningTotal, COALESCE(RunningCount, 0) AS RunningCount, COALESCE(RunningDistinctCount, 0) AS RunningDistinctCount FROM document OUTER APPLY ( SELECT SUM(Amount) AS RunningTotal, COUNT(1) AS RunningCount, COUNT(DISTINCT d2.dateKey) AS RunningDistinctCount FROM Document d2 WHERE d2.DateKey <= document.DateKey ) rt;
Isso pode ser feito no SQL-Server 2012 usando a sintaxe que você sugeriu:
SELECT datekey, SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal FROM document
No entanto, o uso de
DISTINCT
ainda não é permitido, então se DISTINCT for necessário e / ou se a atualização não for uma opção, então eu acho queOUTER APPLY
é sua melhor opçãofonte