Wie verwendet man die WITH-Klausel in MySQL?

111

Ich konvertiere alle meine SQL Server-Abfragen in MySQL und meine darin enthaltenen Abfragen schlagen WITHalle fehl. Hier ist ein Beispiel:

WITH t1 AS
(
     SELECT article.*, userinfo.*, category.*
     FROM question
     INNER JOIN userinfo ON userinfo.user_userid = article.article_ownerid
     INNER JOIN category ON article.article_categoryid = category.catid
     WHERE article.article_isdeleted = 0
)
SELECT t1.*
FROM t1
ORDER BY t1.article_date DESC
LIMIT 1, 3
user161433
quelle
1
Sie haben diese Abfrage verworfen, oder? Es gibt dort überhaupt keinen Grund, einen CTE zu verwenden.
JeremyWeir
2
@NeilMcGuigan Oh mein Gott! Dies ist einer der lustigsten Kommentare, die ich auf dieser Seite gesehen habe (obwohl es wirklich nicht so lustig ist, aber die Frechheit !;)) +1.
Juan Carlos Coto
Ich denke, dies ist eine verwandte oder doppelte Frage. Generieren einer Reihe von Daten
Adam Porad
2
@NeilMcGuigan Die meisten Hosting-Dienste bieten nur MySQL oder MariaDB an. Ohne Berücksichtigung der Kopfschmerzen beim Migrationsprozess von MySQL zu PostgreSQL ist ein Upgrade auf MySQL 8 oder MariaDB 10.2.1
Ivanzinho

Antworten:

134

MySQL vor Version 8.0 unterstützt die WITH-Klausel nicht (CTE in der SQL Server-Sprache; Subquery Factoring in Oracle). Sie müssen also Folgendes verwenden:

  • VORÜBERGEHENDE Tabellen
  • ABGELEITETE Tabellen
  • Inline-Ansichten (effektiv, was die WITH-Klausel darstellt - sie sind austauschbar)

Die Anfrage für die Funktion stammt aus dem Jahr 2006.

Wie bereits erwähnt, haben Sie ein schlechtes Beispiel angegeben. Sie müssen keine Unterauswahl durchführen, wenn Sie die Ausgabe der Spalten in keiner Weise ändern:

  SELECT * 
    FROM ARTICLE t
    JOIN USERINFO ui ON ui.user_userid = t.article_ownerid
    JOIN CATEGORY c ON c.catid =  t.article_categoryid
   WHERE t.published_ind = 0
ORDER BY t.article_date DESC 
   LIMIT 1, 3

Hier ist ein besseres Beispiel:

SELECT t.name,
       t.num
  FROM TABLE t
  JOIN (SELECT c.id
               COUNT(*) 'num'
          FROM TABLE c
         WHERE c.column = 'a'
      GROUP BY c.id) ta ON ta.id = t.id
OMG Ponys
quelle
24
Dies sollte erwähnen, dass CTE normalerweise Rekursion unterstützt - was Sie mit einer Unterabfrage nicht tun können
Hogan
8
Bei dieser Frage geht es darum, die CTE-Unterstützung in MySQL zu "imitieren" - eine Sache, die nicht möglich ist, ist die rekursive Funktionalität von CTEs auf allen Plattformen, die sie unterstützen. Das war mein Punkt.
Hogan
8
Jep. Und sie haben es immer noch nicht in ihrem Release-Zweig implementiert. Anscheinend haben sie dieses "Feature" auf der PHPCONFERENCE2010 in London "vorgeführt". Dieser Kommentar zu diesem Fehlerbericht ist aussagekräftig. [7. Oktober 2008, 19:57 Uhr] Stuart Friedberg: "Valeriy, ihr müsst einen unglaublichen Rückstand haben. Dreiunddreißig Monate zwischen dem Einreichen einer Anfrage und dem Erhalten einer ersten Bestätigung sind eine umwerfende Zeitspanne. Vielen Dank, dass ihr die Anfrage geprüft habt. ""
Shiva
5
Sieht so aus, als würde dies zu MySQL 8 hinzugefügt (Link ist immer noch bugs.mysql.com/bug.php?id=16244 )
Brian
9
Diese Antwort muss getötet werden - im Jahr 2018 unterstützt MySQL nun die WITH-Klausel
jcansell
25

