Як зробити підзапит Postgresql у пропозиції select із приєднанням із речення, як SQL Server?


83

Я намагаюся написати такий запит на postgresql:

select name, author_id, count(1), 
    (select count(1)
    from names as n2
    where n2.id = n1.id
        and t2.author_id = t1.author_id
    )               
from names as n1
group by name, author_id

Це, звичайно, буде працювати на Microsoft SQL Server, але зовсім не на postegresql. Я трохи прочитав її документацію, і, здається, я міг би переписати її як:

select name, author_id, count(1), total                     
from names as n1, (select count(1) as total
    from names as n2
    where n2.id = n1.id
        and n2.author_id = t1.author_id
    ) as total
group by name, author_id

Але це повертає таку помилку в postegresql: "підзапит у FROM не може посилатися на інші відносини того самого рівня запиту". Тож я застряг. Хтось знає, як я можу цього досягти?

Дякую


Насправді, здається, це повинно працювати на Postgres (можливо, 6 років тому цього не було :))
qwertzguy

Відповіді:


124

Я не впевнений, що прекрасно розумію твій намір, але, можливо, наступне буде близьким до того, що ти хочеш:

select n1.name, n1.author_id, count_1, total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select id, author_id, count(1) as total_count
              from names
              group by id, author_id) n2
  on (n2.id = n1.id and n2.author_id = n1.author_id)

На жаль, це додає вимогу групувати перший підзапит за ідентифікатором, а також за ім'ям та авторським ідентифікатором, що, на мою думку, не було бажаним. Я не впевнений, як це обійти, однак вам потрібно мати доступний ідентифікатор, щоб приєднатися до другого підзапиту. Можливо, хтось інший придумає краще рішення.

Поділіться та насолоджуйтесь.


Ідеальний Боб, це справді спрацювало. Дуже дякую! Мені довелося зробити невелику зміну, тому що мені не потрібне об’єднання з ідентифікатором, а лише author_id. Отже, остаточний запит: виберіть n1.name, n1.author_id, count_1, total_count from (select id, name, author_id, count (1) as count_1 from group names by id, name, author_id) n1 внутрішнє приєднання (select author_id, count (1) as total_count from group names by author_id) n2 on (n2.author_id = n1.author_id) Тепер, коли я це маю, я дійсно хочу поділити count_1 на total_count, щоб мати нормалізовану частоту. = D
Рікардо

ops, щойно зрозумів, що sql тут не відформатований належним чином. :( Дасть відповідь на доповнення.
Рікардо

У мене не було проблеми, про яку говорив Рікадо, але цей SQL повністю виправив мої проблеми ...: D ДЯКУЮ !!!
tftd

16

Доповнюючи відповідь @Bob Jarvis та @dmikam , Postgres не виконує хороший план, коли ви не використовуєте LATERAL, нижче моделювання, в обох випадках результати даних запитів однакові, але вартість дуже відрізняється

Структура таблиці

CREATE TABLE ITEMS (
    N INTEGER NOT NULL,
    S TEXT NOT NULL
);

INSERT INTO ITEMS
  SELECT
    (random()*1000000)::integer AS n,
    md5(random()::text) AS s
  FROM
    generate_series(1,1000000);

CREATE INDEX N_INDEX ON ITEMS(N);

Виконання JOINз GROUP BYу підзапиті безLATERAL

EXPLAIN 
SELECT 
    I.*
FROM ITEMS I
INNER JOIN (
    SELECT 
        COUNT(1), n
    FROM ITEMS
    GROUP BY N
) I2 ON I2.N = I.N
WHERE I.N IN (243477, 997947);

Результати

Merge Join  (cost=0.87..637500.40 rows=23 width=37)
  Merge Cond: (i.n = items.n)
  ->  Index Scan using n_index on items i  (cost=0.43..101.28 rows=23 width=37)
        Index Cond: (n = ANY ('{243477,997947}'::integer[]))
  ->  GroupAggregate  (cost=0.43..626631.11 rows=861418 width=12)
        Group Key: items.n
        ->  Index Only Scan using n_index on items  (cost=0.43..593016.93 rows=10000000 width=4)

Використовуючи LATERAL

EXPLAIN 
SELECT 
    I.*
FROM ITEMS I
INNER JOIN LATERAL (
    SELECT 
        COUNT(1), n
    FROM ITEMS
    WHERE N = I.N
    GROUP BY N
) I2 ON 1=1 --I2.N = I.N
WHERE I.N IN (243477, 997947);

Результати

Nested Loop  (cost=9.49..1319.97 rows=276 width=37)
  ->  Bitmap Heap Scan on items i  (cost=9.06..100.20 rows=23 width=37)
        Recheck Cond: (n = ANY ('{243477,997947}'::integer[]))
        ->  Bitmap Index Scan on n_index  (cost=0.00..9.05 rows=23 width=0)
              Index Cond: (n = ANY ('{243477,997947}'::integer[]))
  ->  GroupAggregate  (cost=0.43..52.79 rows=12 width=12)
        Group Key: items.n
        ->  Index Only Scan using n_index on items  (cost=0.43..52.64 rows=12 width=4)
              Index Cond: (n = i.n)

Моя версія Postgres є PostgreSQL 10.3 (Debian 10.3-1.pgdg90+1)


3
Дякуємо за підказку щодо використання LATERAL !!
leole

14

Я знаю, що це старе, але, оскільки Postgresql 9.3, існує можливість використовувати ключове слово "LATERAL" для використання ЗВ'ЯЗАНИХ підзапитів всередині JOINS, тому запит із запитання буде виглядати так:

SELECT 
    name, author_id, count(*), t.total
FROM
    names as n1
    INNER JOIN LATERAL (
        SELECT 
            count(*) as total
        FROM 
            names as n2
        WHERE 
            n2.id = n1.id
            AND n2.author_id = n1.author_id
    ) as t ON 1=1
GROUP BY 
    n1.name, n1.author_id

1
Цікаво, чи є ефективність цих двох запитів різницею, чи для postgresql це однаковий план
deFreitas,

1
Я провів цей тест, і відповідь тут (моя відповідь)
deFreitas

13

Я просто відповідаю тут із відформатованою версією остаточного sql, який мені потрібен, на основі відповіді Боба Джарвіса, як написано в моєму коментарі вище:

select n1.name, n1.author_id, cast(count_1 as numeric)/total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select author_id, count(1) as total_count
              from names
              group by author_id) n2
  on (n2.author_id = n1.author_id)

2
select n1.name, n1.author_id, cast(count_1 as numeric)/total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select distinct(author_id), count(1) as total_count
              from names) n2
  on (n2.author_id = n1.author_id)
Where true

використовується, distinctякщо більше внутрішнього приєднання, оскільки більше приєднання до групи є повільним

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.