Fortgeschrittene PostgreSQL-Abfragen erstellen

PostgreSQLBeginner
Jetzt üben

Einführung

In diesem Lab werden Sie Ihre Fähigkeiten im Schreiben von PostgreSQL-Abfragen verbessern, indem Sie fortgeschrittene Techniken erkunden. Sie lernen, Unterabfragen innerhalb der WHERE-Klausel zu verwenden, um Daten basierend auf den Ergebnissen einer anderen Abfrage zu filtern.

Das Lab führt Sie durch die Definition und Verwendung von Common Table Expressions (CTEs) (Allgemeine Tabellenausdrücke) zur Verbesserung der Lesbarkeit und Modularität von Abfragen. Darüber hinaus wenden Sie Fensterfunktionen (window functions) wie ROW_NUMBER an, um Berechnungen über Datensätze von Zeilen hinweg durchzuführen. Schließlich beherrschen Sie das Gruppieren und Filtern von Daten mithilfe von GROUP BY- und HAVING-Klauseln, um aussagekräftige Erkenntnisse aus Ihren Datensätzen zu gewinnen.

Eine Subquery (Unterabfrage) in einer WHERE-Klausel schreiben

In diesem Schritt lernen Sie, wie Sie eine Unterabfrage (subquery) innerhalb der WHERE-Klausel einer SQL-Abfrage verwenden. Eine Unterabfrage, auch bekannt als innere Abfrage (inner query) oder verschachtelte Abfrage (nested query), ist eine Abfrage, die in eine andere Abfrage eingebettet ist. Unterabfragen werden verwendet, um Daten zurückzugeben, die in der Hauptabfrage als Bedingung verwendet werden, um die abzurufenden Daten weiter einzuschränken.

Grundlegendes zu Unterabfragen in der WHERE-Klausel

Eine Unterabfrage in der WHERE-Klausel wird typischerweise verwendet, um den Wert einer Spalte mit dem Ergebnis der Unterabfrage zu vergleichen. Die Unterabfrage wird zuerst ausgeführt, und ihr Ergebnis wird dann von der äußeren Abfrage verwendet.

Grundlegende Syntax:

SELECT column1, column2
FROM table_name
WHERE column_name IN (SELECT column_name FROM another_table WHERE condition);

Hier gibt die Unterabfrage (SELECT column_name FROM another_table WHERE condition) eine Menge von Werten zurück. Die äußere Abfrage wählt dann Zeilen aus table_name aus, bei denen column_name in dieser Menge enthalten ist.

Szenario:

Nehmen wir an, Sie haben zwei Tabellen: employees (Mitarbeiter) und departments (Abteilungen). Die Tabelle employees enthält Informationen über Mitarbeiter, einschließlich ihrer employee_id (Mitarbeiter-ID), employee_name (Mitarbeitername) und department_id (Abteilungs-ID). Die Tabelle departments enthält Informationen über Abteilungen, einschließlich ihrer department_id und department_name (Abteilungsname).

Wir möchten alle Mitarbeiter finden, die in der Abteilung 'Sales' (Vertrieb) arbeiten.

Schritt 1: Erstellen der Tabellen und Einfügen von Daten

Stellen Sie zunächst eine Verbindung zur PostgreSQL-Datenbank mit dem Benutzer postgres her:

sudo -u postgres psql

Erstellen Sie als Nächstes die Tabelle departments:

CREATE TABLE departments (
    department_id SERIAL PRIMARY KEY,
    department_name VARCHAR(50)
);

Fügen Sie einige Beispieldaten in die Tabelle departments ein:

INSERT INTO departments (department_name) VALUES
('Sales'),
('Marketing'),
('Engineering');

Erstellen Sie nun die Tabelle employees:

CREATE TABLE employees (
    employee_id SERIAL PRIMARY KEY,
    employee_name VARCHAR(50),
    department_id INTEGER REFERENCES departments(department_id)
);

Fügen Sie einige Beispieldaten in die Tabelle employees ein:

INSERT INTO employees (employee_name, department_id) VALUES
('Alice', 1),
('Bob', 2),
('Charlie', 1),
('David', 3),
('Eve', 2);

Schritt 2: Schreiben der Unterabfrage

Schreiben wir nun die Abfrage, um alle Mitarbeiter zu finden, die in der Abteilung 'Sales' arbeiten, indem wir eine Unterabfrage in der WHERE-Klausel verwenden.

SELECT employee_name
FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE department_name = 'Sales');

