SQL-Beispiele aus dem Miro-Board (04 SQL-Vertiefung, Fortsetzung)

Join: Vertiefung

-- Beispiel: Welche Waren liefert Maier aus Königsbrunn?
SELECT DISTINCT
     --h.h_id, h_name, h_ortschaft,  
       w.w_id, w_typ, w_bezeichnung
FROM        haendler h
       JOIN liefert l ON h.h_id = l.h_id
       JOIN ware w    ON l.w_id = w.w_id
WHERE      h.h_name      = 'Maier'
       AND h.h_ortschaft = 'Königsbrunn'
;

Komplexer Mehrfachjoin

-- Beispiel: Welche Waren liefert Maier aus Königsbrunn?
SELECT DISTINCT
     --h.h_id, h_name, h_ortschaft,  
       w.w_id, w_typ, w_bezeichnung
FROM        haendler h
       JOIN liefert l ON h.h_id = l.h_id
       JOIN ware w    ON l.w_id = w.w_id
WHERE      h.h_name      = 'Maier'
       AND h.h_ortschaft = 'Königsbrunn'
;


-- Wer liefert welche Waren billiger als Maier aus Königsbrunn?
-- Die Antwort auf diese Frage wird schrittweise erarbeitet.

-- Welche Waren liefert Maier aus Königsbrunn zu welchem Preis?
SELECT h.h_id, h_name, h_ortschaft,     l.l_preis,
       w.w_id, w_typ, w_bezeichnung
FROM        haendler h
       JOIN liefert l ON h.h_id = l.h_id
       JOIN ware w    ON l.w_id = w.w_id
WHERE      h.h_name      = 'Maier'
       AND h.h_ortschaft = 'Königsbrunn'
;


-- Welche Waren liefert die Anderen zu welchem Preis?
SELECT
       h.h_id, h_name, h_ortschaft,     l.l_preis,
       w.w_id, w_typ, w_bezeichnung
FROM        haendler h
       JOIN liefert l ON h.h_id = l.h_id
       JOIN ware w    ON l.w_id = w.w_id
WHERE  NOT (    h.h_name      = 'Maier'
            AND h.h_ortschaft = 'Königsbrunn'
           )
;

-- Die Anderen liefern dieselben Ware wie M/K billiger (zu einem günstigeren Preis).


-- Wer liefert welche Waren billiger als Maier aus Königsbrunn?
SELECT ha.h_id, ha.h_name, ha.h_ortschaft,
       la.l_preis AS preis_anderer,
       lm.l_preis AS preis,
       wm.w_id, wm.w_typ, wm.w_bezeichnung,
       wa.w_id, wa.w_typ, wa.w_bezeichnung
FROM        haendler hm
       JOIN liefert lm  ON hm.h_id = lm.h_id
       JOIN ware wm     ON lm.w_id = wm.w_id
       ,  -- SQL-89-Join über beide großen Tabellen
            haendler ha
       JOIN liefert la  ON ha.h_id = la.h_id
       JOIN ware wa     ON la.w_id = wa.w_id
WHERE      hm.h_name      = 'Maier'
       AND hm.h_ortschaft = 'Königsbrunn'
       AND NOT (    ha.h_name      = 'Maier'
                AND ha.h_ortschaft = 'Königsbrunn'
               )
       AND wm.w_id = wa.w_id          -- dieselbe Ware
       AND la.l_preis <= lm.l_preis;  -- billiger als Maier aus Königsbrunn
                   -- <   -- falls "echt billliger" gemeint ist
;

Komplexer Mehrfachjoin: Optimierungen

-- Wer liefert welche Waren billiger als Maier aus Königsbrunn?
-- Optimale Lösung: Ursprüngliche Lösung mit den hier verwendeten Vereinfachungen.
--                  Die Tabelle "ware" wird nur einmal verwendet,
--                  weil beide dieselben Waren liefern.

