DISTINCT para apenas uma coluna

155

Digamos que tenho a seguinte consulta.

SELECT ID, Email, ProductName, ProductModel FROM Products

Como posso modificá-lo para que ele não retorne e-mails duplicados?

Em outras palavras, quando várias linhas contêm o mesmo email, desejo que os resultados incluam apenas uma dessas linhas (de preferência a última). Duplicatas em outras colunas devem ser permitidas.

Cláusulas gostam DISTINCTe GROUP BYparecem funcionar em linhas inteiras. Portanto, não tenho certeza de como abordar isso.

Jonathan Wood
fonte
2
Ok, você precisa usar PARTITION ou usar duas instruções select?
CarneyCode
E o que deve ser mostrado se houver, digamos, 2 linhas com o mesmo email, mas ProductName diferente? O (de preferência o último) não está claro. Última por qual pedido?
ypercubeᵀᴹ
@ypercube Como indicado na pergunta, de preferência a última. No entanto, isso não é realmente crítico para mim. Eu só quero um deles.
Jonathan Wood
1
Você pode examinar as seguintes perguntas: pergunta1 , pergunta2 ou pergunta3 .
Marian
Por que você não pode usar: SELECT DISTINCT Email, ID, ProductName, ProductModel FROM Products?
Rick Henderson

Respostas:

186

Se você estiver usando o SQL Server 2005 ou superior, use este:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
              ) a
WHERE rn = 1

EDIT: Exemplo usando uma cláusula where:

SELECT *
  FROM (
                SELECT  ID, 
                        Email, 
                        ProductName, 
                        ProductModel,
                        ROW_NUMBER() OVER(PARTITION BY Email ORDER BY ID DESC) rn
                    FROM Products
                   WHERE ProductModel = 2
                     AND ProductName LIKE 'CYBER%'

              ) a
WHERE rn = 1
Chandu
fonte
4
Devo investigar esta cláusula PARTITION, nunca a vi em ação antes. Obrigado pelo exemplo
LorenVS
@ Cybernate Uma complicação: meu interior SELECTprecisa de uma WHEREcondição. Estou pensando que os números das linhas serão atribuídos a todas as linhas da tabela. Essa sintaxe está um pouco além de mim. Alguma chance de uma atualização que garanta uma linha com um email específico que atenda à WHEREcondição?
Jonathan Wood
1
Você pode adicionar a cláusula where ao sql interno. Vou atualizar o post uma vez que eu consigo acessar meu laptop
Chandu
1
Atualizou a postagem com uma amostra usando a cláusula where.
Chandu
1
Eu só consigo funcionar corretamente quando não JOIN tenho s na minha consulta. Assim que eu tiver um JOIN, o valor ROW_NUMBERretornará valores muito mais altos que "1".
Uwe Keim 23/02
10

Isso pressupõe o SQL Server 2005+ e sua definição de "last" é a PK máxima para um determinado email

WITH CTE AS
(
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel, 
       ROW_NUMBER() OVER (PARTITION BY Email ORDER BY ID DESC) AS RowNumber 
FROM   Products
)
SELECT ID, 
       Email, 
       ProductName, 
       ProductModel
FROM CTE 
WHERE RowNumber = 1
Pero P.
fonte
6

Quando você usa, DISTINCTpense nela como uma linha distinta, não como uma coluna. Ele retornará apenas linhas nas quais as colunas não coincidem exatamente da mesma forma.

SELECT DISTINCT ID, Email, ProductName, ProductModel
FROM Products

----------------------
1 | something@something.com | ProductName1 | ProductModel1
2 | something@something.com | ProductName1 | ProductModel1

A consulta retornaria as duas linhas porque a IDcoluna é diferente. Estou assumindo que a IDcoluna é uma IDENTITYcoluna que está aumentando, se você quiser retornar a última, recomendo algo como isto:

SELECT DISTINCT TOP 1 ID, Email, ProductName, ProductModel
FROM Products
ORDER BY ID DESC

Ele TOP 1retornará apenas o primeiro registro, ordenando-o por ordem IDdecrescente, retornará os resultados com a última linha primeiro. Isso lhe dará o último registro.

jon3laze
fonte
2
Conforme declarado na pergunta, vejo que DISTINCT funciona em toda a linha. Quero fazer o que você sugere acima, mas sempre que o email é duplicado nos resultados (não apenas uma vez).
Jonathan Wood
Nesse caso, eu recomendaria usar a resposta @Cybernate. Isso deve fazer exatamente o que você precisa.
jon3laze
4

Você pode superar isso usando a função GROUP BY

SELECT ID, Email, ProductName, ProductModel FROM Products GROUP BY Email

