Pour les pros du SQL (prb avec récursivité)

Pour les pros du SQL (prb avec récursivité) - SQL/NoSQL - Programmation

Marsh Posté le 10-08-2010 à 15:01:32    

Bonjour, je cherche à effectuer une requête SQL mais je ne trouve pas comment.
 
Voici l'idée.
 
J'ai une table `news` avec les news
news_id | news
 
J'ai une table `flux` avec les flux d'information
flux_id | flux | company_id
 
J'ai une table `company` avec les sociétés (mais chaque société peut appartenir à une autre société/groupe, donc la base est récursive)
company_id | company | company_group_id
 
J'ai une table `news_in_flux` de correspondance entre les news et le flux  
flux_id | news_id
sachant que chaque news peut appartenir à plusieurs flux
 
Je souhaiterais faire une requête qui me permette de récupérer toutes les news dans une liste de flux données, mais vu qu'il peut y avoir des doublons car une même news peut être dans plusieurs flux, je ne veux récupérer que la news du flux de la société la plus haute dans la hiérarchie. (vous comprenez?)
 
Du coup, pour le moment, je fais un "select distinct news_id from flux, where flux IN (ma_liste_de_flux)" ce qui me retourne les news sans doublon, mais je ne sais plus de quel flux il provient.
 
Toute idée est la bienvenue.

Reply

Marsh Posté le 10-08-2010 à 15:01:32   

Reply

Marsh Posté le 10-08-2010 à 15:34:32    

SELECT news_id, flux_id FROM news_in_flux WHERE flux_id IN (ma_liste_de flux) AND EXISTS (SELECT * FROM flux JOIN company ON flux.company_id = company.company_id WHERE flux.flux_id = news_in_flux.flux_id AND  company.company_group_id IS NULL)

 

Mais je tappe un peu au pif


Message édité par alien conspiracy le 10-08-2010 à 15:35:13
Reply

Marsh Posté le 10-08-2010 à 15:41:13    

merci alien, je vais essayer ça, je comprends pas toute la requête mais intuitivement, j'ai l'impression que ça ne peut pas marcher s'il y a plusieurs niveaux de récursivité dans les sociétés (une société appartient à une société qui appartient à une autre société...), je me trompe?

Reply

Marsh Posté le 10-08-2010 à 16:06:08    

Je fais la supposition erronée que la company à la racine possède le flux en question. Je pense que tout problème est insolvable avec une simple requête, tu as besoin de procédure stockée.

Reply

Marsh Posté le 10-08-2010 à 16:11:12    

Ok, c'est ce que je me disais aussi.
 
J'avais pensé à l'éventualité d'enregistrer la hiérarchie complète dans une colonne et ensuite de comparer cette arbo pour savoir quel était la société le plus haut dans la hiérarchie mais sinon je voyais pas comment le faire.
 
Apparemment, postgresql gère les requêtes récursives, c'était ma 2e solution.

Reply

Marsh Posté le 11-08-2010 à 09:20:20    

En ajoutant 2 colonnes ya moyen de faire ce que tu veux en une seule query.
 
Dans la table company il faut ajouter une colonne company_level et company_source (meme data type que company_id).
Tu mets company_level = 0 where company_group_id is NULL, sinon quand tu rajoutes un company tu mets company_level = parentcompany.company_level + 1.
Tu mets company_source = company_id where company_group_id is NULL, pour les autres tu mets company_source = parentcompany.company_source.
 
En gros, le company_level sert a trouver la company la plus "haute" dans la hierarchie, le company_source permet de faire un group by et separer les differents groupes de company.
 
En SQL Server pour remplire company_source et company_level la toute premiere fois tu peux faire ca:

Code :
  1. WITH cte (company_id, company, company_group_id, company_level, company_source)
  2. AS
  3. (
  4.     SELECT company_id, company, company_group_id, 0 company_level, company_id company_source
  5.     FROM company
  6.     WHERE company_group_id IS NULL
  7.     UNION ALL
  8.     SELECT b.company_id, b.company, b.company_group_id, a.company_level + 1, a.company_source
  9.     FROM cte a
  10.         JOIN company b ON a.company_id = b.company_group_id
  11. )
  12. UPDATE company
  13.     SET company_level = b.company_level,
  14.     company_source = b.company_source
  15. FROM company a
  16.     JOIN cte b ON a.company_id = b.company_id


 
Ensuite quand tu ajoutes une company il te suffit de connaite le company_level et company_source du parent (si il y en a un) et de l'inserer directement.
Si tu modifies la hierarchie d'une company tu remets le tout a jour en refesant tourner la query d'initialisation.
 
Pour trouver la liste de news unique et leur flux tu peux faire ca:

