web-dev-qa-db-fra.com

SQL: comment sélectionner un seul identifiant ("rangée") répondant à plusieurs critères dans une seule colonne

J'ai une table très étroite: user_id, ascestry.

La colonne user_id est explicite.

La colonne d'ascendance contient le pays d'origine des ancêtres de l'utilisateur.

Un utilisateur peut avoir plusieurs lignes dans la table, car il peut avoir des ancêtres de plusieurs pays.

Ma question est la suivante: comment sélectionner des utilisateurs dont les ancêtres sont originaires de plusieurs pays spécifiés?

Par exemple, montrez-moi tous les utilisateurs qui ont des ancêtres d'Angleterre, de France et d'Allemagne et renvoyez une ligne par utilisateur répondant à ce critère.

Quel est ce SQL?

 user_id     ancestry

---------   ----------

    1        England
    1        Ireland
    2        France
    3        Germany
    3        Poland
    4        England
    4        France
    4        Germany
    5        France
    5        Germany

Dans le cas des données ci-dessus, je m'attendrais à un résultat de "4", car user_id 4 a des ancêtres d'Angleterre, de France et d'Allemagne.

Merci d'avance.

P.S. Pour clarifier: oui, les colonnes user_id/ascestry constituent une paire unique, de sorte qu'un pays ne soit pas répété pour un utilisateur donné.

P.P.S. Je recherche des utilisateurs provenant des 3 pays - Angleterre, France et Allemagne (et les pays sont arbitraires).

P.P.P.S. Je ne cherche pas de réponses spécifiques à un certain SGBDR. Je cherche à répondre à ce problème "en général".

Je suis content de régénérer la clause where pour chaque requête fournie, ce qui permet de générer la clause where par programmation (par exemple, je peux créer une fonction pour construire la clause WHERE/FROM - WHERE).

23
W. Young

Essaye ça:

Select user_id
from yourtable
where ancestry in ('England', 'France', 'Germany')
group by user_id
having count(user_id) = 3

La dernière ligne signifie que l'ascendance de l'utilisateur comprend les 3 pays.

