2009-02-23 11 views
1

J'ai finalement besoin d'une liste de disques "import" qui incluent "album" enregistrements qui ont seulement une "chanson" chacun.Jointure complexe avec une clause group-by/having imbriquée?

C'est ce que j'utilise maintenant:

select i.id, i.created_at 
from imports i 
where i.id in (
    select a.import_id 
    from albums a inner join songs s on a.id = s.album_id 
    group by a.id having 1 = count(s.id) 
); 

La sélection imbriquée (avec la jointure) est ultra-rapide, mais le externe « dans » l'article est atrocement lent.

J'ai essayé de faire de l'ensemble de la requête une seule jointure (pas d'imbrication) mais j'ai exécuté en problèmes avec les clauses group/having. Le mieux que je pouvais faire était une liste d'enregistrements "import" avec dupes, ce qui n'est pas acceptable.

Existe-t-il une manière plus élégante de composer cette requête?

+0

Souhaitez-vous spécifier RDBMS? – Sung

Répondre

7

Comment agit-il?

SELECT i.id, 
     i.created_at 
FROM imports i 
     INNER JOIN (SELECT a.import_id 
        FROM  albums a 
          INNER JOIN songs s 
           ON a.id = s.album_id 
        GROUP BY a.id 
        HAVING Count(*) = 1) AS TEMP 
     ON i.id = TEMP.import_id; 

Dans la plupart des systèmes de base de données, JOIN fonctionne une perte plus rapide que de faire une WHERE ... IN.

+0

C'était assez proche. J'ai dû ajouter "group by i.id, i.created_at" afin d'atteindre l'exigence "no dupes" (voir article original). Merci. –

+0

ouais, j'ai raté ça. aucun problème. – achinda99

2

Untested:

select 
    i.id, i.created_at 
from 
    imports i 
where 
    exists (select * 
     from 
      albums a 
      join 
      songs s on a.id = s.album_id 
     where 
      a.import_id = i.id 
     group by 
      a.id 
     having 
      count(*) = 1) 

OU

select 
    i.id, i.created_at 
from 
    imports i 
where 
    exists (select * 
     from 
      albums a 
      join 
      songs s on a.id = s.album_id 
     group by 
      a.import_id, a.id 
     having 
      count(*) = 1 AND a.import_id = i.id) 
4
SELECT i.id, i.created_at, COUNT(s.album_id) 
FROM imports AS i 
    INNER JOIN albums AS a 
     ON i.id = a.import_id 
    INNER JOIN songs AS s 
     ON a.id = s.album_id 
GROUP BY i.id, i.created_at 
HAVING COUNT(s.album_id) = 1 

(Vous ne pourriez pas besoin d'inclure le COUNT dans la liste SELECT lui-même. SQL Server ne nécessite pas, mais il est possible qu'une puissance SGBDR différente.)

1

Les trois techniques sugested devrait être plus rapide que votre OÙ:

  1. coexiste avec une sous-requête connexe (GBN)
  2. qui est sous-requête interne rejoint (achinda99)
  3. intérieur Rejoindre tous les trois tables (luke)

(Tout le monde devrait travailler aussi ..., donc +1 pour chacun d'eux. S'il vous plaît laissez-nous savoir si l'un d'entre eux ne fonctionne pas!)

Lequel s'avère en réalité le plus rapide, dépend de vos données et le plan d'exécution. Mais un exemple intéressant de différentes façons d'exprimer la même chose en SQL.

1

J'ai essayé de faire toute la requête d'un unique (pas de nidification) se joignent, mais a couru dans problèmes avec le groupe/ayant clauses.

Vous pouvez vous joindre à la sous-requête en utilisant CTE (Common Table Expression) si vous utilisez la version SQL Server 2005/2008

Pour autant que je sache, CTE est tout simplement une expression qui fonctionne comme une vue virtuelle qui fonctionne un seul un seul sélectionnez instruction - Ainsi, vous serez en mesure de faire ce qui suit. En général, j'utilise le CTE pour améliorer les performances des requêtes.

with AlbumSongs as (
    select a.import_id 
    from albums a inner join songs s on a.id = s.album_id 
    group by a.id 
    having 1 = count(s.id) 
) 
select i.id, i.created_at 
from imports i 
     inner join AlbumSongs A on A.import_id = i.import_id 
Questions connexes