Code :
  1. SELECT a.company, b.flux, d.news
  2. FROM company a
  3.     JOIN flux b ON a.company_id = b.company_id
  4.     JOIN news_in_flux c ON b.flux_id = c.flux_id
  5.     JOIN news d ON c.news_id = d.news_id
  6.     JOIN (
  7.     SELECT c.news_id, d.company_source, MIN(d.company_level) company_level
  8.     FROM flux a
  9.         JOIN news_in_flux b ON a.flux_id = b.flux_id
  10.         JOIN news c ON b.news_id = c.news_id
  11.         JOIN company d ON d.company_id = a.company_id
  12.     WHERE a.flux_id IN (1,2,3,5,9,10,11,13,15)
  13.     GROUP BY c.news_id, c.news, d.company_source
  14.         ) e ON e.news_id = d.news_id AND e.company_level = a.company_level AND e.company_source = a.company_source


 
Tu peux toujours avoir des doublons si des company qui ont la meme _source et le meme _level ont les meme news, dans ce cas la tu peux faire ca (change MIN en autre chose si tu ne veux pas la premiere company par order alphabetique):

Code :
  1. SELECT MIN(a.company), b.flux, d.news
  2. FROM company a
  3.     JOIN flux b ON a.company_id = b.company_id
  4.     JOIN news_in_flux c ON b.flux_id = c.flux_id
  5.     JOIN news d ON c.news_id = d.news_id
  6.     JOIN (
  7.     SELECT c.news_id, d.company_source, MIN(d.company_level) company_level
  8.     FROM flux a
  9.         JOIN news_in_flux b ON a.flux_id = b.flux_id
  10.         JOIN news c ON b.news_id = c.news_id
  11.         JOIN company d ON d.company_id = a.company_id
  12.     WHERE a.flux_id IN (1,2,3,5,9,10,11,13,15)
  13.     GROUP BY c.news_id, c.news, d.company_source
  14.         ) e ON e.news_id = d.news_id AND e.company_level = a.company_level AND e.company_source = a.company_source
  15. GROUP BY b.flux, d.news


 
En general ce n'est pas super de sauver les niveau de hierarchie comme ca, ca peu devenir lourd a gerer en cas de mise a jour frequente, mais dans ton cas je suppose que tu vas passer plus de temps a ajouter des company plutot que de les deplacer dans la hierarchie.
 