Erläuterung:

  • Die Unterabfrage (SELECT department_id FROM departments WHERE department_name = 'Sales') wählt die department_id aus der Tabelle departments aus, bei der der department_name 'Sales' ist. In diesem Fall wird 1 zurückgegeben.
  • Die äußere Abfrage wählt dann den employee_name aus der Tabelle employees aus, bei der die department_id in der von der Unterabfrage zurückgegebenen Menge enthalten ist (die nur 1 ist).

Schritt 3: Ausführen der Abfrage und Anzeigen der Ergebnisse

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 employee_name
---------------
 Alice
 Charlie
(2 rows)

Dies zeigt, dass Alice und Charlie die Mitarbeiter sind, die in der Abteilung 'Sales' arbeiten.

Schritt 4: Verwenden von EXISTS mit Unterabfragen

Eine andere Möglichkeit, Unterabfragen in der WHERE-Klausel zu verwenden, ist mit dem Operator EXISTS. Der Operator EXISTS testet auf das Vorhandensein von Zeilen in einer Unterabfrage. Er gibt true zurück, wenn die Unterabfrage Zeilen zurückgibt, andernfalls false.

Hier ist ein Beispiel für die Verwendung von EXISTS, um das gleiche Ergebnis zu erzielen:

SELECT employee_name
FROM employees e
WHERE EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_id = e.department_id
    AND d.department_name = 'Sales'
);

Diese Abfrage erzielt das gleiche Ergebnis wie die vorherige, verwendet jedoch den Operator EXISTS anstelle von IN.

Erläuterung:

  • Die Unterabfrage SELECT 1 FROM departments d WHERE d.department_id = e.department_id AND d.department_name = 'Sales' prüft, ob es eine Abteilung mit dem Namen 'Sales' gibt, die die gleiche department_id wie der aktuelle Mitarbeiter hat.
  • Wenn die Unterabfrage Zeilen zurückgibt (was bedeutet, dass es eine Abteilung 'Sales' mit der gleichen department_id gibt), gibt der Operator EXISTS true zurück, und der Name des Mitarbeiters wird ausgewählt.

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die gleiche Ausgabe wie zuvor sehen:

 employee_name
---------------
 Alice
 Charlie
(2 rows)

Sie haben nun erfolgreich eine Unterabfrage in der WHERE-Klausel verwendet, um Daten basierend auf einer Bedingung in einer anderen Tabelle zu filtern. Sie haben auch gelernt, wie Sie den Operator EXISTS mit einer Unterabfrage verwenden.

Eine CTE (Common Table Expression, Allgemeine Tabellenausdruck) definieren und verwenden

In diesem Schritt lernen Sie, wie Sie eine Common Table Expression (CTE) (Allgemeiner Tabellenausdruck) in PostgreSQL definieren und verwenden. Eine CTE ist ein temporäres, benanntes Resultset (Ergebnismenge), auf das Sie innerhalb einer einzelnen SELECT-, INSERT-, UPDATE- oder DELETE-Anweisung verweisen können. CTEs sind nützlich, um komplexe Abfragen in einfachere, besser lesbare Teile zu zerlegen.

Grundlegendes zu CTEs

CTEs werden mit der WITH-Klausel definiert. Sie existieren nur für die Dauer der Abfrageausführung.

Grundlegende Syntax:

WITH cte_name AS (
    SELECT column1, column2
    FROM table_name
    WHERE condition
)
SELECT column1, column2
FROM cte_name
WHERE condition;

Hier ist cte_name der Name, den Sie der CTE geben. Die SELECT-Anweisung innerhalb der Klammern definiert das Resultset der CTE. Die äußere SELECT-Anweisung fragt dann die CTE ab, als wäre sie eine reguläre Tabelle.

Szenario:

Fahren wir mit den Tabellen employees (Mitarbeiter) und departments (Abteilungen) aus dem vorherigen Schritt fort und verwenden wir eine CTE, um die Namen der Mitarbeiter und ihre entsprechenden Abteilungsnamen zu finden.

Schritt 1: Überprüfen der Tabellen und Daten

Stellen Sie sicher, dass die Tabellen employees und departments vorhanden sind und die Daten aus dem vorherigen Schritt enthalten. Sie können dies überprüfen, indem Sie die folgenden Abfragen in Ihrem psql-Terminal ausführen:

SELECT * FROM departments;
SELECT * FROM employees;

Wenn die Tabellen oder Daten fehlen, lesen Sie den vorherigen Schritt, um sie zu erstellen und die Daten einzufügen.

Schritt 2: Definieren einer CTE

Definieren wir nun eine CTE namens EmployeeDepartments, die die Tabellen employees und departments verknüpft, um die Mitarbeiternamen und ihre Abteilungsnamen abzurufen.

