web-dev-qa-db-fra.com

Comment faire une sous-requête Postgresql dans une clause select avec une clause join in from comme SQL Server?

J'essaie d'écrire la requête suivante sur 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

Cela fonctionnerait certainement sur Microsoft SQL Server, mais pas du tout sur postegresql. J'ai lu un peu sa documentation et il semble que je pourrais la réécrire comme suit:

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

Mais cela renvoie l'erreur suivante sur postegresql: "la sous-requête dans FROM ne peut pas faire référence à d'autres relations du même niveau de requête". Donc je suis coincé. Est-ce que quelqu'un sait comment je peux y arriver?

Merci

69
Ricardo

Je ne suis pas sûr de comprendre parfaitement votre intention, mais peut-être que ce qui suit serait proche de ce que vous voulez:

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)

Malheureusement, ceci ajoute la nécessité de regrouper la première sous-requête par id, ainsi que name et author_id, ce que je ne pensais pas être voulu. Je ne sais pas trop comment contourner ce problème, car vous devez disposer d'un identifiant pour pouvoir participer à la deuxième sous-requête. Peut-être que quelqu'un d'autre trouvera une meilleure solution.

Partager et profiter.

99
Bob Jarvis

Je réponds simplement ici avec la version formatée de la version finale dont j'avais besoin, basée sur la réponse de Bob Jarvis, telle que publiée dans mon commentaire ci-dessus:

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)
12
Ricardo

Complémentant @ Bob Jarvis et @ dmikam answer, Postgres n'effectue pas un bon plan lorsque vous n'utilisez pas LATERAL, sous une simulation, dans les deux cas, les résultats des données de la requête sont les mêmes, mais les coûts sont très différents

Structure de la table

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

Exécution de JOIN avec GROUP BY Dans une sous-requête sans 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);

Les resultats

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)

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

Résultats

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)

Ma version de Postgres est PostgreSQL 10.3 (Debian 10.3-1.pgdg90+1)

7
deFreitas

Je sais que cela est ancien, mais puisque Postgresql 9. , il existe une option permettant d’utiliser un mot clé "LATERAL" pour utiliser les sous-requêtes RELATED dans JOINS. La requête de la question se présentait ainsi:

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
6
dmikam
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

utilisé distinct si plus de jointure interne, parce que plus la performance du groupe de jointure est lente

0
Zahid Gani