Sql Server equivalente a uma função agregada COUNTIF

164

Estou criando uma consulta com uma GROUP BYcláusula que precisa da capacidade de contar registros com base apenas em uma determinada condição (por exemplo, contar apenas registros onde um determinado valor da coluna é igual a 1).

SELECT  UID, 
        COUNT(UID) AS TotalRecords, 
        SUM(ContractDollars) AS ContractDollars,
        (COUNTIF(MyColumn, 1) / COUNT(UID) * 100) -- Get the average of all records that are 1
FROM    dbo.AD_CurrentView
GROUP BY UID
HAVING  SUM(ContractDollars) >= 500000

A COUNTIF()linha obviamente falhar uma vez que não há nenhuma função SQL nativa chamada COUNTIF, mas a idéia aqui é determinar a porcentagem de todas as linhas que têm o valor '1' para MyColumn.

Alguma idéia de como implementar isso corretamente em um ambiente MS SQL 2005?

senfo
fonte

Respostas:

339

Você pode usar um SUM(não COUNT!) Combinado com uma CASEdeclaração, assim:

SELECT SUM(CASE WHEN myColumn=1 THEN 1 ELSE 0 END)
FROM AD_CurrentView

Nota: em meus próprios testes NULL, não foram um problema, embora isso possa depender do ambiente. Você pode lidar com nulos, como:

SELECT SUM(CASE WHEN ISNULL(myColumn,0)=1 THEN 1 ELSE 0 END)
FROM AD_CurrentView
JoshBerke
fonte
3
(Eu sei que o OP perguntou sobre o MS SQL, mas apenas um pequeno comentário para usuários do SQLite que fazem a mesma coisa) O SQLite não tem ISNULL, em vez disso, você pode fazer CASE WHEN myColumn IS NULLou usar ifnull( stackoverflow.com/a/799406/1861346 )
Matt
54

Eu costumo fazer o que Josh recomendou, mas fiz um brainstorming e testei uma alternativa um pouco chata que eu queria compartilhar.

Você pode aproveitar o fato de que COUNT (ColumnName) não conta NULLs e usar algo como isto:

SELECT COUNT(NULLIF(0, myColumn))
FROM AD_CurrentView

NULLIF - retorna NULL se os dois valores passados ​​forem iguais.

Vantagem: expressa sua intenção de COUNT linhas em vez de ter a notação SUM (). Desvantagem: Não é tão claro como está funcionando ("mágica" geralmente é ruim).

Chris Shaffer
fonte
2
Esta solução pode
fornecer
Post antigo, mas graças a isso ajudou. Eu estendi a magia e cheguei a "única anula" problema adicionando ISNULLo seguinte: SELECT COUNT(NULLIF(0, ISNULL(myColumn, 0))). Espere, que só olha feio ...
pcdev
1
Seria perfeito se houvesse uma função
NULLIFNOT
21

Eu usaria essa sintaxe. Ele obtém o mesmo que as sugestões de Josh e Chris, mas com a vantagem de ser compatível com ANSI e não vinculado a um fornecedor de banco de dados específico.

select count(case when myColumn = 1 then 1 else null end)
from   AD_CurrentView
asgeo1
fonte
2
A resposta de Chris é compatível com Stndard SQL (dica: NULLIFestá incluído o padrão SQL-92). A resposta de Josh pode ser facilmente transformada no SQL padrão substituindo isnullpor COALESCE.
onedaywhen
Na verdade, eu gosto mais dessa resposta, porque ela tem a ideia de "contar linhas" que Chris estava mostrando, mas é mais extensível, pois você pode usar qualquer operador de comparação; não apenas =. Estou usando para "contar o número de respostas> = 2".
Kristen Hammack
3

Adicionando a resposta de Josh,

SELECT COUNT(CASE WHEN myColumn=1 THEN AD_CurrentView.PrimaryKeyColumn ELSE NULL END)
FROM AD_CurrentView

Funcionou bem para mim (no SQL Server 2012) sem alterar a 'contagem' para uma 'soma' e a mesma lógica é portável para outros 'agregados condicionais'. Por exemplo, somando com base em uma condição:

SELECT SUM(CASE WHEN myColumn=1 THEN AD_CurrentView.NumberColumn ELSE 0 END)
FROM AD_CurrentView
Sturgus
fonte
2

E se

SELECT id, COUNT(IF status=42 THEN 1 ENDIF) AS cnt
FROM table
GROUP BY table

Menor que CASE:)

Funciona porque COUNT()não conta valores nulos e IF/ CASEretorna nulo quando a condição não é atendida e não existe ELSE.

Eu acho que é melhor do que usar SUM().

maf-soft
fonte
1

Não é específico do produto, mas o padrão SQL fornece

SELECT COUNT() FILTER WHERE <condition-1>, COUNT() FILTER WHERE <condition-2>, ... FROM ...

para esse fim. Ou algo que se parece muito com isso, não sei de nada.

E, é claro, os fornecedores preferem manter suas soluções proprietárias.

Erwin Smout
fonte
1
Eu nunca tinha ouvido falar disso antes, então procurei. De acordo com modern-sql.com/feature/filter, o único DBMS importante que realmente oferece a FILTERcláusula é o PostgreSQL, mas é emulado por CASEtodos eles.
Kristen Hammack
1

Por que não assim?

SELECT count(1)
FROM AD_CurrentView
WHERE myColumn=1
Michal
fonte
1
Porque ele precisa de muito mais do que apenas a contagem. Ele está tentando obter o número de linhas de uma parte de um grupo e, em seguida, um agregado de todo o grupo, o que você não pode fazer com um WHERE.
Kristen Hammack
1

Eu tive que usar COUNTIF () no meu caso como parte das minhas colunas SELECT E para imitar uma% do número de vezes que cada item apareceu nos meus resultados.

Então eu usei isso ...

SELECT COL1, COL2, ... ETC
       (1 / SELECT a.vcount 
            FROM (SELECT vm2.visit_id, count(*) AS vcount 
                  FROM dbo.visitmanifests AS vm2 
                  WHERE vm2.inactive = 0 AND vm2.visit_id = vm.Visit_ID 
                  GROUP BY vm2.visit_id) AS a)) AS [No of Visits],
       COL xyz
FROM etc etc

Obviamente, você precisará formatar o resultado de acordo com os requisitos de exibição.

Fandango68
fonte
-2
SELECT COALESCE(IF(myColumn = 1,COUNT(DISTINCT NumberColumn),NULL),0) column1,
COALESCE(CASE WHEN myColumn = 1 THEN COUNT(DISTINCT NumberColumn) ELSE NULL END,0) AS column2
FROM AD_CurrentView
Andres Sarria
fonte