MySQL 8 obsługuje popularne wyrażenia tabelowe, zarówno nierekurencyjne, jak i rekurencyjne, CTE (Common Table Expression) to tymczasowy zestaw wyników, do którego można się odwoływać w ramach innej instrukcji SELECT, INSERT, UPDATE lub DELETE.
Nierekurencyjne CTE
Wspólne wyrażenie tabelowe (CTE) jest podobne do tabeli pochodnej, ale jego deklaracja jest umieszczana przed blokiem zapytania, a nie w klauzuli FROM. Używając CTE, podzapytanie jest oceniane tylko raz, Wspólne wyrażenia tabelowe umożliwiają użycie nazwanych tymczasowych zestawów wyników, Wspólne wyrażenia tabelowe są definiowane w instrukcji za pomocą operatora WITH.
Załóżmy, że chcesz poznać procentową zmianę w płatnościach każdego roku w stosunku do roku poprzedniego. Bez CTE musisz napisać dwa podzapytania i są one zasadniczo takie same. MySQL nie jest wystarczająco inteligentny, aby to wykryć i podzapytania są wykonywane dwukrotnie.
SELECT q1.years, q2.years AS next_year, q1.sum1, q2.sum1 AS next_sum, 100 * (q2.sum1 - q1.sum1) / q1.sum1 AS pct FROM (SELECT YEAR(paymentDate) AS years, SUM(amount) AS sum1 FROM payments GROUP BY years) AS q1, (SELECT YEAR(paymentDate) AS years, SUM(amount) AS sum1 FROM payments GROUP BY years) AS q2 WHERE q1.years = q2.years - 1; +-------+-----------+------------+------------+------------+ | years | next_year | sum1 | next_sum | pct | +-------+-----------+------------+------------+------------+ | 2003 | 2004 | 3250217.70 | 4313328.25 | 32.708903 | | 2004 | 2005 | 4313328.25 | 1290293.28 | -70.085901 | +-------+-----------+------------+------------+------------+ 2 rows in set (0.01 sec)
W przypadku nierekurencyjnego CTE wyprowadzone zapytanie jest wykonywane tylko raz i ponownie używane
WITH CTE_NAME AS (SELECT YEAR(paymentDate) AS years, SUM(amount) AS sum1 FROM payments GROUP BY years) SELECT q1.years, q2.years AS next_year, q1.sum1, q2.sum1 AS next_sum,100 * (q2.sum1 - q1.sum1) / q1.sum1 AS pct FROM CTE_NAME AS q1, CTE_NAME AS q2 WHERE q1.years = q2.years - 1; +-------+-----------+------------+------------+------------+ | years | next_year | sum1 | next_sum | pct | +-------+-----------+------------+------------+------------+ | 2003 | 2004 | 3250217.70 | 4313328.25 | 32.708903 | | 2004 | 2005 | 4313328.25 | 1290293.28 | -70.085901 | +-------+-----------+------------+------------+------------+ 2 rows in set (0.00 sec)
Możesz zauważyć, że przy CTE wyniki są takie same, a czas zapytania poprawia się o 50%, czytelność jest dobra i można się do niej wielokrotnie odwoływać
CTEs can refer to other CTEs: WITH cte1 AS (SELECT ... FROM ...), cte2 AS (SELECT ... FROM cte1 ...) SELECT FROM cte1, cte2 ...
Rekursywne CTE
Rekurencyjne CTE to CTE, które odwołuje się do siebie. W ten sposób wielokrotnie wykonywane jest początkowe CTE, zwracając podzbiory danych, aż do zwrócenia pełnego wyniku
WITH RECURSIVE cte_name AS ( cte_definition -- /* seed SELECT */ UNION ALL cte_definition -- /* "recursive" SELECT */ references cte_name. ) -- Statement using the CTE SELECT * FROM cte_name
Seed SELECT jest wykonywany raz w celu utworzenia początkowego podzbioru danych; rekursywny SELECT jest wielokrotnie wykonywany w celu zwrócenia podzbiorów danych, aż do uzyskania pełnego zestawu wyników. Rekurencja zatrzymuje się, gdy iteracja nie generuje żadnych nowych wierszy.
Załóżmy, że chcesz przeprowadzić hierarchiczne przechodzenie danych w celu utworzenia schematu organizacyjnego z łańcuchem zarządzania dla każdego pracownika (czyli ścieżki od dyrektora generalnego do pracownika). Użyj rekurencyjnego CTE! Rekurencyjne CTE są dobrze dostosowane do zapytań o dane hierarchiczne,
Utwórz tabelę
CREATE TABLE mangeremp ( id INT PRIMARY KEY NOT NULL, name VARCHAR(100) NOT NULL, man_id INT NULL, INDEX (man_id), FOREIGN KEY (man_id) REFERENCES mangeremp (id) );
wstawiaj dane, aby uzyskać hierarchiczną strukturę
INSERT INTO mangeremp VALUES (333, "waqas", NULL), # waqas is the CEO (man_id is NULL) (198, "ali", 333), # ali has ID 198 and reports to 333 (waqas) (692, "ahmed", 333), #ahmed report to waqas (29, "oasama", 198), #osama report to ali as alo has ref id 198 (4610, "Mughees", 29), # Mughees report to osama (72, "aslam", 29), (123, "afrooz", 692);
WITH RECURSIVE emp_paths (id, name, path) AS (SELECT id, name, CAST(id AS CHAR(200)) FROM mangeremp WHERE man_id IS NULL UNION ALL SELECT e.id, e.name, CONCAT(ep.path, ',', e.id) FROM emp_paths AS ep JOIN mangeremp AS e ON ep.id = e.man_id ) SELECT * FROM emp_paths ORDER BY path; +------+---------+-----------------+ | id | name | path | +------+---------+-----------------+ | 333 | waqas | 333 | | 198 | ali | 333,198 | | 29 | oasama | 333,198,29 | | 4610 | Mughees | 333,198,29,4610 | | 72 | aslam | 333,198,29,72 | | 692 | ahmed | 333,692 | | 123 | afrooz | 333,692,123 | +------+---------+-----------------+ 7 rows in set (0.00 sec)
SELECT e.id, e.name, CONCAT(ep.path, ',', e.id) FROM emp_paths AS ep JOIN mangeremp AS e ON ep.id = e.man_id ---- recursive query
Każdy wiersz utworzony przez zapytanie rekurencyjne znajduje wszystkich pracowników, którzy podlegają bezpośrednio
pracownikowi utworzonemu przez poprzedni wiersz. Dla każdego takiego pracownika wiersz zawiera
identyfikator pracownika, imię i nazwisko oraz łańcuch zarządzania pracownikami. Łańcuch to łańcuch menedżera
z dodanym na końcu identyfikatorem pracownika