Получение одной строки из JOIN с дополнительным условием - PullRequest
1 голос
/ 25 апреля 2020

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

SELECT b.id_band,
    COUNT(DISTINCT a.id_album),
    COUNT(DISTINCT s.id_song),
    COUNT(DISTINCT m.id_musician),
    (SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER BY(birth)LIMIT 1)
FROM BAND b
    LEFT JOIN ALBUM a ON(b.id_band  = a.id_band)
    LEFT JOIN SONG  s ON(a.id_album = s.id_album)
    JOIN MEMBER m ON(b.id_band= m.id_band)
    JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)

  /*LEFT JOIN(SELECT name FROM MUSICIAN WHERE year_death IS NULL
              ORDER BY(birth) LIMIT 1) AS alive FROM mu*/ -- ??

WHERE b.year_formed = 1981
GROUP BY b.id_band;

Я хотел бы получить самый старый живой член от mu для каждой группы. Но я получаю самого старого музыканта в целом из отношения MUSICIAN.

Вот скриншот, показывающий вывод для моего текущего запроса:

screenshot of result

Ответы [ 5 ]

0 голосов
/ 26 апреля 2020

Я думаю, что это должно быть значительно быстрее (одновременно решая вашу проблему):

SELECT b.id_band, a.*, m.*
FROM   band b
LEFT   JOIN LATERAL (
   SELECT count(*) AS ct_albums, sum(ct_songs) AS ct_songs
   FROM  (
      SELECT id_album, count(*) AS ct_songs
      FROM   album a
      LEFT   JOIN song s USING (id_album)
      WHERE  a.id_band = b.id_band
      GROUP  BY 1
      ) ab
   ) a ON true
LEFT   JOIN LATERAL (
   SELECT count(*) OVER () AS ct_musicians
        , name AS senior_member  -- any other columns you need?
   FROM   member   m
   JOIN   musician mu USING (id_musician)
   WHERE  m.id_band  = b.id_band
   ORDER  BY year_death IS NOT NULL  -- sorts the living first
           , birth
           , name  -- as tiebreaker (my optional addition)
   LIMIT  1
   ) m ON true
WHERE  b.year_formed = 1981;

Получение старшего участника группы решается в LATERAL подзапрос m - без умножения стоимости для базового запроса. Это работает, потому что оконная функция count(*) OVER () вычисляется до применения ORDER BY и LIMIT. Поскольку у групп, естественно, всего несколько участников, это должен быть самый быстрый способ. См .:

Другая оптимизация для подсчета альбомов и песен основана на предположении, что один и тот же id_song никогда не будет включены в несколько альбомов одной группы. Иначе они подсчитываются несколько раз. (Легко исправить и не соотносить с задачей получения старшего участника группы.)

Смысл в том, чтобы устранить необходимость в DISTINCT на верхнем уровне после многократного умножения строк на N-стороне (мне нравится называть это "прокси кросс-соединение"). Это может привести к огромному количеству строк в производной таблице без необходимости.

Кроме того, гораздо удобнее извлечь дополнительный столбец (например, больше столбцов для старшего члена группы), чем с некоторыми другими стилями запросов.

0 голосов
/ 25 апреля 2020

Следующий запрос даст вам самый старый член каждой группы группы. Вы можете установить фильтр на year_formed = 1981, если вам нужно.

SELECT
    b.id_band,
    total_albums,
    total_songs,
    total_musicians
FROM
(
    SELECT b.id_band,
        COUNT(DISTINCT a.id_album) as total_albums,
        COUNT(DISTINCT s.id_song) as total_songs,
        COUNT(DISTINCT m.id_musician) as total_musicians,
        dense_rank() over (partition by b.id_band order by mu.year_death desc) as rnk
    FROM BAND b
        LEFT JOIN ALBUM a ON(b.id_band  = a.id_band)
        LEFT JOIN SONG  s ON(a.id_album = s.id_album)
        JOIN MEMBER m ON(b.id_band= m.id_band)
        JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
    WHERE mu.year_death is NULL
)

where rnk = 1
0 голосов
/ 25 апреля 2020

Вы можете ссылаться на таблицу, которая находится вне этого вложенного выбора, например,

SELECT b.id_band,
COUNT(DISTINCT a.id_album),
COUNT(DISTINCT s.id_song),
COUNT(DISTINCT m.id_musician),
(SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER BY(birth) AND 
MUSICIAN.id_BAND = b.id_band LIMIT 1)
FROM BAND b
LEFT JOIN ALBUM a ON(b.id_band  = a.id_band)
LEFT JOIN SONG  s ON(a.id_album = s.id_album)
JOIN MEMBER m ON(b.id_band= m.id_band)
JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)

/*LEFT JOIN(SELECT name FROM MUSICIAN WHERE year_death IS NULL ORDER 
BY(birth)LIMIT 1) AS alive FROM mu*/
WHERE b.year_formed= 1981       
GROUP BY b.id_band
0 голосов
/ 25 апреля 2020

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

SELECT b.id_band,
    COUNT(DISTINCT a.id_album),
    COUNT(DISTINCT s.id_song),
    COUNT(DISTINCT m.id_musician),
    oldest_living_members.*
FROM 
    band b
    LEFT JOIN album a ON(b.id_band  = a.id_band)
    LEFT JOIN song s ON(a.id_album = s.id_album)
    LEFT JOIN 
    (
      SELECT
         m.id_band
         mu.*,
         ROW_NUMBER() OVER(PARTITION BY m.id_band ORDER BY mu.birthdate ASC) rown
       FROM
         MEMBER m
         JOIN MUSICIAN mu ON(m.id_musician = mu.id_musician)
       WHERE year_death IS NULL
     ) oldest_living_members 
     ON 
         b.id_band = oldest_living_members.id_band AND
         oldest_living_members.rown = 1
WHERE b.year_formed= 1981       
GROUP BY b.id_band

Если вы запустите только подзапрос, вы увидите, как он работает = артисты присоединяются к участнику, чтобы получить идентификатор группы, и это формирует раздел. Rownumber начнет нумерацию с 1 в соответствии с порядком дат рождения (я не знал, какое у вас название колонки для дня рождения; вам придется его редактировать), поэтому самый старый человек (самый ранний день рождения) получает 1 .. Каждый раз, когда изменение идентификатора группы нумерация будет перезапущена с 1 с самым старым человеком в этой полосе. Затем, когда мы присоединяемся, мы просто выбираем 1с

0 голосов
/ 25 апреля 2020

Ну, я думаю, что вы можете следовать структуре, которая у вас есть, но вам нужно JOIN s в подзапросе.

SELECT b.id_band,
       COUNT(DISTINCT a.id_album),
       COUNT(DISTINCT s.id_song),
       COUNT(DISTINCT mem.id_musician),
       (SELECT m.name
        FROM MUSICIAN m JOIN
             MEMBER mem
             ON mem.id_musician = m.id_musician
        WHERE m.year_death IS NULL AND mem.id_band = b.id_band
        ORDER BY m.birth
        LIMIT 1
       ) as oldest_member
FROM BAND b LEFT JOIN
     ALBUM a
     ON b.id_band  = a.id_band LEFT JOIN
     SONG s
     ON a.id_album = s.id_album LEFT JOIN
     MEMBER mem
     ON mem.id_band = b.id_band
WHERE b.year_formed = 1981       
GROUP BY b.id_band
...