Das MySQL-Entwicklerteam gab bekannt, dass Version 8.0 Common Table Expressions in MySQL (CTEs) enthalten wird . So können Abfragen wie folgt geschrieben werden:


WITH RECURSIVE my_cte AS
(
  SELECT 1 AS n
  UNION ALL
  SELECT 1+n FROM my_cte WHERE n<10
)
SELECT * FROM my_cte;
+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
|    6 |
|    7 |
|    8 |
|    9 |
|   10 |
+------+
10 rows in set (0,00 sec)

lisachenko
quelle
bugs.mysql.com/bug.php?id=16244 (Dies ist für 8.0 geplant) + (Rekursive CTEs sind in MySQL 8.0.1 und neuer)
Gavenkoa
19

In SQL gibt die with-Anweisung eine temporäre benannte Ergebnismenge an, die als Common Table Expression (CTE) bezeichnet wird. Es kann für rekursive Abfragen verwendet werden, wird jedoch in diesem Fall als Teilmenge angegeben. Wenn MySQL Unterauswahlen zulässt, würde ich es versuchen

select t1.* 
from  (
            SELECT  article.*, 
                    userinfo.*, 
                    category.* 
            FROM    question INNER JOIN 
                    userinfo ON userinfo.user_userid=article.article_ownerid INNER JOIN category ON article.article_categoryid=category.catid
            WHERE   article.article_isdeleted = 0
     ) t1
ORDER BY t1.article_date DESC Limit 1, 3
Adriaan Stander
quelle
Hier ist eine Einführung für Anfänger in CTE thecodeframework.com/introduction-to-mysql-cte
Gagan
5

Die Funktion "Common Table Expression" ist in MySQL nicht verfügbar. Sie müssen also eine Ansicht oder eine temporäre Tabelle erstellen, um sie zu lösen. Hier habe ich eine temporäre Tabelle verwendet.

Die hier erwähnte gespeicherte Prozedur wird Ihren Bedarf lösen. Wenn ich alle meine Teammitglieder und die zugehörigen Mitglieder erhalten möchte, hilft diese gespeicherte Prozedur:

----------------------------------
user_id   |   team_id
----------------------------------
admin     |   NULL
ramu      |   admin
suresh    |   admin
kumar     |   ramu
mahesh    |   ramu
randiv    |   suresh
-----------------------------------

Code:

DROP PROCEDURE `user_hier`//
CREATE DEFINER=`root`@`localhost` PROCEDURE `user_hier`(in team_id varchar(50))
BEGIN
declare count int;
declare tmp_team_id varchar(50);
CREATE TEMPORARY TABLE res_hier(user_id varchar(50),team_id varchar(50))engine=memory;
CREATE TEMPORARY TABLE tmp_hier(user_id varchar(50),team_id varchar(50))engine=memory;
set tmp_team_id = team_id;
SELECT COUNT(*) INTO count FROM user_table WHERE user_table.team_id=tmp_team_id;
WHILE count>0 DO
insert into res_hier select user_table.user_id,user_table.team_id from user_table where user_table.team_id=tmp_team_id;
insert into tmp_hier select user_table.user_id,user_table.team_id from user_table where user_table.team_id=tmp_team_id;
select user_id into tmp_team_id from tmp_hier limit 0,1;
select count(*) into count from tmp_hier;
delete from tmp_hier where user_id=tmp_team_id;
end while;
select * from res_hier;
drop temporary table if exists res_hier;
drop temporary table if exists tmp_hier;
end

Dies kann aufgerufen werden mit:

mysql>call user_hier ('admin')//
Kanagu
quelle
5

Ich bin dem von lisachenko geteilten Link gefolgt und habe einen weiteren Link zu diesem Blog gefunden: http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html

In diesem Beitrag werden Möglichkeiten zur Emulation der beiden Verwendungen von SQL WITH beschrieben. Wirklich gute Erklärung, wie diese funktionieren, um eine ähnliche Abfrage wie SQL WITH durchzuführen.

1) Verwenden Sie WITH, damit Sie dieselbe Unterabfrage nicht mehrmals ausführen müssen

