SQL / mysql - Sélectionnez distinct / UNIQUE mais retournez toutes les colonnes?

374
SELECT DISTINCT field1, field2, field3, ......   FROM table

J'essaye d'accomplir l'instruction sql suivante mais je veux qu'elle retourne toutes les colonnes est-ce possible? Quelque chose comme:

SELECT DISTINCT field1, * from table
aryaxt
la source
12
Pourquoi ne SELECT DISTINCT * FROM tablefonctionne pas pour vous?
ypercubeᵀᴹ
19
Si votre table a un PK, toutes les lignes doivent l'être distinctpar définition. Si vous essayez simplement de sélectionner DISTINCT field1mais de renvoyer toutes les autres colonnes, que doit-il se passer pour les colonnes qui ont plus d'une valeur pour une field1valeur particulière ? Vous auriez besoin d'utiliser GROUP BYet une sorte d'agrégation sur les autres colonnes par exemple.
Martin Smith,
1
Si vous voulez des lignes répétées et pas seulement des lignes distinctes, supprimez le mot clé distinct.
Hyperboreus
2
Pourriez-vous donner un exemple de ce que vous attendez des résultats? Jusqu'à présent, je ne peux donner aucun sens à votre requête souhaitée.
récursif
3
Voici la réponse à une question similaire posée, vous devez d'abord obtenir la colonne distincte avec leurs identifiants, puis la joindre à la table d'origine. SELECT DISTINCT sur une colonne, retourne plusieurs autres colonnes
yadavr

Réponses:

407

Vous recherchez un groupe par:

select *
from table
group by field1

Qui peut parfois être écrit avec une déclaration distincte:

select distinct on field1 *
from table

Sur la plupart des plates-formes, cependant, aucun des éléments ci-dessus ne fonctionnera car le comportement sur les autres colonnes n'est pas spécifié. (Le premier fonctionne dans MySQL, si c'est ce que vous utilisez.)

Vous pouvez récupérer les champs distincts et vous contenter de choisir une seule ligne arbitraire à chaque fois.

Sur certaines plates-formes (par exemple PostgreSQL, Oracle, T-SQL), cela peut être fait directement en utilisant des fonctions de fenêtre:

select *
from (
   select *,
          row_number() over (partition by field1 order by field2) as row_number
   from table
   ) as rows
where row_number = 1

Sur d'autres (MySQL, SQLite), vous devrez écrire des sous-requêtes qui vous feront joindre la table entière avec elle-même ( exemple ), donc pas recommandé.

Denis de Bernardy
la source
10
La requête n'analysera pas pour moi et donne une erreur: The ranking function "row_number" must have an ORDER BY clause. Nous devons ajouter la clause order by après la partition par field1. Donc, la bonne requête sera select * from ( select *, row_number() over (partition by field1 order by orderbyFieldName) as row_number from table ) as rows where row_number = 1
Ankur-m
1
Merci! J'étais dans le même problème et la solution était le GROUP BY
Joaquin Iurchuk
2
Également dans Oracle (Oracle SQL Developer), vous ne pouvez pas spécifier select *, row_number() over (partition by field1 order by field2) as row_number from table. Vous devez utiliser explicitement le nom / alias de table dans la requête de sélectionselect **table**.*, row_number() over (partition by field1 order by field2) as row_number from table
meta4
1
@jarlh: Peut-être ... aujourd'hui. Comme vous pouvez le remarquer, cette réponse a presque 7 ans, un moment où ce n'était pas le cas dans la mesure où je me souviens de l'arrière quand j'étais actif. Vous êtes invités à retagger et / ou modifier la réponse si vous le jugez nécessaire.
Denis de Bernardy
2
select distinct on (field1) * from table; fonctionne aussi dans PostgreSQL
Chilianu Bogdan
61

D'après la formulation de votre question, je comprends que vous souhaitez sélectionner les valeurs distinctes pour un champ donné et pour chacune de ces valeurs avoir toutes les autres valeurs de colonne dans la même ligne répertoriées. La plupart des SGBD ne permettent pas cela avec ni DISTINCTni GROUP BY, car le résultat n'est pas déterminé.

Pensez-y comme ceci: si votre field1se produit plus d'une fois, quelle valeur de field2sera répertoriée (étant donné que vous avez la même valeur pour field1dans deux lignes mais deux valeurs distinctes de field2dans ces deux lignes).