WITH EmployeeDepartments AS (
    SELECT
        e.employee_name,
        d.department_name
    FROM
        employees e
    JOIN
        departments d ON e.department_id = d.department_id
)
SELECT employee_name, department_name
FROM EmployeeDepartments;

Erläuterung:

  • Die Klausel WITH EmployeeDepartments AS (...) definiert die CTE namens EmployeeDepartments.
  • Die SELECT-Anweisung innerhalb der Klammern verknüpft die Tabelle employees (als e bezeichnet) mit der Tabelle departments (als d bezeichnet) in der Spalte department_id.
  • Die äußere SELECT-Anweisung ruft dann den employee_name und department_name aus der CTE EmployeeDepartments ab.

Schritt 3: Ausführen der Abfrage und Anzeigen der Ergebnisse

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 employee_name | department_name
---------------+-----------------
 Alice         | Sales
 Bob           | Marketing
 Charlie       | Sales
 David         | Engineering
 Eve           | Marketing
(5 rows)

Dies zeigt die Namen aller Mitarbeiter und ihre entsprechenden Abteilungsnamen.

Schritt 4: Verwenden von CTEs zum Filtern

Sie können CTEs auch verwenden, um Daten zu filtern. Suchen wir beispielsweise alle Mitarbeiter, die in der Abteilung 'Sales' arbeiten, mithilfe der CTE EmployeeDepartments.

WITH EmployeeDepartments AS (
    SELECT
        e.employee_name,
        d.department_name
    FROM
        employees e
    JOIN
        departments d ON e.department_id = d.department_id
)
SELECT employee_name
FROM EmployeeDepartments
WHERE department_name = 'Sales';

Erläuterung:

  • Diese Abfrage ähnelt der vorherigen, fügt jedoch eine WHERE-Klausel zur äußeren SELECT-Anweisung hinzu, um die Ergebnisse so zu filtern, dass nur Mitarbeiter berücksichtigt werden, die in der Abteilung 'Sales' arbeiten.

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 employee_name
---------------
 Alice
 Charlie
(2 rows)

Dies zeigt, dass Alice und Charlie die Mitarbeiter sind, die in der Abteilung 'Sales' arbeiten.

Sie haben nun erfolgreich eine CTE definiert und verwendet, um Tabellen zu verknüpfen und Daten zu filtern. CTEs können die Lesbarkeit und Wartbarkeit komplexer SQL-Abfragen erheblich verbessern.

Window Functions (Fensterfunktionen) anwenden (z.B. ROW_NUMBER)

In diesem Schritt lernen Sie, wie Sie Window Functions (Fensterfunktionen) in PostgreSQL anwenden. Window Functions führen Berechnungen über eine Menge von Tabellenzeilen durch, die mit der aktuellen Zeile in Beziehung stehen. Sie ähneln Aggregatfunktionen (aggregate functions), aber im Gegensatz zu Aggregatfunktionen gruppieren Window Functions Zeilen nicht zu einer einzigen Ausgabeziele. Stattdessen liefern sie einen Wert für jede Zeile im Resultset (Ergebnismenge).

Grundlegendes zu Window Functions

Window Functions verwenden die OVER()-Klausel, um das Fenster von Zeilen für die Berechnung zu definieren. Die OVER()-Klausel kann die Klauseln PARTITION BY und ORDER BY enthalten, um das Fenster weiter zu definieren.

Grundlegende Syntax:

SELECT
    column1,
    column2,
    WINDOW_FUNCTION(column3) OVER (
        [PARTITION BY column4]
        [ORDER BY column5]
    ) AS alias_name
FROM
    table_name;
  • WINDOW_FUNCTION: Der Name der Window Function (z.B. ROW_NUMBER, RANK, SUM, AVG).
  • OVER(): Gibt das Fenster an, über das die Funktion arbeitet.
  • PARTITION BY: Teilt die Zeilen in Partitionen auf, und die Window Function wird unabhängig auf jede Partition angewendet.
  • ORDER BY: Definiert die Reihenfolge der Zeilen innerhalb jeder Partition.
  • alias_name: Der Alias für das berechnete Ergebnis der Window Function.

Szenario:

Fahren wir mit den Tabellen employees (Mitarbeiter) und departments (Abteilungen) aus den vorherigen Schritten fort und verwenden wir die Window Function ROW_NUMBER(), um jedem Mitarbeiter innerhalb seiner jeweiligen Abteilung basierend auf seinem employee_name (Mitarbeitername) einen eindeutigen Rang zuzuweisen.

Schritt 1: Anwenden der ROW_NUMBER() Window Function