CREATE VIEW D AS (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR);
SELECT D1.YEAR, (CASE WHEN D1.S>D2.S THEN 'INCREASE' ELSE 'DECREASE' END) AS TREND
FROM
 D AS D1,
 D AS D2
WHERE D1.YEAR = D2.YEAR-1;
DROP VIEW D;

2) Rekursive Abfragen können mit einer gespeicherten Prozedur ausgeführt werden, die den Aufruf einer rekursiven Abfrage ähnelt.

CALL WITH_EMULATOR(
"EMPLOYEES_EXTENDED",
"
  SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS
  FROM EMPLOYEES
  WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL)
",
"
  SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS
  FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID
  GROUP BY M.ID, M.NAME, M.MANAGER_ID
",
"SELECT * FROM EMPLOYEES_EXTENDED",
0,
""
);

Und das ist der Code oder die gespeicherte Prozedur

# Usage: the standard syntax:
#   WITH RECURSIVE recursive_table AS
#    (initial_SELECT
#     UNION ALL
#     recursive_SELECT)
#   final_SELECT;
# should be translated by you to 
# CALL WITH_EMULATOR(recursive_table, initial_SELECT, recursive_SELECT,
#                    final_SELECT, 0, "").

# ALGORITHM:
# 1) we have an initial table T0 (actual name is an argument
# "recursive_table"), we fill it with result of initial_SELECT.
# 2) We have a union table U, initially empty.
# 3) Loop:
#   add rows of T0 to U,
#   run recursive_SELECT based on T0 and put result into table T1,
#   if T1 is empty
#      then leave loop,
#      else swap T0 and T1 (renaming) and empty T1
# 4) Drop T0, T1
# 5) Rename U to T0
# 6) run final select, send relult to client

# This is for *one* recursive table.
# It would be possible to write a SP creating multiple recursive tables.

delimiter |

CREATE PROCEDURE WITH_EMULATOR(
recursive_table varchar(100), # name of recursive table
initial_SELECT varchar(65530), # seed a.k.a. anchor
recursive_SELECT varchar(65530), # recursive member
final_SELECT varchar(65530), # final SELECT on UNION result
max_recursion int unsigned, # safety against infinite loop, use 0 for default
create_table_options varchar(65530) # you can add CREATE-TABLE-time options
# to your recursive_table, to speed up initial/recursive/final SELECTs; example:
# "(KEY(some_column)) ENGINE=MEMORY"
)

BEGIN
  declare new_rows int unsigned;
  declare show_progress int default 0; # set to 1 to trace/debug execution
  declare recursive_table_next varchar(120);
  declare recursive_table_union varchar(120);
  declare recursive_table_tmp varchar(120);
  set recursive_table_next  = concat(recursive_table, "_next");
  set recursive_table_union = concat(recursive_table, "_union");
  set recursive_table_tmp   = concat(recursive_table, "_tmp"); 
  # Cleanup any previous failed runs
  SET @str =
    CONCAT("DROP TEMPORARY TABLE IF EXISTS ", recursive_table, ",",
    recursive_table_next, ",", recursive_table_union,
    ",", recursive_table_tmp);
  PREPARE stmt FROM @str;
  EXECUTE stmt; 
 # If you need to reference recursive_table more than
  # once in recursive_SELECT, remove the TEMPORARY word.
  SET @str = # create and fill T0
    CONCAT("CREATE TEMPORARY TABLE ", recursive_table, " ",
    create_table_options, " AS ", initial_SELECT);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  SET @str = # create U
    CONCAT("CREATE TEMPORARY TABLE ", recursive_table_union, " LIKE ", recursive_table);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  SET @str = # create T1
    CONCAT("CREATE TEMPORARY TABLE ", recursive_table_next, " LIKE ", recursive_table);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  if max_recursion = 0 then
    set max_recursion = 100; # a default to protect the innocent
  end if;
  recursion: repeat
    # add T0 to U (this is always UNION ALL)
    SET @str =
      CONCAT("INSERT INTO ", recursive_table_union, " SELECT * FROM ", recursive_table);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    # we are done if max depth reached
    set max_recursion = max_recursion - 1;
    if not max_recursion then
      if show_progress then
        select concat("max recursion exceeded");
      end if;
      leave recursion;
    end if;
    # fill T1 by applying the recursive SELECT on T0
    SET @str =
      CONCAT("INSERT INTO ", recursive_table_next, " ", recursive_SELECT);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    # we are done if no rows in T1
    select row_count() into new_rows;
    if show_progress then
      select concat(new_rows, " new rows found");
    end if;
    if not new_rows then
      leave recursion;
    end if;
    # Prepare next iteration:
    # T1 becomes T0, to be the source of next run of recursive_SELECT,
    # T0 is recycled to be T1.
    SET @str =
      CONCAT("ALTER TABLE ", recursive_table, " RENAME ", recursive_table_tmp);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    # we use ALTER TABLE RENAME because RENAME TABLE does not support temp tables
    SET @str =
      CONCAT("ALTER TABLE ", recursive_table_next, " RENAME ", recursive_table);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    SET @str =
      CONCAT("ALTER TABLE ", recursive_table_tmp, " RENAME ", recursive_table_next);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    # empty T1
    SET @str =
      CONCAT("TRUNCATE TABLE ", recursive_table_next);
    PREPARE stmt FROM @str;
    EXECUTE stmt;
  until 0 end repeat;
  # eliminate T0 and T1
  SET @str =
    CONCAT("DROP TEMPORARY TABLE ", recursive_table_next, ", ", recursive_table);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  # Final (output) SELECT uses recursive_table name
  SET @str =
    CONCAT("ALTER TABLE ", recursive_table_union, " RENAME ", recursive_table);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  # Run final SELECT on UNION
  SET @str = final_SELECT;
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  # No temporary tables may survive:
  SET @str =
    CONCAT("DROP TEMPORARY TABLE ", recursive_table);
  PREPARE stmt FROM @str;
  EXECUTE stmt;
  # We are done :-)
