Wie führe ich eine Postgresql-Unterabfrage in einer select-Klausel mit einer join-from-Klausel wie SQL Server durch?

81

Ich versuche die folgende Abfrage auf postgresql zu schreiben:

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

Dies würde sicherlich unter Microsoft SQL Server funktionieren, aber unter postegresql überhaupt nicht. Ich habe die Dokumentation ein wenig gelesen und es scheint, als könnte ich sie wie folgt umschreiben:

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

Dies gibt jedoch den folgenden Fehler in postegresql zurück: "Unterabfrage in FROM kann nicht auf andere Beziehungen derselben Abfrageebene verweisen". Also stecke ich fest. Weiß jemand, wie ich das erreichen kann?

Vielen Dank

Ricardo
quelle
Eigentlich scheint es so, als ob dies auf Postgres funktionieren sollte (vielleicht vor 6 Jahren nicht :))
qwertzguy

Antworten:

121

Ich bin mir nicht sicher, ob ich Ihre Absicht perfekt verstehe, aber vielleicht entspricht Folgendes Ihren Wünschen:

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)

Leider wird dadurch die Anforderung hinzugefügt, die erste Unterabfrage nach ID sowie nach Name und author_id zu gruppieren, was meiner Meinung nach nicht erwünscht war. Ich bin mir jedoch nicht sicher, wie ich das umgehen soll, da Sie eine ID zur Verfügung haben müssen, um an der zweiten Unterabfrage teilnehmen zu können. Vielleicht findet jemand anderes eine bessere Lösung.

Teile und genieße.

Bob Jarvis - Monica wieder einsetzen
quelle
Perfekter Bob, das hat wirklich funktioniert. Danke vielmals! Ich musste eine kleine Änderung vornehmen, da ich den Join mit der ID nicht benötige, nur die author_id. Die letzte Abfrage lautet also: Wählen Sie n1.name, n1.author_id, count_1, total_count aus (wählen Sie id, name, author_id, count (1) als count_1 aus Namen aus, die nach ID gruppiert sind, name, author_id) n1 inner join (wählen Sie author_id, count (1) als total_count aus der Namensgruppe nach author_id) n2 on (n2.author_id = n1.author_id) Nun, da ich dies habe, möchte ich count_1 wirklich durch total_count teilen, um eine normalisierte Häufigkeit zu erhalten. = D
Ricardo
ops, habe gerade festgestellt, dass die SQL hier nicht richtig formatiert wird. :( Wird eine Antwort geben, um zu ergänzen.
Ricardo
Ich hatte nicht das Problem, über das Ricado sprach, aber diese SQL hat meine Probleme vollständig behoben ...: D DANKE !!!
Tftd
15

Als Ergänzung zu der Antwort von @Bob Jarvis und @dmikam führt Postgres keinen guten Plan durch, wenn Sie LATERAL unterhalb einer Simulation nicht verwenden. In beiden Fällen sind die Ergebnisse der Abfragedaten gleich, aber die Kosten sind sehr unterschiedlich

Tabellenstruktur

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

Durchführen JOINmit GROUP BYin Unterabfrage ohneLATERAL

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

Die Ergebnisse

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)

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

Ergebnisse

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)

Meine Postgres-Version ist PostgreSQL 10.3 (Debian 10.3-1.pgdg90+1)

deFreitas
quelle
3
Vielen Dank für den Hinweis zur Verwendung von LATERAL!
Leole
13

Ich antworte hier nur mit der formatierten Version des endgültigen SQL, die ich brauchte, basierend auf der Antwort von Bob Jarvis, wie in meinem Kommentar oben angegeben:

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

Ich weiß, dass dies alt ist, aber seit Postgresql 9.3 gibt es eine Option, ein Schlüsselwort "LATERAL" zu verwenden, um RELATED-Unterabfragen innerhalb von JOINS zu verwenden, sodass die Abfrage aus der Frage folgendermaßen aussehen würde:

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
dmikam
quelle
1
Ich frage mich, ob die Leistung dieser beiden Abfragen unterschiedlich ist oder ob es für postgresql der gleiche Plan ist
deFreitas
1
Ich habe diesen Test gemacht und die Antwort ist hier (meine Antwort)
deFreitas
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

distinctWird verwendet, wenn mehr innere Verknüpfungen vorhanden sind, da die Leistung der Verknüpfungsgruppen langsamer ist

Zahid Gani
quelle