SELECT ha.h_id, ha.h_name, ha.h_ortschaft,
       la.l_preis AS preis_anderer,
       lm.l_preis AS preis,
       wm.w_id, wm.w_typ, wm.w_bezeichnung --,
     --wa.w_id, wa.w_typ, wa.w_bezeichnung -- Unnötige doppelte Ausgabe der Warenattributwerte
FROM        haendler hm
       JOIN liefert lm  ON hm.h_id = lm.h_id
       JOIN ware wm     ON lm.w_id = wm.w_id
       ,  -- SQ-89-Join über beide großen Tabellen
            haendler ha
       JOIN liefert la  ON ha.h_id = la.h_id
     --JOIN ware wa     ON la.w_id = wa.w_id -- Die Warenattributewerte sind auch in wa enthalten,
                                             -- da wa.w_id = wm.w_wid
WHERE      hm.h_name      = 'Maier'
       AND hm.h_ortschaft = 'Königsbrunn'
       AND NOT (    ha.h_name      = 'Maier'
                AND ha.h_ortschaft = 'Königsbrunn'
               )
       AND lm.w_id = la.w_id          -- Dieselbe Ware, wa.w_id existiert nicht mehr.
                                      -- Es gilt aber wm.w_id = lm.w_id = la.w_id = wa.w_id.
                                      -- Es kann also für den Join über die beiden
                                      -- großen Tabellen wa.w_id durch la.w_id ersetzt werden.
       AND la.l_preis <= lm.l_preis;  -- <  ist "echt billiger"
;

SELECT ha.h_id, ha.h_name, ha.h_ortschaft,
       la.l_preis AS preis_anderer,
       lm.l_preis AS preis,
       wm.w_id, wm.w_typ, wm.w_bezeichnung
FROM        haendler hm
       JOIN liefert lm  ON hm.h_id = lm.h_id
       JOIN ware wm     ON lm.w_id = wm.w_id
       ,  -- SQ-89-Join über beide großen Tabellen
            haendler ha
       JOIN liefert la  ON ha.h_id = la.h_id
WHERE      hm.h_name      = 'Maier'
       AND hm.h_ortschaft = 'Königsbrunn'
       AND lm.h_id <> la.h_id         -- Maier ist verschieden von den Anderen
       AND lm.w_id = la.w_id          -- dieselbe Ware
       AND la.l_preis <= lm.l_preis;  -- <  ist "echt billiger"
;

Großeltern

DROP TABLE IF EXISTS eltern CASCADE;

CREATE TABLE eltern
( kind  VARCHAR NOT NULL,
  elter VARCHAR NOT NULL,

  PRIMARY KEY (kind, elter)
);


DELETE FROM eltern;

INSERT INTO eltern(kind, elter)
VALUES
('Ja', 'Si'), ('Ol', 'Si'),  ('Li', 'So'), ('La', 'So'),
('Si', 'Ma'), ('So', 'Ma'),  ('Ve', 'Ma'),
('Si', 'Wo'), ('So', 'Wo'),  ('Ve', 'Wo'),
('Ma', 'Fr'), ('Ma', 'JaS'), 
('Fr', 'Th'),
('Wo','Il'), ('Wo','Ka'),
('Il','Ro'), ('Ka', 'Cä')
;


DELETE FROM eltern;

INSERT INTO eltern(kind, elter)
VALUES
('Ol', 'Si'), ('Li', 'So'),
('Si', 'Ma'), ('So', 'Ma'), 
('Ma', 'Fr'),
('Fr', 'Th')
;


-- Gibt zu jeder Person den Großelter aus.
-- Großeltern = Eltern der Eltern

SELECT e1.kind AS person, e2.elter AS großelter
FROM eltern e1 JOIN eltern e2 ON e1.elter = e2.kind
;

SELECT e1.kind AS person, e1.elter AS elter,
       e2.kind AS elter_als_kind, e2.elter AS großelter
FROM   eltern e1 JOIN eltern e2 ON e1.elter = e2.kind
;

Urgroßeltern

