views:

1973

answers:

5

I ultimately need a list of "import" records that include "album" records which only have one "song" each.

This is what I'm using now:

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)
);

The nested select (with the join) is blazing fast, but the external "in" clause is excruciatingly slow.

I tried to make the entire query a single (no nesting) join but ran into problems with the group/having clauses. The best I could do was a list of "import" records with dupes, which is not acceptable.

Is there a more elegant way to compose this query?

+2  A: 

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)

OR

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)
gbn
+6  A: 

How's this?

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;

In most database systems, the JOIN works a lost faster than doing a WHERE ... IN.

achinda99
This was close enough. I had to add "group by i.id, i.created_at" in order to achieve the "no dupes" requirement (see original post). Thanks.
Teflon Ted
yeah, i missed that. no problem.
achinda99
+3  A: 
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

(You might not need to include the COUNT in the SELECT list itself. SQL Server doesn't require it, but it's possible that a different RDBMS might.)

LukeH
+1  A: 

All three sugested techniques should be faster than your WHERE IN:

  1. Exists with a related subquery (gbn)
  2. Subquery that is inner joined (achinda99)
  3. Inner Joining all three tables (luke)

(All should work, too ..., so +1 for all of them. Please let us know if one of them does not work!)

Which one actually turns out to be the fastest, depends on your data and the execution plan. But an interesting example of different ways for expressing the same thing in SQL.

IronGoofy
+1  A: 

I tried to make the entire query a single (no nesting) join but ran into problems with the group/having clauses.

You can join subquery using CTE (Common Table Expression) if you are using SQL Server version 2005/2008

As far as I know, CTE is simply an expression that works like a virtual view that works only one a single select statement - So you will be able to do the following. I usually find using CTE to improve query performance as well.

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
Sung Meister