END|

delimiter ;
sbaer
quelle
2

Diese Funktion wird als allgemeiner Tabellenausdruck http://msdn.microsoft.com/en-us/library/ms190766.aspx bezeichnet

In mySQL können Sie nicht genau das tun. Am einfachsten wäre es wahrscheinlich, eine Ansicht zu erstellen, die diesen CTE widerspiegelt, und einfach aus der Ansicht auszuwählen. Sie können dies mit Unterabfragen tun, aber das wird wirklich schlecht funktionieren. Wenn Sie auf CTEs stoßen, die eine Rekursion durchführen, weiß ich nicht, wie Sie diese ohne Verwendung gespeicherter Prozeduren neu erstellen können.

BEARBEITEN: Wie ich in meinem Kommentar sagte, ist für das von Ihnen veröffentlichte Beispiel kein CTE erforderlich, daher müssen Sie es für die Frage vereinfacht haben, da es nur als geschrieben werden kann

SELECT article.*, userinfo.*, category.* FROM question
     INNER JOIN userinfo ON userinfo.user_userid=article.article_ownerid
     INNER JOIN category ON article.article_categoryid=category.catid
     WHERE article.article_isdeleted = 0
 ORDER BY article_date DESC Limit 1, 3
JeremyWeir
quelle
4
@derobert: Nicht wahr. Eine Ansicht verfügt über Metadaten (dh CREATE/DROP VIEW), und Sie können einer Ansicht Berechtigungen erteilen.
Bill Karwin
1

Ich mochte die Antwort von @ Brad aus diesem Thread , wollte aber eine Möglichkeit, die Ergebnisse für die weitere Verarbeitung zu speichern (MySql 8):

-- May need to adjust the recursion depth first
SET @@cte_max_recursion_depth = 10000 ; -- permit deeper recursion

-- Some boundaries 
set @startDate = '2015-01-01'
    , @endDate = '2020-12-31' ; 

-- Save it to a table for later use
drop table if exists tmpDates ;
create temporary table tmpDates as      -- this has to go _before_ the "with", Duh-oh! 
    WITH RECURSIVE t as (
        select @startDate as dt
      UNION
        SELECT DATE_ADD(t.dt, INTERVAL 1 DAY) FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY) <= @endDate
    )
    select * FROM t     -- need this to get the "with"'s results as a "result set", into the "create"
;

-- Exists?
select * from tmpDates ;

Welches produziert:

dt        |
----------|
2015-01-01|
2015-01-02|
2015-01-03|
2015-01-04|
2015-01-05|
2015-01-06|
Sinecospi
quelle