Làm cách nào để bạn sử dụng mệnh đề “WITH” trong MySQL?


112

Tôi đang chuyển đổi tất cả các truy vấn SQL Server của mình sang MySQL và các truy vấn của tôi có WITHtrong đó đều không thành công. Đây là một ví dụ:

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
Bạn đã bỏ qua truy vấn đó phải không? Không có lý do gì để sử dụng CTE ở đó cả.
JeremyWeir

2
@NeilMcGuigan Ôi trời! Đây là một trong những nhận xét hài hước nhất mà tôi đã thấy trên trang web này (mặc dù nó thực sự không hài hước lắm, nhưng là sự táo tợn!;)) +1.
Juan Carlos Coto

Tôi nghĩ rằng đây là một câu hỏi có liên quan hoặc trùng lặp Tạo một loạt các ngày
Adam Porad

2
@NeilMcGuigan Hầu hết các dịch vụ lưu trữ chỉ cung cấp MySQL hoặc MariaDB, rằng nếu không có đếm đau đầu của quá trình chuyển đổi từ MySQL sang PostgreSQL, thật dễ dàng để nâng cấp lên MySQL 8 hoặc MariaDB 10.2.1
Ivanzinho

Câu trả lời:


135

MySQL trước phiên bản 8.0 không hỗ trợ mệnh đề WITH (CTE trong cách nói của SQL Server; Subquery Factoring trong Oracle), vì vậy bạn chỉ cần sử dụng:

  • Bảng TẠM THỜI
  • Đã phân phối bảng
  • chế độ xem nội tuyến (hiệu quả là mệnh đề WITH đại diện - chúng có thể hoán đổi cho nhau)

Yêu cầu cho tính năng này có từ năm 2006.

Như đã đề cập, bạn đã cung cấp một ví dụ kém - không cần thực hiện chọn phụ nếu bạn không thay đổi đầu ra của các cột theo bất kỳ cách nào:

  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

Đây là một ví dụ tốt hơn:

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
Điều này sẽ đề cập đến rằng CTE thường hỗ trợ đệ quy - mà bạn không thể làm gì với một phụ truy vấn
Hogan

8
Câu hỏi này là về việc "bắt chước" hỗ trợ CTE trong MySQL - một điều không thể không làm là chức năng đệ quy của CTE trong tất cả các nền tảng hỗ trợ nó, đó là quan điểm của tôi.
Hogan

8
Đúng vậy. Và họ vẫn chưa thực hiện nó trong nhánh phát hành của họ. Rõ ràng họ đã "khoe" "tính năng" này tại PHPCONFERENCE2010 ở London. Nhận xét này về báo cáo lỗi đó đang nói. [7 tháng 10 năm 2008 19:57] Stuart Friedberg: "Valeriy, các bạn chắc hẳn có một công việc tồn đọng không thể tin được. Ba mươi ba tháng từ khi gửi yêu cầu đến khi nhận được xác nhận đầu tiên là một khoảng thời gian đáng kinh ngạc. Cảm ơn bạn đã xem xét yêu cầu. "
Shiva

5
Có vẻ như điều này đang được thêm vào mysql 8 (liên kết vẫn là bug.mysql.com/bug.php?id=16244 )
Brian

9
Câu trả lời này nhu cầu tiêu diệt - năm 2018, MySQL tại suuports WITH khoản
jcansell

26

Nhóm nhà phát triển Mysql đã thông báo rằng phiên bản 8.0 sẽ có Biểu thức bảng chung trong MySQL (CTE) . Vì vậy, có thể viết các truy vấn như sau:


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 (Điều này được lên kế hoạch cho 8,0) + (Recursive CTEs là trong MySQL 8.0.1 và mới hơn)
gavenkoa

19

Trong Sql, câu lệnh with chỉ định một tập kết quả được đặt tên tạm thời, được gọi là biểu thức bảng chung (CTE). Nó có thể được sử dụng cho các truy vấn đệ quy, nhưng trong trường hợp này, nó chỉ định là tập con. Nếu mysql cho phép các lựa chọn phụ, tôi sẽ thử

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

Đây là phần giới thiệu cho người mới bắt đầu về CTE thecodeframework.com/introduction-to-mysql-cte
Gagan

6

Tôi theo liên kết được chia sẻ bởi lisachenko và tìm thấy một liên kết khác đến blog này: http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html

Bài đăng đưa ra các cách mô phỏng 2 cách sử dụng SQL WITH. Giải thích thực sự tốt về cách chúng hoạt động để thực hiện một truy vấn tương tự như SQL WITH.

1) Sử dụng WITH để bạn không phải thực hiện cùng một truy vấn phụ nhiều lần

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) Truy vấn đệ quy có thể được thực hiện bằng một thủ tục được lưu trữ làm cho cuộc gọi tương tự như một truy vấn đệ quy.

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

Và đây là mã hoặc thủ tục được lưu trữ

# 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

Tính năng 'Common Table Expression' không có sẵn trong MySQL, vì vậy bạn phải tạo một khung nhìn hoặc bảng tạm thời để giải quyết, ở đây tôi đã sử dụng một bảng tạm thời.

Thủ tục lưu trữ được đề cập ở đây sẽ giải quyết nhu cầu của bạn. Nếu tôi muốn nhận được tất cả các thành viên trong nhóm của mình và các thành viên liên kết của họ, quy trình được lưu trữ này sẽ giúp:

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

Mã:

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

Điều này có thể được gọi bằng cách sử dụng:

mysql>call user_hier ('admin')//

2

Tính năng đó được gọi là biểu thức bảng chung http://msdn.microsoft.com/en-us/library/ms190766.aspx

Bạn sẽ không thể làm điều chính xác trong mySQL, điều dễ dàng nhất có lẽ là tạo một chế độ xem phản chiếu CTE đó và chỉ cần chọn từ chế độ xem. Bạn có thể làm điều đó với các truy vấn con, nhưng điều đó sẽ thực sự kém. Nếu bạn gặp bất kỳ CTE nào thực hiện đệ quy, tôi không biết làm cách nào bạn có thể tạo lại CTE đó mà không sử dụng các thủ tục được lưu trữ.

CHỈNH SỬA: Như tôi đã nói trong nhận xét của mình, ví dụ bạn đã đăng không cần CTE, vì vậy bạn phải đơn giản hóa nó cho câu hỏi vì nó có thể được viết là

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: Không đúng. Một chế độ xem có siêu dữ liệu (tức là CREATE/DROP VIEW) và bạn có thể cấp đặc quyền cho một chế độ xem.
Bill Karwin

1

Tôi thích câu trả lời của @ Brad từ chủ đề này , nhưng muốn có cách lưu kết quả để xử lý thêm (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 ;

Sản xuất:

dt        |
----------|
2015-01-01|
2015-01-02|
2015-01-03|
2015-01-04|
2015-01-05|
2015-01-06|
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.