2017-10-03 14 views
0

J'ai un problème abstrait qui peut être simplifié comme le problème suivant: Supposons que nous avons deux tables persons et names qui ressemblent comme suit:Comment écrire une requête MySQL SELECT pour atteindre ce résultat?

SELECT * FROM persons; 
+----+-------+--------+ 
| id | name | fan_of | 
+----+-------+--------+ 
| 1 | alice |  2 | 
| 2 | bob |  4 | 
| 3 | carol |  1 | 
| 4 | dave |  3 | 
| 5 | bob |  2 | 
+----+-------+--------+ 

et

SELECT * FROM names; 
+----+-------+--------+ 
| id | name | active | 
+----+-------+--------+ 
| 1 | alice |  1 | 
| 2 | bob |  1 | 
| 3 | carol |  0 | 
| 4 | dave |  1 | 
+----+-------+--------+ 

Toute personne (une rangée dans la table persons) est un fan de lui-même ou d'une autre personne (représentée par les autres personnes id dans la colonne fan_of). La table names contient des noms qui peuvent être actifs ou inactifs.

Pour un offset donné k, je veux SELECT les personnes (lignes de persons) qui ont le nom k+1 -ème actif comme leur nom ou qui ont une de ces personnes que leurs fans. Par exemple, si le décalage est 1, le deuxième nom actif est bob et je veux donc sélectionner toutes les personnes avec le nom bob plus les personnes qui ont un de ces bobs en tant que leurs fans, qui est dans cet exemple la rangée de personnes avec id = 4. Cela signifie que je veux avoir le résultat:

+----+------+--------+ 
| id | name | fan_of | 
+----+------+--------+ 
| 2 | bob |  4 | 
| 4 | dave |  3 | 
| 5 | bob |  2 | 
+----+------+--------+ 

Ce que j'ai à ce jour est la requête suivante:

1 SELECT * FROM persons WHERE 
    2  EXISTS (
    3   SELECT * FROM (
    4    SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1 
    5  ) AS selectedname WHERE (selectedname.name=persons.name) 
    6 ) 
    7  OR 
    8  EXISTS (
    9   SELECT * FROM(
10    SELECT * FROM persons WHERE EXISTS (
11     SELECT * FROM (
12      SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1 
13    ) AS selectedname WHERE (selectedname.name=persons.name) 
14   ) 
15  ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of 
16 ); 

Il me donne le résultat souhaité ci-dessus, mais s'il vous plaît noter qu'il est inefficace, car les lignes 3-5 et 11-13 sont les mêmes.

je les deux questions suivantes:

  1. Que peut-on faire pour éviter cette inefficacité?
  2. j'ai réellement besoin de faire la distinction entre les lignes qui venaient de la name condition (ici les lignes avec le nom = bob) et ceux qui sont venus de la condition fan_of (ici la ligne avec le nom = dave). Ce pourrait être fait dans le code d'application mais alors j'aurais besoin d'une autre requête de base de données avant de trouver le k+1 -th nom actif et ceci pourrait être lent (s'il vous plaît corrigez-moi si c'est la meilleure solution). Je préférerais une colonne supplémentaire z qui me permet de distinguer comme

    +----+------+--------+---+ 
    | id | name | fan_of | z | 
    +----+------+--------+---+ 
    | 2 | bob |  4 | 1 | 
    | 4 | dave |  3 | 0 | 
    | 5 | bob |  2 | 1 | 
    +----+------+--------+---+ 
    

    Comment une telle sortie est atteint?

+0

Sur la base de votre exemple, devrait: "Pour un décalage k donné, je veux SÉLECTIONNER les personnes (rangées de personnes) ayant comme nom le k + 1-ème nom actif ou qui ont l'une de ces personnes comme leurs fans" Ne signifie pas réellement "Je veux sélectionner chaque personne de" personne "qui a le k + 1-ème nom actif, ou chaque personne qui est fan d'une autre personne avec ce nom? C'est la seule façon dont votre description a du sens pour moi –

+0

@ChrisJ Non, je veux dire comme il est dit, mais j'avoue que cela n'a pas de sens comme un exemple du monde réel. Ce que je veux, c'est que toutes les personnes avec ce nom et tout ce qui est référé par la colonne 'fan_of'. – phinz

+0

Donc, 'Dave' est récupéré parce que Bob est un fan de lui, et Bob est le nième nom actif (sélectionné)? –

Répondre

1

Il me semble que je peux obtenir le minimum que vous voulez atteindre en utilisant des paramètres (devrait-il être une option).

Ce n'est pas joli, mais je ne peux pas voir un moyen simple de réaliser ce que vous demandez, donc c'est ce que j'ai jusqu'à présent .... (set @offset pour répondre à 'k')

SET @offset = 1; 
SET @name = (SELECT name FROM (select name, @rank := @rank +1 as Rank from names n, (SELECT @rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + @offset)); 
select 
a.* 
From persons a 
where (a.name = @name) OR (a.id IN (SELECT fan_of from persons where name = @name)); 

Si vous n'avez toujours pas de réponse au moment où j'ai mangé, je vais regarder la partie 2.

(je l'espère, je l'ai lu votre mémoire correctement)

post-scriptum J'ai gardé le @name SQL dans une seule ligne car il semble mieux lire dans ce contexte.

Editer: Voici un indicateur de source assez salissant mais fonctionnel, en utilisant votre exemple. Z = 1 est l'endroit où la ligne est de la name, '0' est de fan_of

SET @offset = 1; 
SET @name = (SELECT name FROM (select name, @rank := @rank +1 as Rank from names n, (SELECT @rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + @offset)); 
select 
a.*,'1' as z 
From persons a 
where (a.name = @name) 
union 
select 
a.*,'0' as z 
From persons a 
where (a.id IN (SELECT fan_of from persons where name = @name)); 

ID Distinct Requête:

SET @offset = 1; 
SET @name = (SELECT name FROM (select name, @rank := @rank +1 as Rank from names n, (SELECT @rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + @offset)); 
SELECT id, name, fan_of, z FROM 
(select 
distinct a.id, 
a.name, 
a.fan_of, 
1 as z 
From persons a 
where (a.name = @name) 
union 
select 
distinct a.id, 
a.name, 
a.fan_of, 
0 as z 
From persons a 
where (a.id IN (SELECT fan_of from persons where name = @name)) 
ORDER BY z desc) qry 
GROUP BY id; 

Ce produit:

+----+------+--------+---+ 
| id | name | fan_of | z | 
+----+------+--------+---+ 
| 2 | bob | 4 | 1 | 
| 5 | bob | 2 | 1 | 
| 4 | dave | 3 | 0 | 
+----+------+--------+---+ 
+0

Merci beaucoup, je vais essayer demain et cocher cette réponse utile pour l'instant, dans mon fuseau horaire, son retard et je ne suis qu'un débutant MySQL (qui n'utilisait pas de paramètres auparavant) ... – phinz

+0

Pas de problème - I J'ai ajouté une option pour réaliser la partie 2. Pas la plus élaborée, mais fonctionnelle. –

+0

Ça marche !! Mais est-il possible d'éviter la double sélection de la ligne avec 'id' = 2 et' name' = bob? Il est listé deux fois une fois avec 'z' = 1 et une autre fois avec' z' = 0. Je veux seulement la ligne avec 'z' = 1. – phinz