Сложное объединение с вложенным предложением group-by / using? - PullRequest
1 голос
/ 23 февраля 2009

Мне в конечном итоге нужен список «импортируемых» записей, которые включают «альбом» записи, в которых есть только одна «песня».

Это то, что я сейчас использую:

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

Вложенный выбор (с объединением) вспыхивает быстро, но внешний Предложение "in" мучительно медленно.

Я попытался сделать весь запрос одним (без вложений) соединением, но выполнил в проблемы с группой / имеющие пункты. Лучшее, что я мог сделать, это список «импортных» записей с дубликатами, что недопустимо.

Есть ли более элегантный способ составить этот запрос?

Ответы [ 5 ]

8 голосов
/ 23 февраля 2009

Как это?

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; 

В большинстве систем баз данных JOIN работает быстрее, чем WHERE ... IN.

4 голосов
/ 23 февраля 2009
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

(Возможно, вам не нужно включать COUNT в сам список SELECT. SQL Server не требует этого, но возможно, что другая СУБД может.)

2 голосов
/ 23 февраля 2009

Непроверенные:

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)
1 голос
/ 24 февраля 2009

Я пытался сделать весь запрос одиночное (без вложенности) соединение, но натолкнулось на проблемы с группой / имеющие статьи.

Вы можете присоединиться к подзапросу, используя CTE (Common Table Expression), если вы используете SQL Server версии 2005/2008

Насколько я знаю, CTE - это просто выражение, которое работает как виртуальное представление, которое работает только с одним оператором select - поэтому вы сможете сделать следующее. Я обычно нахожу использование CTE для улучшения производительности запросов.

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
1 голос
/ 24 февраля 2009

Все три метода sugested должны быть быстрее, чем ваш WHERE IN:

  1. Существует со связанным подзапросом (gbn)
  2. Подзапрос с внутренним соединением (achinda99)
  3. Внутреннее соединение всех трех столов (Люк)

(Все тоже должны работать ..., поэтому +1 для всех. Пожалуйста, дайте нам знать, если один из них не работает!)

Какой из них окажется самым быстрым, зависит от ваших данных и плана выполнения. Но интересный пример различных способов выражения одного и того же в SQL.

...