Ce n'est jamais facil d'expliquer des queries hierarchique, donc si ca peu t'aider voici la totalité du code que j'ai utilisé pour ton probleme (les noms et hierarchie des company ne sont pas correcte, mais c'est le matin et j'ai pas plus d'idée que ca :) ):

Code :
  1. CREATE TABLE news (
  2.     news_id int identity(1,1) PRIMARY KEY,
  3.     news varchar(max) NOT NULL
  4. )
  5.  
  6. CREATE TABLE flux (
  7.     flux_id int identity(1,1) PRIMARY KEY,
  8.     flux varchar(max) NOT NULL,
  9.     company_id int NOT NULL
  10. )
  11.  
  12. --drop table company
  13. CREATE TABLE company (
  14.     company_id int identity(1,1) PRIMARY KEY,
  15.     company varchar(max) NOT NULL,
  16.     company_group_id int NULL,
  17.     company_level int NULL,
  18.     company_source int NULL
  19. )
  20.  
  21. CREATE TABLE news_in_flux (
  22.     flux_id int NOT NULL,
  23.     news_id int NOT NULL
  24. )
  25.  
  26. INSERT news (news) VALUES ('My first news...'), ('Second news'), ('Third news')
  27. INSERT news (news) VALUES ('News 4'), ('News 5'), ('News 6'), ('News 7'), ('News 8'), ('News 9')
  28. SELECT * FROM news
  29.  
  30. INSERT company (company,company_group_id) VALUES ('Apple', NULL),
  31.     ('Microsoft',NULL),
  32.     ('Google',NULL),
  33.     ('iPhone Corp.',1),
  34.     ('iPad Corp.',1),
  35.     ('SilverLight',2),
  36.     ('Youtube',3),
  37.     ('Ebay', 3),
  38.     ('PayPal',8)
  39. SELECT * FROM company
  40.  
  41. INSERT flux (flux,company_id) VALUES ('Apple1',1),
  42.     ('Apple3',1),
  43.     ('Msft2',2),
  44.     ('Msft4',2),
  45.     ('Google7',3),
  46.     ('iPhone6',4),
  47.     ('iPhone5',4),
  48.     ('Silv8',6),
  49.     ('Silv2',6),
  50.     ('You7',7),
  51.     ('You2',7),
  52.     ('Ebay3',8),
  53.     ('Ebay7',8),
  54.     ('Paypal1',9),
  55.     ('Paypal7',9)
  56. SELECT * FROM flux
  57.  
  58. INSERT news_in_flux (flux_id,news_id)
  59. VALUES (1,1),
  60.        (2,3),
  61.        (3,2),
  62.        (4,4),
  63.        (5,7),
  64.        (6,6),
  65.        (7,5),
  66.        (8,8),
  67.        (9,2),
  68.        (10,7),
  69.        (11,2),
  70.        (12,3),
  71.        (13,7),
  72.        (14,1),
  73.        (15,7)
  74. SELECT * FROM news_in_flux
  75. ORDER BY news_id
  76.  
  77. SELECT *
  78. FROM flux a
  79.     JOIN news_in_flux b ON a.flux_id = b.flux_id
  80.     JOIN news c ON b.news_id = c.news_id
  81. WHERE a.flux_id IN (1,2,3,5,9,10,11,13,15)
  82.  
  83. SELECT * FROM company
  84. UPDATE company SET company_level = NULL
  85.  
  86. WITH cte (company_id, company, company_group_id, company_level, company_source)
  87. AS
  88. (
  89.     SELECT company_id, company, company_group_id, 0 company_level, company_id company_source
  90.     FROM company
  91.     WHERE company_group_id IS NULL
  92.     UNION ALL
  93.     SELECT b.company_id, b.company, b.company_group_id, a.company_level + 1, a.company_source
  94.     FROM cte a
  95.         JOIN company b ON a.company_id = b.company_group_id
  96. )
  97. UPDATE company
  98.     SET company_level = b.company_level,
  99.     company_source = b.company_source
  100. FROM company a
  101.     JOIN cte b ON a.company_id = b.company_id
  102.     
  103. SELECT *
  104. FROM flux a
  105.     JOIN news_in_flux b ON a.flux_id = b.flux_id
  106.     JOIN news c ON b.news_id = c.news_id
  107.     JOIN company d ON d.company_id = a.company_id
  108. WHERE a.flux_id IN (1,2,3,5,9,10,11,13,15)
  109.  
  110. SELECT MIN(a.company), b.flux, d.news
  111. FROM company a
  112.     JOIN flux b ON a.company_id = b.company_id
  113.     JOIN news_in_flux c ON b.flux_id = c.flux_id
  114.     JOIN news d ON c.news_id = d.news_id
  115.     JOIN (
  116.     SELECT c.news_id, d.company_source, MIN(d.company_level) company_level
  117.     FROM flux a
  118.         JOIN news_in_flux b ON a.flux_id = b.flux_id
  119.         JOIN news c ON b.news_id = c.news_id
  120.         JOIN company d ON d.company_id = a.company_id
  121.     WHERE a.flux_id IN (1,2,3,5,9,10,11,13,15)
  122.     GROUP BY c.news_id, c.news, d.company_source
  123.         ) e ON e.news_id = d.news_id AND e.company_level = a.company_level AND e.company_source = a.company_source
  124. GROUP BY b.flux, d.news


Message cité 1 fois
Message édité par Oliiii le 11-08-2010 à 09:26:58
Reply

Marsh Posté le 11-08-2010 à 09:34:39    

Merci Oliiii pour cette longue réponse, j'imagine que tu as dû y passer pas mal de temps. Je vais regarder ça plus en détail et essayer de comprendre les requêtes. Je suis sur la requête en ce moment même et je pense avoir trouvé une solution qui permette de le faire sans recourir à des colonnes supplémentaires justement (ce qui m'embêtait un peu en terme de maintenance). Mais avoir une colonne company_level ou company_source peut toujours être utile. Je vais privilégier la solution la plus optimisée sachant que la majorité des requêtes vont être effectivement des SELECT pour récupérer les news tandis que les mises à jour de sociétés ou les changements dans la hiérarchie seront très limités.
 
Je reviens vers toi

Reply

Marsh Posté le 11-08-2010 à 12:48:17    

'Tention je balance juste l'idée comme ca, mais y aurait pas moyen de faire quelque chose de similaire sans avoir à créer les deux colonnes mais en les générant sur le coup dans une sous-requete, en utilisant une clause CONNECT BY?
Un truc du genre:

SELECT level AS company_level, CONNECT_BY_ROOT company_group_id AS company_source
START WITH company_group_id IS NULL
CONNECT BY PRIOR company_id = company_group_id;


Edit: ha oui, Oracle only je suppose par contre...


Message édité par lasnoufle le 11-08-2010 à 12:48:38

---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 11-08-2010 à 13:35:44    

Il ya moyen de faire l'equivalent en SQL Server, le probleme est que les performances vont se degrader tres tres vite.
Les colonnes supplementaire sont la pour eviter de recalculer la meme chose a chaque Select (en partant du principe qu'on va faire beaucoup plus de Select/Insert que d'update).

Reply

Marsh Posté le 11-08-2010 à 14:47:10    

Agreed.


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 11-08-2010 à 14:47:10   

Reply

Marsh Posté le 11-08-2010 à 19:28:37    

Oliiii a écrit :

Pleins de choses


J'y ai pensé mais j'avais trop la fleme de l'expliquer. Chapeau d'avoir eu le courage.  :D


Message édité par alien conspiracy le 11-08-2010 à 19:29:57
Reply

Marsh Posté le 17-08-2010 à 23:52:27    

J'ai finalement utiliser ta solution Oli qui me parait la meilleure.
 
Merci beaucoup pour ton aide !

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed