Bagaimana Anda menggunakan klausa "WITH" di MySQL?


112

Saya mengubah semua kueri SQL Server saya ke MySQL dan kueri yang ada WITHdi dalamnya semuanya gagal. Berikut contohnya:

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

1
Anda membodohi pertanyaan itu, bukan? Tidak ada alasan sama sekali untuk menggunakan CTE di sana.
JeremyWeir

2
@NeilMcGuigan Ya Tuhan! Ini adalah salah satu komentar terlucu yang pernah saya lihat di situs ini (meskipun sebenarnya tidak terlalu lucu, tetapi sifat nakal!;)) +1.
Juan Carlos Coto

Saya pikir ini adalah pertanyaan terkait atau duplikat. Menghasilkan serangkaian tanggal
Adam Porad

2
@NeilMcGuigan Sebagian besar layanan hosting hanya menawarkan MySQL atau MariaDB, sehingga tanpa menghitung pusingnya proses migrasi dari MySQL ke PostgreSQL, akan lebih mudah untuk memutakhirkan ke MySQL 8 atau MariaDB 10.2.1
Ivanzinho

Jawaban:


135

MySQL sebelum versi 8.0 tidak mendukung klausa WITH (CTE dalam bahasa SQL Server; Subquery Factoring di Oracle), jadi Anda harus menggunakan:

  • Tabel TEMPORARY
  • Tabel berbeda
  • tampilan sebaris (secara efektif mewakili klausa WITH - keduanya dapat dipertukarkan)

Permintaan fitur tersebut dimulai pada tahun 2006.

Seperti yang disebutkan, Anda memberikan contoh yang buruk - tidak perlu melakukan subselect jika Anda tidak mengubah output kolom dengan cara apa pun:

  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

Inilah contoh yang lebih baik:

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

24
Ini harus menyebutkan bahwa CTE biasanya mendukung rekursi - yang tidak dapat Anda lakukan dengan sub-kueri
Hogan

8
Pertanyaan ini adalah tentang "meniru" dukungan CTE di MySQL - satu hal yang tidak dapat dilakukan adalah fungsi rekursif CTE di semua platform yang mendukungnya, itulah maksud saya.
Hogan

8
Ya. Dan mereka masih belum menerapkannya di cabang rilis mereka. Rupanya mereka "memamerkan" fitur "ini" di PHPCONFERENCE2010 di London. Komentar pada laporan bug itu menjelaskan. [7 Okt 2008 19:57] Stuart Friedberg: "Valeriy, kalian pasti memiliki backlog yang tidak dapat dipercaya. Tiga puluh tiga bulan antara mengajukan permintaan dan mendapatkan pengakuan pertama adalah waktu yang sangat lama. Terima kasih telah mempertimbangkan permintaan tersebut. "
Siwa

5
Sepertinya ini ditambahkan ke mysql 8 (tautannya masih bugs.mysql.com/bug.php?id=16244 )
Brian

9
Jawaban ini perlu dibunuh - pada 2018, MySQL sekarang mendukung WITH Clause
jcansell

26

Tim Pengembang Mysql mengumumkan bahwa versi 8.0 akan memiliki Common Table Expressions di MySQL (CTEs) . Jadi dimungkinkan untuk menulis kueri seperti ini:


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)


bugs.mysql.com/bug.php?id=16244 (Ini direncanakan untuk 8.0) + (CTE rekursif ada di MySQL 8.0.1 dan yang lebih baru)
gavenkoa

19

Dalam Sql, pernyataan with menentukan kumpulan hasil bernama sementara, yang dikenal sebagai ekspresi tabel umum (CTE). Ini dapat digunakan untuk kueri rekursif, tetapi dalam kasus ini, ditetapkan sebagai subset. Jika mysql memungkinkan untuk sub-pilihan saya akan mencoba

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

Berikut adalah pengenalan pemula untuk CTE thecodeframework.com/introduction-to-mysql-cte
Gagan

6

Saya mengikuti tautan yang dibagikan oleh lisachenko dan menemukan tautan lain ke blog ini: http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html

Posting ini menjabarkan cara meniru 2 penggunaan SQL DENGAN. Penjelasan yang sangat bagus tentang bagaimana ini bekerja untuk melakukan kueri yang mirip dengan SQL WITH.

1) Gunakan DENGAN agar Anda tidak perlu melakukan sub-kueri yang sama beberapa kali

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) Kueri rekursif dapat dilakukan dengan prosedur tersimpan yang membuat panggilan mirip dengan kueri rekursif.

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,
""
);

Dan ini adalah kode atau prosedur yang tersimpan

# 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 ;

5

Fitur 'Common Table Expression' tidak tersedia di MySQL, jadi Anda harus pergi ke membuat tampilan atau tabel sementara untuk menyelesaikannya, di sini saya telah menggunakan tabel sementara.

Prosedur tersimpan yang disebutkan di sini akan memenuhi kebutuhan Anda. Jika saya ingin mendapatkan semua anggota tim saya dan anggota terkait mereka, prosedur tersimpan ini akan membantu:

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

Kode:

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

Ini bisa disebut menggunakan:

mysql>call user_hier ('admin')//

2

Fitur itu disebut ekspresi tabel umum http://msdn.microsoft.com/en-us/library/ms190766.aspx

Anda tidak akan dapat melakukan hal yang tepat di mySQL, hal termudah mungkin adalah membuat tampilan yang mencerminkan CTE itu dan cukup pilih dari tampilan. Anda dapat melakukannya dengan subkueri, tetapi kinerjanya sangat buruk. Jika Anda mengalami CTE yang melakukan rekursi, saya tidak tahu bagaimana Anda dapat membuatnya kembali tanpa menggunakan prosedur tersimpan.

EDIT: Seperti yang saya katakan di komentar saya, contoh yang Anda posting tidak memerlukan CTE, jadi Anda harus menyederhanakannya untuk pertanyaan karena hanya dapat ditulis sebagai

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

4
@derobert: Tidak benar. Tampilan memiliki metadata (yaitu CREATE/DROP VIEW), dan Anda dapat memberikan hak istimewa pada tampilan.
Bill Karwin

1

Saya menyukai jawaban @ Brad dari utas ini , tetapi ingin cara menyimpan hasil untuk diproses lebih lanjut (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 ;

Yang menghasilkan:

dt        |
----------|
2015-01-01|
2015-01-02|
2015-01-03|
2015-01-04|
2015-01-05|
2015-01-06|
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.