MySQL에서 "WITH"절을 어떻게 사용합니까?


112

모든 SQL Server 쿼리를 MySQL로 변환하고 있으며 여기 WITH에 포함 된 쿼리 가 모두 실패합니다. 예를 들면 다음과 같습니다.

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
당신은 그 쿼리를 멍청하게 만들었습니까? 거기에는 CTE를 사용할 이유가 전혀 없습니다.
JeremyWeir

2
@NeilMcGuigan 오 마이 갓! 이것은 내가이 사이트에서 본 가장 재미있는 댓글 중 하나입니다.
Juan Carlos Coto

나는 이것이 관련되거나 중복 된 질문이라고 생각 한다 일련의 날짜 생성
Adam

2
호스팅 서비스 @NeilMcGuigan 대부분은 PostgreSQL을 MySQL에서 마이그레이션 프로세스의 두통을 계산하지 않고, 그것으로 업그레이드하기 쉽게 것으로, MySQL의 또는 MariaDB 제공 MySQL의 8 또는 MariaDB 10.2.1
Ivanzinho

답변:


135

버전 8.0 이전의 MySQL 은 WITH 절 (SQL Server 용어의 CTE, Oracle의 Subquery Factoring)을 지원하지 않으므로 다음을 사용해야합니다.

  • 임시 테이블
  • 파생 테이블
  • 인라인 뷰 (효과적으로 WITH 절이 나타내는 것-상호 교환 가능)

이 기능에 대한 요청은 2006 년으로 거슬러 올라갑니다.

언급했듯이 좋지 않은 예를 제공했습니다. 어떤 식 으로든 열의 출력을 변경하지 않는 경우 하위 선택을 수행 할 필요가 없습니다.

  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

다음은 더 좋은 예입니다.

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
당신이 하위 쿼리를 할 수 없어 - CTE는 일반적으로 재귀 지원이 언급해야한다
호건

8
이 질문은 MySQL에서 CTE 지원을 "모방"하는 것에 관한 것입니다. 할 수없는 한 가지는이를 지원하는 모든 플랫폼에서 CTE의 재귀 적 기능입니다. 그게 제 요점이었습니다.
Hogan

8
예. 그리고 그들은 여전히 릴리스 브랜치에서 구현하지 않았습니다. 분명히 그들은 런던의 PHPCONFERENCE2010에서이 "기능"을 "보여줬다". 그 버그 보고서에 대한이 의견은 말하고 있습니다. [2008 년 10 월 7 일 19:57] 스튜어트 프리드 버그 : "Valeriy, 믿을 수없는 백 로그가있을 것입니다. 요청을 제출하고 첫 번째 승인을받는 데 30 개월이 걸린다는 것은 엄청난 시간입니다. 요청을 고려해 주셔서 감사합니다. "
Shiva

5
이것이 mysql 8에 추가 된 것 같습니다 (링크는 여전히 bugs.mysql.com/bug.php?id=16244 )
Brian

9
이 답변의 요구 살해 - 2018, MySQL은 이제 절과 suuports
jcansell

26

Mysql 개발자 팀은 버전 8.0이 MySQL (CTE)에서 공통 테이블 표현식을 가질 것이라고 발표했습니다 . 따라서 다음과 같은 쿼리를 작성할 수 있습니다.


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(8.0 용으로 예정) + (재귀 CTE는 MySQL 8.0.1 이상에 있음)
gavenkoa

19

SQL에서 with 문은 CTE (공통 테이블 식)로 알려진 임시 명명 된 결과 집합을 지정합니다. 재귀 쿼리에 사용할 수 있지만이 경우 하위 집합으로 지정합니다. mysql이 하위 선택을 허용하면 시도 할 것입니다.

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

다음은 CTE에 대한 초보자 소개입니다. thecodeframework.com/introduction-to-mysql-cte
Gagan

6

lisachenko가 공유 한 링크를 따라이 블로그에 대한 또 다른 링크를 찾았습니다. http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html

이 게시물은 SQL WITH의 두 가지 용도를 에뮬레이션하는 방법을 설명합니다. SQL WITH와 유사한 쿼리를 수행하는 방법에 대한 정말 좋은 설명입니다.

1) WITH를 사용하면 동일한 하위 쿼리를 여러 번 수행 할 필요가 없습니다.

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) 재귀 쿼리는 쿼리를 사용하는 재귀와 유사한 호출을 만드는 저장 프로 시저로 수행 할 수 있습니다.

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

그리고 이것은 코드 또는 저장 프로 시저입니다.

# 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

MySQL에서는 '공통 테이블 표현식'기능이 제공되지 않으므로 해결하려면보기 또는 임시 테이블을 만들어야합니다. 여기서는 임시 테이블을 사용했습니다.

여기에 언급 된 저장 프로 시저가 필요를 해결합니다. 모든 팀 구성원 및 관련 구성원을 가져 오려면이 저장 프로 시저가 도움이됩니다.

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

암호:

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

다음을 사용하여 호출 할 수 있습니다.

mysql>call user_hier ('admin')//

2

이 기능을 공통 테이블 표현식이라고합니다. http://msdn.microsoft.com/en-us/library/ms190766.aspx

mySQL에서 정확한 작업을 수행 할 수 없습니다. 가장 쉬운 방법은 해당 CTE를 미러링하는보기를 만들고보기에서 선택하는 것입니다. 하위 쿼리로 수행 할 수 있지만 실제로 성능이 좋지 않습니다. 재귀를 수행하는 CTE를 만나면 저장 프로 시저를 사용하지 않고 어떻게 다시 만들 수 있을지 모르겠습니다.

편집 : 내 의견에서 말했듯이 게시 한 예제에는 CTE가 필요하지 않으므로 다음과 같이 작성할 수 있으므로 질문에 대해 단순화해야합니다.

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 : 사실이 아닙니다. 보기에는 메타 데이터 (예 CREATE/DROP VIEW:)가 있으며보기에 대한 권한을 부여 할 수 있습니다.
Bill Karwin

1

스레드 에서 @Brad의 답변을 좋아 했지만 추가 처리를 위해 결과를 저장하는 방법을 원했습니다 (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 ;

다음을 생성합니다.

dt        |
----------|
2015-01-01|
2015-01-02|
2015-01-03|
2015-01-04|
2015-01-05|
2015-01-06|
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.