-- Urgroßeltern = Eltern der Großeltern
--              = Eltern der Eltern der Eltern
SELECT e1.kind AS person, e3.elter AS urgrosselter
FROM        eltern e1
       JOIN eltern e2 ON e1.elter = e2.kind
       JOIN eltern e3 ON e2.elter = e3.kind
;

Ahnen

SELECT e1.kind AS person, e1.elter AS vorfahre, 1 AS generation
FROM   eltern e1

UNION

SELECT e1.kind AS person, e2.elter AS vorfahre, 2 AS generation
FROM        eltern e1
       JOIN eltern e2 ON e1.elter = e2.kind

UNION

SELECT e1.kind AS person, e3.elter AS vorfahre, 3 AS generation
FROM        eltern e1
       JOIN eltern e2 ON e1.elter = e2.kind
       JOIN eltern e3 ON e2.elter = e3.kind

UNION

SELECT e1.kind AS person, e4.elter AS vorfahre, 4 AS generation
FROM        eltern e1
       JOIN eltern e2 ON e1.elter = e2.kind
       JOIN eltern e3 ON e2.elter = e3.kind
       JOIN eltern e4 ON e3.elter = e4.kind

ORDER BY person, generation
;

Rekursive Views (Vorfahren)

CREATE RECURSIVE VIEW vorfahren(person, vorfahre, generation)
AS
SELECT e.kind, e.elter, 1
FROM   eltern e
UNION
SELECT e.kind, v.vorfahre, v.generation+1
FROM   eltern e JOIN vorfahren v ON e.elter = v.person
;


SELECT   person, vorfahre, generation FROM vorfahren
ORDER BY person, generation
FETCH FIRST 1000 ROWS ONLY
;

Aggregation

SELECT COUNT(*) AS anzahl
FROM   ware
;

SELECT MIN(l_preis)                 AS min_preis,
       MAX(l_preis)                 AS max_preis,
       SUM(l_preis)                 AS sum_preis,
       AVG(l_preis)                 AS avg_preis,
       ROUND(AVG(l_preis), 0)       AS round_avg_preis,
       AVG(ROUND(l_preis, 0))       AS avg_round_preis,
       COUNT(*)                     AS count_star,
       COUNT(l_lieferzeit)          AS count_lieferzeit,
       COUNT(DISTINCT l_lieferzeit) AS count_distinct_lieferzeit,
       COUNT(*)-COUNT(l_lieferzeit) AS lieferzeiten_gleich_null
FROM   liefert
;

-- Wie viel unterschiedliche Waren sind lieferbar?

SELECT COUNT(*)
FROM   (SELECT DISTINCT w_id FROM liefert) AS dummy
;

SELECT COUNT(DISTINCT w_id)
FROM   liefert
;

Gruppierung der Tabelle „liefert”

SELECT ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis)
FROM   liefert l
WHERE  l.w_id = 1
;

SELECT ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis)
FROM liefert l
WHERE  l.w_id = 2
;

SELECT ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis)
FROM liefert l
WHERE  l.w_id = 12
;

SELECT   ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis),
         w_id
FROM     liefert
GROUP BY w_id
;

SELECT   ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis),
         w_typ, w_bezeichnung
FROM     liefert l JOIN ware w ON l.w_id = w.w_id
GROUP BY w_typ, w_bezeichnung
;

-- SQL 3: w_id -> w_typ, w_bezeichnung
-- Wenn w_id eindeutig, dann sind auch w_typ und w_bezeichnung eindeutig
-- GROUP BY w.w_id -- funktioniert auch
-- ;

SELECT   ROUND(AVG(l_preis),2) AS avg, MIN(l_preis), MAX(l_preis),
         w_typ, w_bezeichnung
FROM     liefert l JOIN ware w ON l.w_id = w.w_id
GROUP BY w_typ, w_bezeichnung
;

SELECT   l_preis, l_lieferzeit, w_typ, w_bezeichnung
FROM     liefert l JOIN ware w ON l.w_id = w.w_id
ORDER BY w_typ, w_bezeichnung
;

Beispiel