Marshall Unduemi
fonte
16
A coluna 'Products.ID' é inválida na lista de seleção porque não está contida em uma função agregada ou na cláusula GROUP BY.
palota
2
Isso não funciona sem o uso de algo como MAX (ID), MAX (ProductName), MAX (ProductModel) para as outras colunas
avl_sweden
2
No postgres, você só precisa da função agregada na coluna que será usada no grupo por cláusula, por exemplo SELECT id, max(email) AS email FROM tbl GROUP by email. No servidor SQL, TODAS as colunas da SELECTcláusula devem estar em uma função agregada. Isso me morde toda vez que eu volto.
Bruce Pierson
Isso nunca vai funcionar. É uma má solução
Dan AS
1

Para o Access, você pode usar a consulta SQL Select que apresento aqui:

Por exemplo, você tem esta tabela:

CLIENTE || NOMBRES || ENVIAR

888 || T800 ARNOLD || [email protected]

123 || JOHN CONNOR || [email protected]

125 || SARAH CONNOR ||[email protected]

E você precisa selecionar apenas e-mails distintos. Você pode fazer isso com isso:

SQL SELECT:

SELECT MAX(p.CLIENTE) AS ID_CLIENTE
, (SELECT TOP 1 x.NOMBRES 
    FROM Rep_Pre_Ene_MUESTRA AS x 
    WHERE x.MAIL=p.MAIL 
     AND x.CLIENTE=(SELECT MAX(l.CLIENTE) FROM Rep_Pre_Ene_MUESTRA AS l WHERE x.MAIL=l.MAIL)) AS NOMBRE, 
p.MAIL
FROM Rep_Pre_Ene_MUESTRA AS p
GROUP BY p.MAIL;

Você pode usar isso para selecionar o ID máximo, o nome correspondente ao ID máximo, e pode adicionar qualquer outro atributo dessa maneira. Então, no final, você coloca a coluna distinta para filtrar e a agrupa apenas com a última coluna distinta.

Isso fornecerá o ID máximo com os dados correspondentes, você poderá usar min ou qualquer outra função e replicar essa função nas subconsultas.

Essa seleção retornará:

CLIENTE || NOMBRES || ENVIAR

888 || T800 ARNOLD || [email protected]

125 || SARAH CONNOR ||[email protected]

Lembre-se de indexar as colunas selecionadas e a coluna distinta não deve ter dados numéricos, todos em maiúsculas ou minúsculas; caso contrário, não funcionará. Isso funcionará com apenas um correio registrado também. Feliz codificação !!!

jRam90
fonte
0

O motivo DISTINCTe o GROUP BYtrabalho em linhas inteiras é que sua consulta retorna linhas inteiras.

Para ajudar você a entender: Tente escrever à mão o que a consulta deve retornar e você verá que é ambíguo o que colocar nas colunas não duplicadas.

Se você literalmente não se importa com o que está nas outras colunas, não as devolva. Retornar uma linha aleatória para cada endereço de email parece um pouco inútil para mim.

JohnFx
fonte
@JohnFix Quero retornar linhas inteiras. Só não quero que as linhas sejam retornadas quando os resultados já incluem uma linha com o mesmo valor na coluna E-mail.
Jonathan Wood
Então, como ele deve decidir qual retornar? Deseja realmente uma consulta que retorne uma linha arbitrária para cada email. Isso realmente cheira como você pode precisar repensar o problema que está tentando resolver. Quase toda vez que me fazem essa pergunta (e ela aparece muito), o desenvolvedor não pensou nas consequências do aplicativo para esse comportamento.
31411 JohnFx
6
Estou realmente tendo problemas para seguir sua lógica. Conforme indicado na pergunta, eu preferiria o último (classificado por ID). Sim, se ele selecionasse uma linha aleatória, tudo bem. E, sim, eu pensei sobre isso.
Jonathan Wood
0

Tente isto

;With Tab AS (SELECT DISTINCT Email FROM  Products)
SELECT Email,ROW_NUMBER() OVER(ORDER BY Email ASC) AS  Id FROM Tab
ORDER BY Email ASC
Abdullah Yousuf
fonte
-2

Tente o seguinte:

SELECT ID, Email, ProductName, ProductModel FROM Products WHERE ID IN (SELECT MAX(ID) FROM Products GROUP BY Email)
Сергей Пустовит
fonte
2
Por que devemos tentar isso? Por que isso é melhor do que as outras respostas postadas aqui nos últimos 8 anos? Se você deseja compartilhar uma maneira melhor de resolver o problema, é necessário explicar por que o recomenda.
Dharman