Usando DISTINCT na função de janela com OVER

18

Estou tentando migrar uma consulta do Oracle para o SQL Server 2014.

Aqui está minha consulta que funciona muito bem no Oracle:

select
count(distinct A) over (partition by B) / count(*) over() as A_B
from MyTable 

Aqui está o erro que recebi depois de tentar executar esta consulta no SQL Server 2014.

Use of DISTINCT is not allowed with the OVER clause

Alguém sabe qual é o problema? Esse tipo de consulta é possível no SQL Server? Por favor informar.

Omri
fonte
Você realmente precisa de uma linha no resultado para cada linha MyTable? Ou são linhas distintas o suficiente? E você não precisa considerar a divisão com erro zero se não houver linhas MyTable?
Erwin Brandstetter

Respostas:

12

Alguém sabe qual é o problema? Esse tipo de consulta é possível no SQL Server?

Não, não está implementado no momento. Consulte a seguinte solicitação de item de conexão.

Solicitação de aprimoramento da cláusula OVER - cláusula DISTINCT para funções agregadas

Outra variante possível seria

SELECT M.A,
       M.B,
       T.A_B
FROM   MyTable M
       JOIN (SELECT CAST(COUNT(DISTINCT A) AS NUMERIC(18,8)) / SUM(COUNT(*)) OVER() AS A_B,
                    B
             FROM   MyTable
             GROUP  BY B) T
         ON EXISTS (SELECT M.B INTERSECT SELECT T.B) 

o elenco para NUMERICestá lá para evitar a divisão inteira. O motivo da cláusula de junção é explicado aqui .

Ele pode ser substituído por ON M.B = T.B OR (M.B IS NULL AND T.B IS NULL)se preferir (ou simplesmente ON M.B = T.Bse a Bcoluna não for anulável).

Martin Smith
fonte
14

Isso fornece a contagem distinta (*) para A particionada por B:

dense_rank() over (partition by B order by A) 
+ dense_rank() over (partition by B order by A desc) 
- 1
Ben
fonte
3
Solução interessante. Suponho que ele deve ter um aviso de isenção de responsabilidade de que funciona apenas quando Anão é nulo (pois acho que também conta nulos).
ypercubeᵀᴹ
Deveria ser abs(dense_rank - dense_rank) + 1eu acredito.
norcalli
7

Você pode dense_rank()usar o valor máximo de para obter a contagem distinta de A particionada por B.

Para cuidar do caso em que A pode ter valores nulos, você pode first_valuedescobrir se um nulo está presente na partição ou não e subtrair 1 se for o sugerido por Martin Smith no comentário.

select (max(T.DenseRankA) over(partition by T.B) - 
          cast(iif(T.FirstA is null, 1, 0) as numeric(18, 8))) / T.TotalCount as A_B
from (
     select dense_rank() over(partition by T.B order by T.A) DenseRankA,
            first_value(T.A) over(partition by T.B order by T.A) as FirstA,
            count(*) over() as TotalCount,
            T.A,
            T.B
     from MyTable as T
     ) as T
Mikael Eriksson
fonte
5

Tente fazer uma subconsulta, agrupando por A, B e incluindo a contagem. Em seguida, na sua consulta externa, sua contagem (distinta) se torna uma contagem regular e sua contagem (*) se torna uma soma (cnt).

select
count(A) over (partition by B) * 1.0 / 
    sum(cnt) over() as A_B
from
(select A, B, count(*) as cnt
 from MyTable
 group by A, B) as partial;
Rob Farley
fonte