Como eu (ou posso) SELECT DISTINCT em várias colunas?

415

Eu preciso recuperar todas as linhas de uma tabela onde 2 colunas combinadas são todas diferentes. Então, eu quero todas as vendas que não têm outras vendas que aconteceram no mesmo dia pelo mesmo preço. As vendas únicas com base no dia e preço serão atualizadas para um status ativo.

Então, eu estou pensando:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Mas meu cérebro dói ir além disso.

sheats
fonte

Respostas:

436
SELECT DISTINCT a,b,c FROM t

é aproximadamente equivalente a:

SELECT a,b,c FROM t GROUP BY a,b,c

É uma boa idéia se acostumar com a sintaxe GROUP BY, pois ela é mais poderosa.

Para sua consulta, eu faria assim:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
Joel Coehoorn
fonte
117
Essa consulta, embora correta e sendo aceita por um ano, é extremamente ineficiente e desnecessariamente. Não use isso. Forneci uma alternativa e alguma explicação em outra resposta.
Erwin Brandstetter
1
SELECT DISTINCT a, b, c FROM t é exatamente o mesmo que SELECT a, b, c FROM t GROUP BY a, b, c?
famargar
8
@famargar para o caso simples, ainda, mas eles têm significados diferentes semanticamente, e são diferentes em termos do que você pode fazer para a etapa ao criar uma consulta maior. Além disso, as pessoas nos fóruns de tecnologia geralmente podem ser extremamente pedantes em relação às coisas. Acho útil adicionar palavras de doninha às minhas postagens nesse contexto.
Joel Coehoorn
344

Se você reunir as respostas até o momento, limpar e melhorar, chegaria a esta consulta superior:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

O que é muito mais rápido que qualquer um deles. Nukes o desempenho da resposta atualmente aceita pelo fator 10 - 15 (nos meus testes no PostgreSQL 8.4 e 9.1).

Mas isso ainda está longe de ser o ideal. Use uma NOT EXISTS(anti-) semi-junção para obter um desempenho ainda melhor. EXISTSé SQL padrão, existe desde sempre (pelo menos desde o PostgreSQL 7.2, muito antes da pergunta) e se encaixa perfeitamente nos requisitos apresentados:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> mexer aqui
Old SQL Fiddle

Chave exclusiva para identificar a linha

Se você não tiver uma chave primária ou exclusiva para a tabela ( idno exemplo), poderá substituir a coluna do sistema ctidpela finalidade desta consulta (mas não por outras finalidades):

   AND    s1.ctid <> s.ctid

Toda tabela deve ter uma chave primária. Adicione um se você ainda não o tiver. Sugiro uma serialou uma IDENTITYcoluna no Postgres 10+.

Relacionado:

Como isso é mais rápido?

A subconsulta na EXISTSanti-semi-junção pode parar de avaliar assim que o primeiro dupe for encontrado (não faz sentido procurar mais). Para uma tabela base com poucas duplicatas, isso é levemente mais eficiente. Com muitas duplicatas, isso se torna muito mais eficiente.

Excluir atualizações vazias

Para linhas que já possuem status = 'ACTIVE'essa atualização, isso não mudaria nada, mas ainda assim, insira uma nova versão de linha a custo total (pequenas exceções se aplicam). Normalmente, você não quer isso. Adicione outra WHEREcondição, como demonstrado acima, para evitar isso e torná-lo ainda mais rápido:

Se statusestiver definido NOT NULL, você pode simplificar para:

AND status <> 'ACTIVE';

O tipo de dados da coluna deve suportar o <>operador. Alguns tipos como jsonnão. Vejo:

Diferença sutil no manuseio de NULL

Esta consulta (diferente da resposta atualmente aceita por Joel ) não trata valores NULL como iguais. As duas linhas a seguir (saleprice, saledate)serão classificadas como "distintas" (embora pareçam idênticas ao olho humano):

