2010-05-11 2 views
2

Supposons que j'ai deux tables distinctes que je regarde pour interroger. Ces deux tables ont une relation avec une troisième table. Comment puis-je interroger les deux tables avec une requête unique, non basée sur UNION?Comment puis-je effectuer cette requête entre des tables liées sans utiliser UNION?

Voici un exemple théorique. J'ai une table d'utilisateur. Cet utilisateur peut avoir à la fois des CD et des livres. Je veux trouver tous les livres et les CD de cet utilisateur avec une seule requête correspondant à une chaîne ("génial" dans cet exemple).

Une UNION requête basée pourrait ressembler à ceci:

SELECT "book" AS model, name, ranking 
FROM book 
WHERE name LIKE 'Awesome%' 
UNION 
SELECT "cd" AS model, name, ranking 
    FROM cd 
WHERE name LIKE 'Awesome%' 
ORDER BY ranking DESC 

Comment puis-je effectuer une requête comme celui-ci sans l'Union? Si je fais une simple jointure à gauche de l'utilisateur vers des livres et des CD, nous obtenons un nombre total de résultats égal au nombre de cds correspondant au nombre de livres correspondants. Existe-t-il un GROUP BY ou un autre moyen d'écrire la requête pour résoudre ce problème?

(EDIT: La raison pour laquelle je voudrais éviter l'approche de l'Union est parce que c'est une requête DQL et que Doctrine ne supporte pas UNION.Si ce n'est pas possible sans UNION, je vais utiliser la route SQL native En outre, la requête réelle contient un tas de colonnes supplémentaires qui ne se correspondent pas dans l'exemple ci-dessus.)

+7

Qu'est-ce qui ne va pas avec l'approche UNION? –

+0

et que voulez-vous dire en comparant un champ sur chaque table – Mark

+1

Je suis d'accord avec KM. Si vous considérez les tables comme un ensemble de livres et un ensemble de CD (comme vous le devriez), il devrait être assez simple de voir qu'une union des deux ensembles (dans votre exemple) est plus appropriée. –

Répondre

3

Si vous tentez d'éviter l'union, vous devez créer une vue.

EDIT: Pour create view vous avez deux options

Si votre requête est uniquement pour la sélection d'enregistrements il devrait y avoir aucun problème avec

CREATE VIEW media AS 
SELECT "book" AS model, name, ranking 
FROM book 
WHERE name LIKE 'Awesome%' 
UNION 
SELECT "cd" AS model, name, ranking 
    FROM cd 
WHERE name LIKE 'Awesome%' 
ORDER BY ranking DESC 

Si vous avez besoin de vue qui peut être mis à jour alors il pourrait voler si vous refactoring:

  • créer la table qui contiendra tous les types de données et les médias
  • c réer deux vues qui se partageront les données sur le type de support (étant donné que ces vues sont simples 1: 1 requêtes aux tables underlaying ils devraient être actualisable et vous devriez être en mesure de les utiliser dans la cartographie ORM ou d'autres requêtes SQL)

EDIT2 : J'ai oublié de commenter le fait que UNION ALL est un must sur UNION sauf si vous voulez que MySQL commence à construire l'index sur le disque chaque fois que vous lancez la vue/requête (merci HLGEM).

+0

la vue aura le syndicat –

+0

@KM, cela va-t-il déranger Doctrine? – Unreason

+0

Droit! Vous créez ce point de vue: CREATE VIEW V_BOOK_CD AS SELECT "livre" AS modèle, le nom, le classement LIVRE DE L'UNION SELECT "cd" en tant que modèle, nom, classement à partir du CD; ... puis vous sélectionnez à partir de la vue. – UltraCommit

0

À moins qu'il n'y ait une raison impérieuse de ne pas utiliser UNION, utilisez simplement UNION.

0

Peut-être que vous pourriez essayer quelque chose comme ça ... l'exemple suppose que la troisième table est appelée User:

$q = Doctrine_Query::create() 
    ->select('c.cd, b.book') 
    ->from('User u') 
    ->LeftJoin('Cd c ON u.user_id = c.user_id AND c.name LIKE ?, 'Awesome%') 
    ->LeftJoin('Book b ON u.user_id = b.user_id AND b.name LIKE ?, 'Awesome%'); 
$result = $q->execute(); 
+0

C'est où je suis en ce moment. Le problème est que dans les coulisses, cette requête est vraiment lente lorsque nous avons beaucoup de livres et de CD correspondants, car le nombre réel d'éléments renvoyés par la base de données est (nombre de CD correspondants) * (nombre de livres correspondants). –

+0

Je suppose que le problème est avec le LIKE .... avez-vous un index sur le champ "name" dans les deux tables CD et Book? Alternativement, repenser l'approche dans le sens de la suggestion de Bill Karwin pourrait aussi accélérer un peu les choses. – Tom

5

Pensez comment vous modélisez cela dans une application OO. Vous créez une super-classe que vous étendez pour les livres et les CD, et votre utilisateur possède alors un ensemble de Collectibles. Tout objet donné dans cet ensemble est soit un livre ou un CD (ou un autre type de collection) mais il a exactement l'un de ces sous-types.

Vous pouvez faire quelque chose de similaire avec SQL, en créant une table correspondant au supertype:

CREATE TABLE Collectibles (
    collectible_id SERIAL PRIMARY KEY, 
    user_id  INT NOT NULL, 
    FOREIGN KEY (user_id) REFERENCES Users(user_id) 
); 

Ensuite, chaque sous-type contient une référence pour le rendre de collection:

CREATE TABLE Books (
    book_id BIGINT UNSIGNED PRIMARY KEY 
    book_name VARCHAR(100) NOT NULL, 
    FOREIGN KEY (book_id) REFERENCES Collectibles(collectible_id) 
); 

CREATE TABLE CDs (
    cd_id BIGINT UNSIGNED PRIMARY KEY 
    cd_name VARCHAR(100) NOT NULL, 
    FOREIGN KEY (cd_id) REFERENCES Collectibles(collectible_id) 
); 

Maintenant, vous peut faire votre requête et être assuré que vous n'obtiendrez pas un produit cartésien:

SELECT u.*, COALESCE(b.book_name, d.cd_name) AS media_name 
FROM Users u 
JOIN Collectibles c ON (u.user_id = c.user_id) 
LEFT OUTER JOIN Books b ON (b.book_id = c.collectible_id) 
LEFT OUTER JOIN CDs d ON (d.cd_id = c.collectible_id); 
+0

ERREUR 1146 (42S02): La table 'schema.Users' n'existe pas :) – Unreason

+0

C'est probablement la bonne solution :) Il faudrait juste un refactoring massif. –

+1

L'autre option consiste à contourner DQL et à utiliser SQL, vous pouvez donc utiliser UNION.Lorsque vous utilisez un ORM, vous devez vous préparer au fait que la couche d'abstraction ne gérera pas tous les scénarios. –

Questions connexes