21
Hong Ning
SELECT DISTINCT (user_id) 
FROM [user]
WHERE user.user_id In (select user_id from user where ancestry = 'England') 
    And user.user_id In (select user_id from user where ancestry = 'France') 
    And user.user_id In (select user_id from user where ancestry = 'Germany');`
5
HuckIt

Les utilisateurs qui ont un des les 3 pays

SELECT DISTINCT user_id
FROM table
WHERE ancestry IN('England','France','Germany')

Les utilisateurs qui ont all 3 countries

SELECT DISTINCT A.userID
FROM table A
   INNER JOIN table B on A.user_id = B.user_id
   INNER JOIN table C on A.user_id = C.user_id
WHERE A.ancestry = 'England'
   AND B.ancestry = 'Germany'
   AND C.ancestry = 'France'
4
Chains

force brute (et seulement testé sur un système Oracle, mais je pense que c'est assez standard):

select distinct usr_id from users where user_id in (
    select user_id from (
      Select user_id, Count(User_Id) As Cc
      From users 
      GROUP BY user_id
    ) Where Cc =3
  )
  and ancestry in ('England', 'France', 'Germany')
;

edit: J'aime mieux la réponse de @ HuckIt.

0
StevenV

Première manière: JOIN:

attirer des gens de plusieurs pays:

SELECT u1.user_id 
FROM users u1
JOIN users u2
on u1.user_id  = u2.user_id 
AND u1.ancestry <> u2.ancestry

Obtenez des personnes de 2 pays spécifiques:

SELECT u1.user_id 
FROM users u1
JOIN users u2
on u1.user_id  = u2.user_id 
WHERE u1.ancestry = 'Germany'
AND u2.ancestry = 'France'

Pour 3 pays ... rejoignez trois fois. Pour obtenir le résultat (s) une seule fois, distincte.

Deuxième voie: GROUP BY

Cela aura pour utilisateurs qui ont 3 lignes (ayant ... compte) et ensuite vous spécifiez quelles lignes sont autorisées. Notez que si vous n'avez pas de clé unique sur (user_id, ancestry), un utilisateur avec "id, england" qui apparaît 3 fois correspondra également ... donc cela dépend de votre structure de table et/ou de vos données.

SELECT user_id 
FROM users u1
WHERE ancestry = 'Germany'
OR ancestry = 'France'
OR ancestry = 'England'
GROUP BY user_id
HAVING count(DISTINCT ancestry) = 3
0
Konerak

comme la réponse ci-dessus, mais j'ai un enregistrement en double, donc je dois créer une sous-requête avec distinct

Select user_id
(
   select distinct userid
   from yourtable
   where user_id = @userid

) t1
where 
ancestry in ('England', 'France', 'Germany')
group by user_id
having count(user_id) = 3

c’est ce que j’ai utilisé car j’ai plusieurs enregistrements (journaux de téléchargement) et cela vérifie que tous les fichiers requis ont été téléchargés.

0
CyberNinja

l’une des méthodes permettant d’obtenir tous les identificateurs d’utilisateur correspondant à toutes les conditions est la suivante:

SELECT DISTINCT user_id FROM table WHERE ancestry IN ('England', '...', '...') GROUP BY user_id HAVING count(*) = <number of conditions that has to be satisfied> 

etc. Si vous devez prendre tous les user_ids qui remplissent au moins une condition, alors vous pouvez le faire.

SELECT DISTINCT user_id from table where ancestry IN ('England', 'France', ... , '...')

Je ne sais pas s'il existe quelque chose de similaire à IN mais qui joint les conditions avec AND au lieu de OR

0
mkk

Cette question date de quelques années, mais je suis arrivée via un duplicata. Je veux aussi suggérer une solution plus générale. Si vous savez que vous avez toujours un nombre fixe d'ancêtres, vous pouvez utiliser certaines auto-jointes comme déjà suggéré dans les réponses. Si vous voulez une approche générique, continuez à lire.

Ce dont vous avez besoin ici s’appelle Quotient en algèbre relationnelle. Le quotient est plus ou moins l'inversion du produit cartésien (ou jointure croisée en SQL). 

Disons que votre ancêtre A est (j'utilise une notation de table ici, je pense que c'est mieux pour comprendre)

ancestry
-----------
'England'
'France'
'Germany'

et votre ensemble utilisateur U est

user_id
--------
   1
   2
   3

Le produit cartésien C=AxU est alors:

user_id  |  ancestry
---------+-----------
   1     | 'England'
   1     | 'France'
   1     | 'Germany'
   2     | 'England'
   2     | 'France'
   2     | 'Germany'
   3     | 'England'
   3     | 'France'
   3     | 'Germany'

Si vous calculez le quotient défini U=C/A, alors vous obtenez

user_id
--------
   1
   2
   3

Si vous refaites le produit cartésien UXA, vous aurez à nouveau C. Mais notez que pour un ensemble T, (T/A)xA ne reproduira pas nécessairement T. Par exemple, si T est

user_id  |  ancestry
---------+-----------
   1     | 'England'
   1     | 'France'
   1     | 'Germany'
   2     | 'England'
   2     | 'France'

alors (T/A) est

user_id
--------
   1

(T/A)xA sera alors

user_id  |  ancestry
---------+------------
   1     | 'England'
   1     | 'France'
   1     | 'Germany'

Notez que les enregistrements pour user_id=2 ont été éliminés par les opérations Quotient et Produit cartésien.

Votre question est la suivante: quel user_id a des ancêtres de tous les pays de votre groupe d'ancêtres? En d'autres termes, vous voulez U=T/AT est votre ensemble d'origine (ou votre table).

Pour implémenter le quotient en SQL, vous devez suivre 4 étapes:

  1. Créez le produit cartésien de votre ensemble d'ascendance et l'ensemble de All. User_ids.
  2. Trouver tous les enregistrements du produit cartésien qui n'ont pas de partenaire dans le jeu d'origine (jointure à gauche)
  3. Extraire les user_ids du résultat de 2) 
  4. Renvoie tous les user_ids de l'ensemble d'origine qui ne sont pas inclus dans l'ensemble de résultats de 3)

Alors faisons-le étape par étape. J'utiliserai la syntaxe TSQL (serveur Microsoft SQL), mais il devrait être facilement adaptable à d'autres SGBD. En tant que nom pour la table (user_id, ancestry) je choisis ancestor

CREATE TABLE ancestry_set (ancestry nvarchar(25))
INSERT INTO ancestry_set (ancestry) VALUES ('England')
INSERT INTO ancestry_set (ancestry) VALUES ('France')
INSERT INTO ancestry_set (ancestry) VALUES ('Germany')

CREATE TABLE ancestor ([user_id] int, ancestry nvarchar(25))
INSERT INTO ancestor ([user_id],ancestry) VALUES (1,'England')
INSERT INTO ancestor ([user_id],ancestry) VALUES(1,'Ireland')
INSERT INTO ancestor ([user_id],ancestry) VALUES(2,'France')
INSERT INTO ancestor ([user_id],ancestry) VALUES(3,'Germany')
INSERT INTO ancestor ([user_id],ancestry) VALUES(3,'Poland')
INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'England')
INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'France')
INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'Germany')
INSERT INTO ancestor ([user_id],ancestry) VALUES(5,'France')
INSERT INTO ancestor ([user_id],ancestry) VALUES(5,'Germany')

1) Créez le produit cartésien de votre ensemble d'ascendance et l'ensemble de tous les user_ids. 

SELECT a.[user_id],s.ancestry
FROM ancestor a, ancestry_set s
GROUP BY a.[user_id],s.ancestry

2) Trouver tous les enregistrements du produit cartésien qui n’ont pas de partenaire dans le jeu d’origine (jointure à gauche) et

3) Extrayez les user_ids du résultat de 2)

SELECT DISTINCT cp.[user_id]
FROM (SELECT a.[user_id],s.ancestry
      FROM ancestor a, ancestry_set s
      GROUP BY a.[user_id],s.ancestry) cp
   LEFT JOIN ancestor a ON cp.[user_id]=a.[user_id] AND cp.ancestry=a.ancestry
WHERE a.[user_id] is null

4) Renvoie tous les user_ids de l'ensemble d'origine qui ne sont pas inclus dans l'ensemble de résultats de 3)

SELECT DISTINCT [user_id]
FROM ancestor
WHERE [user_id] NOT IN (
   SELECT DISTINCT cp.[user_id]
   FROM (SELECT a.[user_id],s.ancestry
         FROM ancestor a, ancestry_set s
         GROUP BY a.[user_id],s.ancestry) cp
   LEFT JOIN ancestor a ON cp.[user_id]=a.[user_id] AND cp.ancestry=a.ancestry
   WHERE a.[user_id] is null
   )
0
Martin K.