Vous pouvez cependant utiliser des fonctions d'agrégation (explicitement pour chaque champ que vous souhaitez afficher) et utiliser un GROUP BYau lieu de DISTINCT:

SELECT field1, MAX(field2), COUNT(field3), SUM(field4), .... FROM table GROUP BY field1
Costi Ciudatu
la source
4
+1 pour cette solution. Donc, nous pouvons le faire SELECT field1, MIN(field2), MIN(field3), MIN(field4), .... FROM table GROUP BY field1, et les champs2, 3, 4 ,,, ne doivent pas nécessairement être des entiers (ou d'autres chiffres), ils peuvent également être des champs char
traquer le
Fonctionnait bien jusqu'à ce que je sois coincé dans une colonne booléenne. Les valeurs de colonne MIN (Dynamique) sont modifiées en false même si c'était vrai. Toute autre fonction d'agrégation disponible pour adresser boolean - signonsridhar il y a 6 minutes. Sum (dynamic) changé false en 1
signonsridhar
1
Grande suggestion, m'a conduit à ma solution qui, je pense, est plus universelle - jetez un coup d'œil!
Garrett Simpson
@signonsridhar transforme votre booléen en entier et utilise la somme; par exemplesum(cast(COL as int)) > 0
Drew
26

Si j'ai bien compris votre problème, il est similaire à celui que je viens d'avoir. Vous voulez pouvoir limiter l'utilisabilité de DISTINCT à un champ spécifié, plutôt que de l'appliquer à toutes les données.

Si vous utilisez GROUP BY sans fonction d'agrégation, quel champ que vous regroupez GROUP BY sera votre fichier DISTINCT.

Si vous faites votre requête:

SELECT * from table GROUP BY field1;

Il affichera tous vos résultats sur la base d'une seule instance de field1.

Par exemple, si vous avez une table avec nom, adresse et ville. Une seule personne a plusieurs adresses enregistrées, mais vous voulez juste une seule adresse pour la personne, vous pouvez interroger comme suit:

SELECT * FROM persons GROUP BY name;

Le résultat sera qu'une seule instance de ce nom apparaîtra avec son adresse, et l'autre sera omise de la table résultante. Attention: si vos fichiers ont des valeurs atomiques telles que firstName, lastName que vous souhaitez regrouper par les deux.

SELECT * FROM persons GROUP BY lastName, firstName;

car si deux personnes ont le même nom de famille et que vous ne les regroupez que par nom, l'une de ces personnes sera omise des résultats. Vous devez garder ces choses en considération. J'espère que cela t'aides.

rocklandcitizen
la source
Comme mentionné dans la réponse acceptée, fonctionnerait pour la plupart des incarnations de SQL - uniquement pour MYSQL
Garrett Simpson
15
SELECT  c2.field1 ,
        field2
FROM    (SELECT DISTINCT
                field1
         FROM   dbo.TABLE AS C
        ) AS c1
        JOIN dbo.TABLE AS c2 ON c1.field1 = c2.field1
Orageux
la source
Pourquoi y a-t-il C aliasquand il peut fonctionner sans lui? en ligneFROM dbo.TABLE AS C
Talha
2
Je pense que cela est dû à mon utilisation de RedGate SQLPrompt. De la façon dont je l'ai configuré, il ajoute toujours des alias - même si cela n'est pas nécessaire. C'est là "juste au cas où"
Stormy
Cela semblait prometteur pour moi, mais cela a toujours ramené toutes les lignes, pas le champ distinct1. :(
Michael Fever
13

Voilà une très bonne question. J'ai déjà lu quelques réponses utiles ici, mais je peux probablement ajouter une explication plus précise.

La réduction du nombre de résultats de requête avec une instruction GROUP BY est facile tant que vous ne recherchez pas d'informations supplémentaires. Supposons que vous obteniez le tableau suivant «emplacements».

--country-- --city--
 France      Lyon
 Poland      Krakow
 France      Paris
 France      Marseille
 Italy       Milano

Maintenant, la requête

SELECT country FROM locations
GROUP BY country

aura pour résultat:

--country--
 France
 Poland
 Italy

Cependant, la requête suivante

SELECT country, city FROM locations
GROUP BY country

... lance une erreur dans MS SQL, car comment votre ordinateur pourrait-il savoir laquelle des trois villes françaises "Lyon", "Paris" ou "Marseille" vous voulez lire dans le champ à droite de "France"?

Afin de corriger la deuxième requête, vous devez ajouter ces informations. Pour ce faire, vous pouvez utiliser les fonctions MAX () ou MIN () en sélectionnant la valeur la plus grande ou la plus petite parmi tous les candidats. MAX () et MIN () ne s'appliquent pas seulement aux valeurs numériques, mais comparent également l'ordre alphabétique des valeurs de chaîne.

SELECT country, MAX(city) FROM locations
GROUP BY country

aura pour résultat:

--country-- --city--
 France      Paris
 Poland      Krakow
 Italy       Milano

ou:

SELECT country, MIN(city) FROM locations
GROUP BY country

aura pour résultat:

--country-- --city--
 France      Lyon
 Poland      Krakow
 Italy       Milano

Ces fonctions sont une bonne solution tant que vous êtes d'accord avec la sélection de votre valeur dans les deux extrémités de l'ordre alphabétique (ou numérique). Mais que faire si ce n'est pas le cas? Supposons que vous ayez besoin d'une valeur avec une certaine caractéristique, par exemple en commençant par la lettre «M». Maintenant, les choses se compliquent.

La seule solution que j'ai pu trouver jusqu'à présent est de mettre l'intégralité de votre requête dans une sous-requête et de construire la colonne supplémentaire en dehors d'elle à la main:

SELECT
     countrylist.*,
     (SELECT TOP 1 city
     FROM locations
     WHERE
          country = countrylist.country
          AND city like 'M%'
     )
FROM
(SELECT country FROM locations
GROUP BY country) countrylist

aura pour résultat:

--country-- --city--
 France      Marseille
 Poland      NULL
 Italy       Milano
Ulf Sanne
la source
5

Grande question @aryaxt - vous pouvez dire que c'était une excellente question parce que vous l'avez posée il y a 5 ans et je suis tombée dessus aujourd'hui en essayant de trouver la réponse!

J'ai juste essayé de modifier la réponse acceptée pour l'inclure, mais au cas où ma modification ne se ferait pas dans:

Si votre table n'était pas si grande et en supposant que votre clé primaire était un entier à incrémentation automatique, vous pourriez faire quelque chose comme ceci:

SELECT 
  table.*
FROM table
--be able to take out dupes later
LEFT JOIN (
  SELECT field, MAX(id) as id
  FROM table
  GROUP BY field
) as noDupes on noDupes.id = table.id
WHERE
  //this will result in only the last instance being seen
  noDupes.id is not NULL
Garrett Simpson
la source
5

Essayer

SELECT table.* FROM table 
WHERE otherField = 'otherValue'
GROUP BY table.fieldWantedToBeDistinct
limit x
Pedro Ramos
la source
3

Vous pouvez le faire avec une WITHclause.

Par exemple:

WITH c AS (SELECT DISTINCT a, b, c FROM tableName)
SELECT * FROM tableName r, c WHERE c.rowid=r.rowid AND c.a=r.a AND c.b=r.b AND c.c=r.c

Cela vous permet également de sélectionner uniquement les lignes sélectionnées dans la WITHrequête de clauses.

user2225399
la source
2

Pour SQL Server, vous pouvez utiliser le dense_rank et les fonctions de fenêtrage supplémentaires pour obtenir toutes les lignes ET les colonnes avec des valeurs dupliquées sur les colonnes spécifiées. Voici un exemple...

with t as (
    select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r1' union all
    select col1 = 'c', col2 = 'b', col3 = 'a', other = 'r2' union all
    select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r3' union all
    select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r4' union all
    select col1 = 'c', col2 = 'b', col3 = 'a', other = 'r5' union all
    select col1 = 'a', col2 = 'a', col3 = 'a', other = 'r6'
), tdr as (
    select 
        *, 
        total_dr_rows = count(*) over(partition by dr)
    from (
        select 
            *, 
            dr = dense_rank() over(order by col1, col2, col3),
            dr_rn = row_number() over(partition by col1, col2, col3 order by other)
        from 
            t
    ) x
)

select * from tdr where total_dr_rows > 1

Cela prend un nombre de lignes pour chaque combinaison distincte de col1, col2 et col3.

dotjoe
la source
trop compliqué et spécifique à une implémentation de SQL
Garrett Simpson
1
select min(table.id), table.column1
from table 
group by table.column1
KadoJ
la source
Cela a fonctionné pour moi !! Cela vaut la peine de le noter, si vous utilisez fetch_array (), vous devrez alors appeler chaque ligne via une étiquette d'index plutôt que d'appeler implicitement le nom de la ligne. Il n'y a pas assez de caractères pour écrire l'exemple que j'ai: X désolé !!
Brandon Printiss
0
SELECT *
FROM tblname
GROUP BY duplicate_values
ORDER BY ex.VISITED_ON DESC
LIMIT 0 , 30

dans ORDER BYje viens de mettre l'exemple ici, vous pouvez également ajouter un champ ID dans ce

SagarPPanchal
la source
Comme mentionné dans la réponse acceptée, fonctionnerait pour la plupart des incarnations de SQL - uniquement pour MYSQL
Garrett Simpson
0

J'ai trouvé cela ailleurs ici, mais c'est une solution simple qui fonctionne:

 WITH cte AS /* Declaring a new table named 'cte' to be a clone of your table */
 (SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY val1 DESC) AS rn
 FROM MyTable /* Selecting only unique values based on the "id" field */
 )
 SELECT * /* Here you can specify several columns to retrieve */
 FROM cte
 WHERE rn = 1
Michael Fever
la source
Fonctionne pour MSSQL
Michael Fever
-1

Ajoutez GROUP BY au champ que vous souhaitez vérifier pour les doublons auxquels votre requête pourrait ressembler

SELECT field1, field2, field3, ......   FROM table GROUP BY field1

field1 sera vérifié pour exclure les enregistrements en double

ou vous pouvez interroger comme

SELECT *  FROM table GROUP BY field1

les enregistrements en double du champ1 sont exclus de SELECT

iCodeCrew
la source
1
La clause GROUP BY doit correspondre aux champs sélectionnés. sinon, il filed2 must appear in the GROUP BY clause or be used in an aggregate function
générera une
-2

Incluez simplement tous vos champs dans la clause GROUP BY.

wayneh
la source
3
Pour en faire une bonne réponse, vous devez inclure un peu plus de détails sur ce que vous voulez dire.
Robbert
-2

Cela peut être fait par requête interne

$query = "SELECT * 
            FROM (SELECT field
                FROM table
                ORDER BY id DESC) as rows               
            GROUP BY field";
Zaheer Babar
la source
2
Cela ne répond pas à la question, l'OP tentait d'obtenir toutes les données de la table mais supprimait les lignes contenant des doublons d'un seul champ
Garrett Simpson
-3
SELECT * from table where field in (SELECT distinct field from table)
Andrew
la source
7
Cela ne fera pas l'affaire. Vous avez sélectionné la colonne distincte dans la sous-requête, mais la clause where obtient toutes ces colonnes avec cette valeur. Ainsi, la requête équivaut à écrire «sélectionner * dans la table», à moins que la colonne «champ» ne soit une colonne unique, auquel cas le distinct sur cette colonne n'est pas du tout requis.
Ankur-m
-3

SELECT DISTINCT FIELD1, FIELD2, FIELD3 FROM TABLE1 fonctionne si les valeurs des trois colonnes sont uniques dans la table.

Si, par exemple, vous avez plusieurs valeurs identiques pour le prénom, mais que le nom et les autres informations dans les colonnes sélectionnées sont différents, l'enregistrement sera inclus dans le jeu de résultats.

Doris Gammenthaler
la source
2
Cela ne répond pas à la question, l'OP tentait d'obtenir toutes les données de la table mais supprimait les lignes contenant des doublons d'un seul champ
Garrett Simpson
-3

Je suggère d'utiliser

SELECT  * from table where field1 in 
(
  select distinct field1 from table
)

De cette façon, si vous avez la même valeur dans field1 sur plusieurs lignes, tous les enregistrements seront retournés.

Ioannis K
la source
1
Ce n'est pas différent avec SELECT * FROM table;. Encore plus C'est lent.
Shin Kim
S'il vous plaît, essayez d'abord votre réponse.
Sherif