Entfernen doppelter Zeilen mit zwei oder mehr linken Verknüpfungstabellen

7

Mit einer solchen Abfrage

SELECT a.id, a.name,
       COALESCE( json_agg(b.*), '[]'::json ),
       COALESCE( json_agg(c.*), '[]'::json ),
  FROM a
  LEFT JOIN b ON a.id = b.a_id
  LEFT JOIN c ON a.id = c.a_id
 GROUP BY a.id, a.name;

Bei der Ausführung werden beide cund bmiteinander multipliziert und es werden doppelte Einträge im JSON-Array-Objekt erzeugt.

Ich habe versucht, die Abfrage so zu ändern, dass stattdessen 2 Unterabfragen verwendet werden, erhalte jedoch alle möglichen Fehler und Warnungen, z. B. "Unterabfrage darf nur eine Spalte zurückgeben" usw.

Ich habe es auch versucht LEFT OUTER JOIN, aber ich glaube, ich beherrsche noch nicht, wie das Verknüpfen von Tabellen funktioniert, da es nur für Duplikate gilt bund cimmer noch multipliziert wird und Duplikate enthält.

Bearbeiten : Verwenden von DISTINCTFehlern mit "Konnte keinen Gleichheitsoperator für Typ json identifizieren" für die COALESCEFunktionen.

Wie kann ich diese Abfrage beheben und nur eindeutige Zeilen aggregieren?


Bearbeiten 2

Ich muss angeben, dass beide Tabellen bund ctatsächlich VIEWs sind und beide mindestens eine json_aggSpalte haben, sodass ich sie nicht einfach verwenden kann json_agg(DISTINCT b.*). Das wäre viel zu einfach gewesen.


Bearbeiten 3

Hier ist ein kleiner Ausschnitt, um das Problem zu reproduzieren:

--DROP TABLE IF EXISTS tbl_a CASCADE;
--DROP TABLE IF EXISTS tbl_b CASCADE;
--DROP TABLE IF EXISTS tbl_c CASCADE;

CREATE TABLE tbl_a (
  id bigserial NOT NULL,
  name character varying(16),
  CONSTRAINT "PK_tbl_a" PRIMARY KEY (id)
) WITH ( OIDS=FALSE );

CREATE TABLE tbl_b (
  a_id bigint NOT NULL,
  foo json NOT NULL DEFAULT '{}'::json,
  CONSTRAINT "FK_tbl_b_a" FOREIGN KEY (a_id)
      REFERENCES tbl_a (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
) WITH ( OIDS=FALSE );

CREATE TABLE tbl_c (
  a_id bigint NOT NULL,
  bar json NOT NULL DEFAULT '{}'::json,
  CONSTRAINT "FK_tbl_c_a" FOREIGN KEY (a_id)
      REFERENCES tbl_a (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
) WITH ( OIDS=FALSE );

INSERT INTO tbl_a (id,name) VALUES (1, 'Test');
INSERT INTO tbl_b (a_id, foo) VALUES (1, '{"foo":"Hello"}'::json), (1, '{"foo":"World"}'::json);
INSERT INTO tbl_c (a_id, bar) VALUES (1, '{"bar":"abc"}'::json), (1, '{"bar":"def"}'::json);

SELECT tbl_a.id, tbl_a.name,
       COALESCE(json_agg(tbl_b.*), '{}'::json),
       COALESCE(json_agg(tbl_c.*), '{}'::json)
  FROM tbl_a
  LEFT JOIN tbl_b ON tbl_a.id = tbl_b.a_id
  LEFT JOIN tbl_c ON tbl_a.id = tbl_c.a_id
 GROUP BY tbl_a.id, tbl_a.name;

Kehrt zurück

id  name    coalesce                              coalesce
--  ------  ------------------------------------  ----------------------
 1  "Test"  "[{"a_id":1,"foo":{"foo":"World"}},   "[{"a_id":1,"bar":{"bar":"abc"}},
             {"a_id":1,"foo":{"foo":"Hello"}},    {"a_id":1,"bar":{"bar":"abc"}}, 
             {"a_id":1,"foo":{"foo":"World"}},    {"a_id":1,"bar":{"bar":"def"}}, 
             {"a_id":1,"foo":{"foo":"Hello"}}]"   {"a_id":1,"bar":{"bar":"def"}}]"
Yanick Rochon
quelle

Antworten:

5

Ich habe eine Lösung gefunden. Ich bin nicht sicher, ob es optimal ist, aber es funktioniert.

SELECT tbl_a.id, tbl_a.name,
       COALESCE( ( SELECT json_agg(tbl_b.*)
                     FROM tbl_b 
                    WHERE tbl_b.a_id = tbl_a.id ), '{}'::json),
       COALESCE( ( SELECT json_agg(tbl_c.*)
                     FROM tbl_c 
                    WHERE tbl_c.a_id = tbl_a.id ), '{}'::json)
  FROM tbl_a;

Was richtig zurückkehrt

id  name    coalesce                             coalesce
--  ------  -----------------------------------  ----------------------
 1  "Test"  "[{"a_id":1,"foo":{"foo":"World"}},  "[{"a_id":1,"bar":{"bar":"abc"}},
              {"a_id":1,"foo":{"foo":"Hello"}},    {"a_id":1,"bar":{"bar":"def"}}]"
Yanick Rochon
quelle
4

Eine andere Option ist die Verwendung abgeleiteter Tabellen:

SELECT
  a.id,
  a.name,
  COALESCE(b.json_data, '{}'::json) AS b_json,
  COALESCE(c.json_data, '{}'::json) AS c_json
FROM
  tbl_a AS a
  LEFT JOIN
    (
      SELECT
        a_id,
        json_agg(*) AS json_data
      FROM
        tbl_b
      GROUP BY
        a_id
    ) AS b ON b.a_id = a.id
  LEFT JOIN
    (
      SELECT
        a_id,
        json_agg(*) AS json_data
      FROM
        tbl_c
      GROUP BY
        a_id
    ) AS c ON c.a_id = a.id
;

In diesem Fall tbl_bund tbl_cwerden aggregiert, bevor sie verbunden werden tbl_a. Aufgrund der Gruppierung nach enthält a_idkeine der abgeleiteten Tabellen a_idzum Zeitpunkt des Beitritts doppelte Einträge. Dies verhindert, dass die Joins mini-kartesische Produkte produzieren - der Effekt, den Sie bei Ihrer ursprünglichen Abfrage hatten.

Andriy M.
quelle
Beide laufen ungefähr zur gleichen Zeit, aber es ist ein schöner Trick! Prost!
Yanick Rochon