Schreiben wir nun eine Abfrage, die die Window Function ROW_NUMBER() verwendet, um jedem Mitarbeiter innerhalb seiner Abteilung einen Rang zuzuweisen.

SELECT
    employee_name,
    department_id,
    ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name) AS employee_rank
FROM
    employees;

Erläuterung:

  • ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name): Dies ist die Window Function.
    • ROW_NUMBER(): Weist jeder Zeile innerhalb des Fensters eine eindeutige fortlaufende ganze Zahl zu.
    • PARTITION BY department_id: Teilt die Zeilen basierend auf der department_id (Abteilungs-ID) in Partitionen auf. Dies bedeutet, dass die Rangfolge für jede Abteilung separat erfolgt.
    • ORDER BY employee_name: Gibt die Reihenfolge an, in der die Zeilen innerhalb jeder Partition eingestuft werden. In diesem Fall werden die Mitarbeiter alphabetisch nach ihrem employee_name eingestuft.
  • employee_rank: Dies ist der Alias, der dem Ergebnis der Window Function gegeben wird.

Schritt 2: Ausführen der Abfrage und Anzeigen der Ergebnisse

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 employee_name | department_id | employee_rank
---------------+---------------+---------------
 Alice         |             1 |             1
 Charlie       |             1 |             2
 Bob           |             2 |             1
 Eve           |             2 |             2
 David         |             3 |             1
(5 rows)

Dies zeigt den Namen jedes Mitarbeiters, seine Abteilungs-ID und seinen Rang innerhalb seiner Abteilung. Zum Beispiel ist Alice in Abteilung 1 auf Platz 1 und Charlie in Abteilung 1 auf Platz 2.

Schritt 3: Verwenden von Window Functions mit CTEs

Sie können Window Functions auch innerhalb von CTEs verwenden, um Ihre Abfragen übersichtlicher zu gestalten. Schreiben wir die vorherige Abfrage mit einer CTE um.

WITH RankedEmployees AS (
    SELECT
        employee_name,
        department_id,
        ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name) AS employee_rank
    FROM
        employees
)
SELECT
    employee_name,
    department_id,
    employee_rank
FROM
    RankedEmployees;

Diese Abfrage erzeugt das gleiche Ergebnis wie die vorherige, verwendet aber eine CTE, um die Berechnung der Window Function zu kapseln.

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die gleiche Ausgabe wie zuvor sehen.

Gruppieren und Filtern mit GROUP BY und HAVING

In diesem Schritt lernen Sie, wie Sie die Klauseln GROUP BY und HAVING in PostgreSQL verwenden, um Zeilen zu gruppieren und die gruppierten Ergebnisse zu filtern. Die Klausel GROUP BY gruppiert Zeilen, die in den angegebenen Spalten die gleichen Werte haben, zu zusammenfassenden Zeilen, z. B. um die Anzahl der Mitarbeiter in jeder Abteilung zu ermitteln. Die Klausel HAVING wird verwendet, um diese gruppierten Zeilen basierend auf einer angegebenen Bedingung zu filtern.

Grundlegendes zu GROUP BY und HAVING

  • GROUP BY: Diese Klausel gruppiert Zeilen mit den gleichen Werten in einer oder mehreren Spalten zu einer zusammenfassenden Zeile. In der Regel verwenden Sie Aggregatfunktionen (aggregate functions) (z. B. COUNT, SUM, AVG, MIN, MAX), um Werte für jede Gruppe zu berechnen.
  • HAVING: Diese Klausel filtert die von der Klausel GROUP BY erstellten Gruppen. Sie ähnelt der Klausel WHERE, arbeitet aber mit Gruppen anstelle von einzelnen Zeilen.

Grundlegende Syntax:

SELECT
    column1,
    aggregate_function(column2)
FROM
    table_name
WHERE
    condition
GROUP BY
    column1
HAVING
    aggregate_function(column2) condition;
  • column1: Die Spalte, nach der gruppiert werden soll.
  • aggregate_function(column2): Eine Aggregatfunktion, die auf column2 für jede Gruppe angewendet wird.
  • WHERE: Filtert Zeilen vor der Gruppierung.
  • GROUP BY: Gruppiert Zeilen basierend auf den Werten in column1.
  • HAVING: Filtert Gruppen nach der Gruppierung, basierend auf dem Ergebnis der Aggregatfunktion.

Szenario:

Fahren wir mit den Tabellen employees (Mitarbeiter) und departments (Abteilungen) fort und verwenden wir GROUP BY und HAVING, um die Abteilungen zu finden, die mehr als einen Mitarbeiter haben.