SELECT   ROUND(AVG(l_preis),2)      AS durchschnittspreis,
         ROUND(AVG(l_lieferzeit),1) AS durchschnittslieferzeit,
         w_typ, w_bezeichnung
FROM     liefert l JOIN ware w ON l.w_id = w.w_id
GROUP BY w_typ, w_bezeichnung
ORDER BY w_typ, w_bezeichnung
;

Array-Agg und JSON-Agg

SELECT   DISTINCT
         h_name, h_ortschaft, w_typ, w_bezeichnung
FROM     haendler h JOIN liefert l ON h.h_id = l.h_id
                    JOIN ware w ON l.w_id = w.w_id 
ORDER BY h_name, h_ortschaft
;


SELECT   h_name, h_ortschaft, 
         ARRAY_AGG(DISTINCT w_typ)         AS typen, 
         ARRAY_AGG(DISTINCT w_bezeichnung) AS bezeichnungen
FROM     haendler h JOIN liefert l ON h.h_id = l.h_id
                    JOIN ware w ON l.w_id = w.w_id 
GROUP BY h_name, h_ortschaft
ORDER BY h_name, h_ortschaft
;


SELECT   h_name, h_ortschaft, 
         JSON_AGG(DISTINCT w_typ)                       AS typen, 
         JSON_OBJECT_AGG(DISTINCT w_bezeichnung, w_typ) AS waren
FROM     haendler h JOIN liefert l ON h.h_id = l.h_id
                    JOIN ware w ON l.w_id = w.w_id 
GROUP BY h_name, h_ortschaft
ORDER BY h_name, h_ortschaft
;

Having-Klausel

/* Durchschnittspreis aller Waren mit einer w_id kleiner < 100,
   die billiger als 1000 Euro sind und für die mindestens zwei
   verschiedene Preise (< 1000 Euro) existieren.

   Achtung:
     l_preis < 1000 darf nur in der Where-Klausel stehen
     count (DISTINCT l_preis) >= 2 darf nur in der
     Having-Klausel stehen.
     w_id < 100 darf in beiden Klauseln stehen
     (es reicht, sie in eine zu schreiben)
*/

SELECT   w_id, ROUND(AVG(l_preis),2) AS durchschnittspreis
FROM     liefert
WHERE    l_preis < 1000 AND w_id < 100
GROUP BY w_id
HAVING   COUNT(DISTINCT l_preis) >= 2 AND w_id < 100
;

Windowing

SELECT   w_typ, w_bezeichnung,
         COUNT(*)           OVER (ORDER BY w_typ, w_bezeichnung) AS nr,
         ROW_NUMBER(*)      OVER () as nr2,
         COUNT(*)           OVER () AS anzahl,
         COUNT(*)           OVER (PARTITION BY w_typ) AS nr_typ,
         COUNT(*)           OVER (PARTITION BY w_typ ORDER BY w_typ,
                                  w_bezeichnung
                                 ) AS nr2_typ,
         ROW_NUMBER(*)      OVER (PARTITION BY w_typ) AS nr3_typ,
         COUNT(*)           OVER (ORDER BY w_typ) AS rank_false,
         RANK(*)            OVER (ORDER BY w_typ) AS rank,
         DENSE_RANK(*)      OVER (ORDER BY w_typ) AS dense_rank,
         MIN(w_bezeichnung) OVER (PARTITION BY w_typ) AS min,
         MAX(w_bezeichnung) OVER (PARTITION BY w_typ) AS max
FROM     ware
ORDER BY w_typ
;

SELECT   l_preis, l_lieferzeit, w_id,
         ROW_NUMBER(*)      OVER (PARTITION BY w_id)    AS row_nr,
         ROUND(AVG(l_preis) OVER (PARTITION BY w_id),2) AS avg_preis,
         ROUND(AVG(l_preis) OVER (PARTITION BY w_id
                                  ORDER BY l_lieferzeit
                                  RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING
                                 )
               , 2
              ) AS gleitender_durchschnitt
FROM     liefert
ORDER BY w_id
;