(123, NULL)
(123, NULL)

Também passa em um índice exclusivo e quase em qualquer outro lugar, já que os valores NULL não comparam iguais de acordo com o padrão SQL. Vejo:

OTOH, GROUP BY, DISTINCTou DISTINCT ON ()valores mimo nulo como igual. Use um estilo de consulta apropriado, dependendo do que você deseja alcançar. Você ainda pode usar essa consulta mais rápida com em IS NOT DISTINCT FROMvez de= em qualquer uma ou todas as comparações, para tornar a comparação NULL igual. Mais:

Se todas as colunas comparadas estiverem definidas NOT NULL, não haverá espaço para discordância.

Erwin Brandstetter
fonte
16
Boa resposta. Como sou um servidor de sql, a primeira sugestão de usar uma tupla com uma verificação IN () não me ocorreria. A sugestão inexistente geralmente terminará com o mesmo plano de execução no servidor sql que a junção interna.
Joel Coehoorn
2
Agradável. A explicação aumenta muito o valor da resposta. Estou quase tentado a executar alguns testes com o Oracle para ver como os planos se comparam com o Postgres e o SQLServer.
Peter Peter
2
@alairock: Onde você conseguiu isso? Para o Postgres, o oposto é verdadeiro. Ao contar todas as linhas, count(*)é mais eficiente que count(<expression>). Apenas tente. O Postgres possui uma implementação mais rápida para essa variante da função agregada. Talvez você esteja confundindo o Postgres com outros RDBMS?
Erwin Brandstetter
6
@alairock: Eu sou co-autor dessa página e ela não diz nada do tipo.
Erwin Brandstetter
2
@ ErwinBrandstetter, você está sempre certo com suas respostas em toda a pilha. Você ajudou ao longo dos anos de uma maneira quase inimaginável. Quanto a este exemplo, eu sabia algumas maneiras diferentes de resolver meu problema, mas queria ver que alguém havia testado a eficiência entre as possibilidades. Obrigado.
WebWanderer
24

O problema com sua consulta é que, ao usar uma cláusula GROUP BY (que você basicamente usa distinta), você pode usar apenas colunas que agrupa ou agrega funções. Você não pode usar o ID da coluna porque existem valores potencialmente diferentes. No seu caso, sempre há apenas um valor por causa da cláusula HAVING, mas a maioria dos RDBMS não é inteligente o suficiente para reconhecer isso.

No entanto, isso deve funcionar (e não precisa de associação):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

Você também pode usar MAX ou AVG em vez de MIN, é importante usar apenas uma função que retorne o valor da coluna se houver apenas uma linha correspondente.

Christian Berg
fonte
1

Desejo selecionar os valores distintos de uma coluna 'GrondOfLucht', mas eles devem ser classificados na ordem em que são fornecidos na coluna 'classificação'. Não consigo obter os valores distintos de apenas uma coluna usando

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Ele também fornecerá a coluna 'classificação' e, como 'GrondOfLucht' AND 'classificar' não é exclusivo, o resultado será TODAS as linhas.

use o GROUP para selecionar os registros de 'GrondOfLucht' na ordem dada por 'classificação

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
frans eilering
fonte
Isso basicamente explica o que a resposta aceita faz, mas eu recomendo não usar esses nomes como exemplo (pelo menos traduzi-los). PS: Eu recomendo sempre nomear tudo em inglês em todos os projetos, mesmo que você seja holandês.
Kerwin Sneijders
0

Se o seu DBMS não suportar distintos com várias colunas como esta:

select distinct(col1, col2) from table

A seleção múltipla em geral pode ser executada com segurança da seguinte maneira:

select distinct * from (select col1, col2 from table ) as x

Como isso pode funcionar na maioria dos DBMS, espera-se que seja mais rápido que agrupar por solução, pois você está evitando a funcionalidade de agrupamento.

Abdulhafeth Sartawi
fonte