Schritt 1: Gruppieren nach Abteilung und Zählen der Mitarbeiter

Schreiben wir zunächst eine Abfrage, um die Mitarbeiter nach Abteilung zu gruppieren und die Anzahl der Mitarbeiter in jeder Abteilung zu zählen.

SELECT
    d.department_name,
    COUNT(e.employee_id) AS employee_count
FROM
    employees e
JOIN
    departments d ON e.department_id = d.department_id
GROUP BY
    d.department_name;

Erläuterung:

  • SELECT d.department_name, COUNT(e.employee_id) AS employee_count: Dies wählt den Abteilungsnamen und die Anzahl der Mitarbeiter-IDs für jede Abteilung aus.
  • FROM employees e JOIN departments d ON e.department_id = d.department_id: Dies verknüpft die Tabellen employees und departments über die Spalte department_id.
  • GROUP BY d.department_name: Dies gruppiert die Zeilen nach Abteilungsnamen, sodass die Funktion COUNT() die Anzahl der Mitarbeiter in jeder Abteilung zählt.

Schritt 2: Ausführen der Abfrage und Anzeigen der Ergebnisse

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 department_name | employee_count
-----------------+----------------
 Engineering     |              1
 Marketing       |              2
 Sales           |              2
(3 rows)

Dies zeigt die Anzahl der Mitarbeiter in jeder Abteilung.

Schritt 3: Filtern mit HAVING

Fügen wir nun eine HAVING-Klausel hinzu, um die Ergebnisse so zu filtern, dass nur Abteilungen berücksichtigt werden, die mehr als einen Mitarbeiter haben.

SELECT
    d.department_name,
    COUNT(e.employee_id) AS employee_count
FROM
    employees e
JOIN
    departments d ON e.department_id = d.department_id
GROUP BY
    d.department_name
HAVING
    COUNT(e.employee_id) > 1;

Erläuterung:

  • Diese Abfrage ist die gleiche wie die vorherige, fügt aber eine HAVING-Klausel hinzu:
    • HAVING COUNT(e.employee_id) > 1: Dies filtert die Gruppen so, dass nur Abteilungen berücksichtigt werden, in denen die Anzahl der Mitarbeiter-IDs größer als 1 ist.

Schritt 4: Ausführen der Abfrage und Anzeigen der Ergebnisse

Führen Sie die Abfrage in Ihrem psql-Terminal aus. Sie sollten die folgende Ausgabe sehen:

 department_name | employee_count
-----------------+----------------
 Marketing       |              2
 Sales           |              2
(2 rows)

Dies zeigt nur die Abteilungen, die mehr als einen Mitarbeiter haben (Marketing und Sales).

Sie haben nun erfolgreich die Klauseln GROUP BY und HAVING verwendet, um Zeilen zu gruppieren und die gruppierten Ergebnisse basierend auf einer Bedingung zu filtern.

Zusammenfassung

In diesem Lab haben Sie gelernt, wie man fortgeschrittene PostgreSQL-Abfragen schreibt. Sie begannen mit der Verwendung von Subqueries (Unterabfragen) innerhalb der WHERE-Klausel, um Daten basierend auf den Ergebnissen einer anderen Abfrage zu filtern. Dies umfasste das Verständnis der Syntax und Anwendung von Subqueries, um Spaltenwerte mit einer Menge von Werten zu vergleichen, die von der inneren Abfrage zurückgegeben werden.

Anschließend gingen Sie dazu über, Common Table Expressions (CTEs) (Allgemeine Tabellenausdrücke) zu definieren und zu verwenden, um die Lesbarkeit und Modularität der Abfrage zu verbessern. CTEs ermöglichen es Ihnen, komplexe Abfragen in einfachere, besser handhabbare Teile zu zerlegen.

Als Nächstes haben Sie Window Functions (Fensterfunktionen) wie ROW_NUMBER angewendet, um Berechnungen über Mengen von Zeilen durchzuführen. Window Functions ähneln Aggregatfunktionen (aggregate functions), liefern aber einen Wert für jede Zeile im Resultset (Ergebnismenge), anstatt Zeilen zu einer einzigen Ausgabeziele zu gruppieren.

Schließlich haben Sie das Gruppieren und Filtern von Daten mit den Klauseln GROUP BY und HAVING gemeistert, um aussagekräftige Erkenntnisse aus Ihren Datensätzen zu gewinnen. Die Klausel GROUP BY gruppiert Zeilen, die in den angegebenen Spalten die gleichen Werte haben, zu zusammenfassenden Zeilen, während die Klausel HAVING diese gruppierten Zeilen basierend auf einer angegebenen Bedingung filtert.