MySQL 8.0 - 상관 서브 쿼리의 쿼리 변환 1 - Transform correlated scalar subqueries

Share

Last Updated on 4월 9, 2022 by Jade(정현호)

안녕하세요 
이번 포스팅에서는 MySQL 서버 8.0.24 추가된 Transform correlated scalar subqueries 기능에 대해서 확인 해보려고 합니다. 

WL#13520 의 내용에 대한 확인/테스트 와 내용 번역으로 이루어져 있습니다. 

Correlated (Scalar) Subquery

Correlated Subquery 는 우리나라 말로 상관 서브쿼리 또는 상호연관 쿼리 등으로 표현 되며 내부의 서브쿼리가 외부의(메인의) 쿼리 와 연관된, 즉 참조하는 쿼리를 Correlated Subquery 라고 합니다
(포스팅에서는 이하 상관 서브쿼리 라고 표현 하겠습니다)

반대로 서브쿼리가 외부 쿼리와 어떤 연관 관계도 없이 단독으로 사용 된다면 비 상관 서브쿼리 라고 할 수 있겠습니다.

먼저 용어 적인 면에서 정리를 하고 넘어가려고 합니다.

통상 Oracle DB에서는 Select 절에서 사용 되는 서브쿼리를 Scalar subquery 라고 부릅니다.

그리고 MariaDB의 문서에서는 Scalar subquery 를 아래와 같이 표현 하고 있습니다.

A scalar subquery is a subquery that returns a single value. This is the simplest form of a subquery, and can be used in most places a literal or single column value is valid.



Oracle 문서에서도 동일(유사)한 공통의 문구를 확인 할 수 있습니다.

A scalar subquery expression is a subquery that returns exactly one column value from one row


MySQL WorkLog 와 Documents 에 Transform correlated scalar subqueries 라고 표현되어 있지만 기술되어 있는 예시 SQL들은 WHERE 절과 SELECT 절의 모두에서 서브쿼리가 사용되고 있습니다.

그래서 문서에 따라서 포스팅에서는 Scalar Subquery 라고 표현 하는 부분에 대해서는 "스칼라 서브 쿼리는 단일 값을 반환하는 서브 쿼리입니다(A scalar subquery is a subquery that returns a single value) 로 인지하고 글이 기술되어 있습니다.
           

Transform correlated scalar subqueries

Basic idea

해당 기능이 추가 된 기본적인 생각은 상관 관계 서브쿼리를 사용할 경우, 여러 번 구체화하는 것을 방지하기 위해(피하기 위해서) derived table을 구체화하여 상관 관계 있는 predicate 를 사용하여 Join(또는 Outer Join) 을 사용하는 하고자 하는 것 입니다.

먼저 테스트에 사용할 테이블과 데이터를 입력 해보도록 하겠습니다.

• 테이블 생성

create table t1 (
     id int auto_increment not null primary key,
     a int,
     b int,
     key ix_a(a)
);

create table t2 (
     id int auto_increment not null primary key,
     a int,
     b int,
     key ix_a(a)
);


• 데이터 입력

-- REDO 비활성화
ALTER INSTANCE DISABLE INNODB REDO_LOG;


-- T1 에 입력
DELIMITER $$
DROP PROCEDURE IF EXISTS loopInsert$$
 
CREATE PROCEDURE loopInsert()
BEGIN
    DECLARE i INT DEFAULT 1;
        
    WHILE i <= 300000 DO
        INSERT INTO t1
          VALUES(i, i, i);
        SET i = i + 1;
    END WHILE;
END$$
DELIMITER ;

call loopInsert();

DROP PROCEDURE loopInsert;


-- T2 에 입력
insert into t2 values(1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7);


-- REDO 활성화
ALTER INSTANCE ENABLE INNODB REDO_LOG;

      

Note

MySQL 8.0.21 버전 부터 인스턴스 레벨로 REDO 로그를 비할성화/활성화 할 수 있으며 인스턴스 생성 후, 복구가 필요치 않은 1회성 대량의 데이터 마이그레이션 등의 작업에서 REDO 로그를 비활성 후 데이터를 입력 하는 방법도 고려할 수 있습니다.

         

DEPENDENT SUBQUERY

DEPENDENT SUBQUERY 에 대해서 먼저 확인을 해보도록 하겠습니다.

DEPENDENT SUBQUERY 는 FROM 절에서 사용 되는 서브쿼리(오라클에서는 인라인 뷰 라고 표현) 가 아닌 서브 쿼리에서 단독으로 사용하는 비 상관 서브쿼리가 아닌 외부의 쿼리에서 값을 전달 받아서 실행되는, 즉 상관 서브쿼리가 사용 되는 경우 서브쿼리 블럭의 select_type 에 DEPENDENT SUBQUERY 이 표현되게 됩니다.

이런 형태는 상관 서브쿼리가 먼저 실행되지 못하고, 외부 쿼리(메인쿼리)의 결과 값에 의존성 또는 연관관계를 가지게 되며, 그렇기 때문에 실행 순서가 외부 쿼리-> 내부 쿼리 순으로 수행되는 것이 보통의 경우 입니다.

서브쿼리의 실행에 대한 부분은 MySQL 과 Oracle 이 조금은 다르게 처리 되며, Oracle 을 기준에서는 통상적으로 사용하던 실행 형태(순서) 이지만, MySQL 에서는 쿼리 유형에 따라서, MySQL 버전에 따라서 최적화 되지 못한(DEPENDENT SUBQUERY) 형태로 수행될 수도 있습니다.

물론 Oracle 에서도 쿼리와 데이터에 따라서 Unnest 하거나 하지 않거나 순서를 서브쿼리 부터 하거나 또는 외부 쿼리 부터 하는 등 상황에 따라서 힌트에 따라서 컨트롤 하여 사용합니다.

쉬운 예시인 아래와 같은 IN 절의 경우 Oracle 은 Subquery 부터 수행하여 그 결과를 외부 쿼리(메인 쿼리)에 조건절에 상수값 형태로 제공 하게 됩니다. 

select *
from t1
where col1 in (select col1
                 from t2
                 where col2='mathematics');

Oracle 기준 또는 보통의 경우 서브쿼리 부터 수행을 해서 그 결과 값을 외부쿼리(메인 쿼리)에 상수값으로 제공해주는 것이 실행 계획 상에는 가장 좋을 것이라고 생각 할 수 있습니다.

[참고] 이와 관련된 유사한 내용은 아래 포스팅을 참조해보시면 됩니다.


MySQL의 예전 버전에서는(특정 Optimizer 기능이 포함되지 않은 버전에서는) Plan 에서 DEPENDENT SUBQUERY 으로 수행되었습니다. 

-- LooseScan 와 materialized_subquery 기능 disable
set session optimizer_switch='semijoin=off';
set session optimizer_switch='materialization=off';


explain
select *
  from t1
    where a IN (select a from t2 where id in(1,2,3) );
    
+----+--------------------+-------+----------------+------+---------+------+--------+----------+--------------------------+
| id | select_type        | table | type           | key  | key_len | ref  | rows   | filtered | Extra                    |
+----+--------------------+-------+----------------+------+---------+------+--------+----------+--------------------------+
|  1 | PRIMARY            | t1    | ALL            | NULL | NULL    | NULL | 300065 |   100.00 | Using where              |
|  2 | DEPENDENT SUBQUERY | t2    | index_subquery | ix_a | 5       | func |      1 |    60.00 | Using where; Using index |
+----+--------------------+-------+----------------+------+---------+------+--------+----------+--------------------------+

* 일부 컬럼은 가로 길이에 의해 편집되어 있습니다

IN 절 이면서 서브쿼리가 상관 서브쿼리가 아닌데도 select_type 열 값이 DEPENDENT SUBQUERY 로 나올 수 있으며, 이런 경우에는 보통 IN 절이 MySQL 의 쿼리 최적화 과정에서 Transformation 이 되면서 DEPENDENT SUBQUERY 으로 수행되는 것 입니다.

그래서 MySQL 에서는 서브쿼리를 사용할 경우 SQL Plan 에 대해서 확인을 해야할 필요성이 있습니다.
사용자는 위의 쿼리에서 서브쿼리의 in(1,2,3) 조건으로 먼저 조회한 다음에 T1 태이블에 조건값으로 제공한다고 생각할 수도 있지만 실상은 그렇지 않을수 있기 때문 입니다.

최신의 버전에서는 위와 같은 Simpe IN Subquery 는 DEPENDENT SUBQUERY 대신 다른 형태로 수행되는 편이긴 합니다.

          

WHERE 절의 상관 서브쿼리

아래와 같은 상관 관계가 있는 서브쿼리를 수행할 경우 플랜을 확인 해보도록 하겠습니다.

explain
SELECT * 
 FROM t1 
  WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0;
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
| id | select_type        | table | type | key  | key_len | ref      | rows   | filtered | Extra       |
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
|  1 | PRIMARY            | t1    | ALL  | NULL | NULL    | NULL     | 300065 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | t2    | ref  | ix_a | 5       | tdb.t1.a |      1 |   100.00 | Using index |
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
* 가로 길이에 따라서 일부 컬럼이 편집되어 있습니다.


쿼리 플랜에서 확인 되는 것처럼 select_type이 DEPENDENT SUBQUERY 으로 수행됨에 따라서 서브 쿼리가 반복 수행이 되게 됩니다.

MySQL 8.0.18 에서 추가된 쿼리 플랜 수행시 통계치가 아닌 실제 수행을 하여 쿼리에 소비되는 Cost 와 시간을 측정하는 explain analyze 로 확인 하였을 때 cost=30174 와 0.46 sec,결과rows 5건 으로 확인 되었습니다.

explain analyze 에 관한 자세한 내용은 아래 포스팅을 참고하시면 됩니다.


MySQL 8.0.24 버전에서 Transform correlated scalar subqueries 이 추가 되었으며, optimizer_switch 에서 subquery_to_derived 과 연관되어 있습니다.

subquery_to_derived 는 테스트를 진행한 8.0.27 까지는 기본값이 OFF 입니다(마이너 버전 변경에 따라서 달라 질 수 있음)
그래서 ON 으로 변경해서 SQL 의 변경사항을 확인 해보겠습니다.

set session optimizer_switch='subquery_to_derived=on';

explain
SELECT * 
 FROM t1 
  WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0;
+----+-------------+------------+-------+------+---------+---------------+------+----------+--------------------------+
| id | select_type | table      | type  | key  | key_len | ref           | rows | filtered | Extra                    |
+----+-------------+------------+-------+------+---------+---------------+------+----------+--------------------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL | NULL    | NULL          |    5 |    33.33 | Using where              |
|  1 | PRIMARY     | t1         | ref   | ix_a | 5       | derived_1_2.a |    1 |   100.00 | NULL                     |
|  2 | DERIVED     | t2         | index | ix_a | 5       | NULL          |    5 |   100.00 | Using where; Using index |
+----+-------------+------------+-------+------+---------+---------------+------+----------+--------------------------+


처음 Driving 되는 쿼리 블럭이 <derived2> 로 확인 되며 <derived2>는 곧 t2(서브쿼리의 테이블) 를 의미 하게 됩니다
그래서 원하는 형태대로 서브쿼리 T2 테이블이 먼저 Access 되고 난 후 T1 과 조인 되는 형태로 Plan이 변경 되었고, 그에 따라서 읽는 Rows 수도 확연하게 줄어든 것을 확인할 수 있습니다.

explain analyze 로 확인 해 봤을때도 cost=3.67, 수행시간 0.00 sec 로 이전과 매우 차이가 나는 것을 확인 할 수 있습니다.

그리고 explain 하단의 note 란에는 변환된(transformation) 쿼리에 대한 정보를 확인 할 수 있습니다.

-- Query Transformation 정보

 /* select#1 */
select `tdb`.`t1`.`id` AS `id`
     , `tdb`.`t1`.`a` AS `a`
     , `tdb`.`t1`.`b` AS `b`
  from `tdb`.`t1` join
  ( /* select#2 */select `tdb`.`t2`.`a` AS `a`
         , count(0) AS `Name_exp_2`
      from `tdb`.`t2`
     where(`tdb`.`t2`.`a` > 0)
     group by `tdb`.`t2`.`a`) `derived_1_2`
    where((`tdb`.`t1`.`a` = `derived_1_2`.`a`)
              and reject_if(
                   (`derived_1_2`.`Name_exp_2` > 1)
                     )
                   )

서브쿼리가 group by 로 변경 되어 FROM 절로 이동 된 것을 확인 할 수 있으며 그로 인하여 JOIN 으로 수행 되고 있습니다.

"WHERE (SELECT a FROM t2 WHERE t2.a=t1.a) > 0;"
쿼리의 WHERE 절은 상관 서브쿼리 이며 IN 이 아니기 때문에 서브쿼리의 결과가 1개 이상 나오게 되면 subquery returns more than 1 row 에러가 발생되게 됩니다

그래서 위에서 변환된 쿼리에서 COUNT() 값이 1을 초과하지 않는지를 확인 해야 하며 내부적으로 reject_if 함수를 이용하게 됩니다.
그래서 COUNT() 가 1 을 초과화게 되면, reject_if 에 의해서 카디널리티 > 1 이면 오류가 발생되게 됩니다.

             

Outer aggregation

상관서브쿼리가 외부 쿼리 블록의 집계인 경우 변환이 쉽지 않으므로 이러한 쿼리는 변환에서 제외됩니다
예를 들어:

SET session optimizer_switch='subquery_to_derived=on';

explain
SELECT 
  COUNT(*),
  a,
  (SELECT MIN(a) FROM t2 WHERE a = COUNT(*))  
FROM t1 GROUP BY a;

+----+--------------------+-------+-------+------+---------+------+--------+----------+--------------------------+
| id | select_type        | table | type  | key  | key_len | ref  | rows   | filtered | Extra                    |
+----+--------------------+-------+-------+------+---------+------+--------+----------+--------------------------+
|  1 | PRIMARY            | t1    | index | ix_a | 5       | NULL | 300065 |   100.00 | Using index              |
|  2 | DEPENDENT SUBQUERY | t2    | ref   | ix_a | 5       | func |      1 |   100.00 | Using where; Using index |
+----+--------------------+-------+-------+------+---------+------+--------+----------+--------------------------+
* 가로 길이에 따른 일부 컬럼이 편집되어 있습니다.


위의 쿼리에서 상관 서브쿼리 블럭의 COUNT(*)는 WHERE 절에서의 위치로 인해 외부 쿼리(메인쿼리)에서 집계됩니다.
필터링은 그룹화 전에 발생되게 됩니다.

즉, 그룹화된 상관 서브 쿼리 와 join 하기 전에 외부 그룹화를 수행해야 하며, 위의 쿼리와 같은 경우 쿼리 변환에서 제외되게 됩니다.

마찬가지로 이러한 외부 SQL 집계가 고려 중인 상관 서브 쿼리에 중첩된 서브 쿼리가 있는 경우도 Query transformation 이 Skip 되게 됩니다.
         

DISTINCT

이 특별한 경우는 우리가 그룹화하는 필드만 포함하는 select expression에 서브쿼리가 DISTINCT가 포함된 경우입니다.

SELECT *
  FROM t1
 WHERE (SELECT DISTINCT a
          FROM t2
         WHERE t2.a=t1.a
       ) > 0 ;

쿼리는 위와 같은 형태를 띄게 됩니다.

Original 쿼리의 실행 계획 및 subquery_to_derived 활성 화 후 SQL 실행 계획을 확인 해보도록 하겠습니다.

-- Original Plan
explain
SELECT *
  FROM t1
 WHERE (SELECT DISTINCT a
          FROM t2
         WHERE t2.a=t1.a
       ) > 0 ;
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
| id | select_type        | table | type | key  | key_len | ref      | rows   | filtered | Extra       |
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
|  1 | PRIMARY            | t1    | ALL  | NULL | NULL    | NULL     | 300065 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | t2    | ref  | ix_a | 5       | tdb.t1.a |      1 |   100.00 | Using index |
+----+--------------------+-------+------+------+---------+----------+--------+----------+-------------+
* 가로 길이에 따른 일부 컬럼이 편집되어 있습니다.



-- subquery_to_derived 활성화 후 Plan
SET session optimizer_switch='subquery_to_derived=on';

explain
SELECT *
  FROM t1
 WHERE (SELECT DISTINCT a
          FROM t2
         WHERE t2.a=t1.a
       ) > 0 ;
+----+-------------+------------+-------+------+---------+---------------+------+----------+-------------------------------------------+
| id | select_type | table      | type  | key  | key_len | ref           | rows | filtered | Extra                                     |
+----+-------------+------------+-------+------+---------+---------------+------+----------+-------------------------------------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL | NULL    | NULL          |    5 |    33.33 | Using where                               |
|  1 | PRIMARY     | t1         | ref   | ix_a | 5       | derived_1_2.a |    1 |   100.00 | NULL                                      |
|  2 | DERIVED     | t2         | index | ix_a | 5       | NULL          |    5 |   100.00 | Using where; Using index; Using temporary |
+----+-------------+------------+-------+------+---------+---------------+------+----------+-------------------------------------------+
* 가로 길이에 따른 일부 컬럼이 편집되어 있습니다.


이번에도 Original 에서는 DEPENDENT SUBQUERY 으로 수행 되었으며 그에 따라 비효율이 발생되고 있습니다.
optimizer 모드에서 subquery_to_derived 를 활성화를 하면 조인 형태로 수행이 되어 많은 개선된 실행 계획으로 수행이 되는 것을 확인 할 수 있습니다.

       

A special case: COUNT

이번에 확인 해볼 부분은 상관 서브쿼리 이면서 count 를 하며, 서브쿼리의 위치가 SELECT 절에 위치한(오라클에서는 스칼라 서브쿼리 라고함) 경우 입니다.

테스트를 위해서 테이블을 새로 만들고 데이터를 입력해 보겠습니다.

CREATE TABLE t3 (id INT);
CREATE TABLE t4 (id INT);

INSERT INTO t3 VALUES (1), (2);
INSERT INTO t4 VALUES (1);


그리고 쿼리와 그 수행 결과는 아래와 같습니다.

SELECT t3.id, ( SELECT COUNT(t4.id)
                  FROM t4 AS t4
                  WHERE t4.id = t3.id ) AS c FROM t3;
+------+------+
| id   | c    |
+------+------+
|    1 |    1 |
|    2 |    0 |
+------+------+


카운트의 경우 조인 조건이 맞지 않는 공집합의 경우 값이 없음(no rows) 가 아닌 0 결과가 나오게 됩니다.
SUM 함수는 다르며 관련 되어서는 아래 포스팅을 참고 해보셔도 좋을 것 같습니다.


그래서 위의 서브 쿼리의 경우 id=2 는 조인 조건이 맞지 않지만 count 쿼리 이기 때문에 c 컬럼의(카운트 결과) 값이 0 으로 나오게 됩니다. 서브쿼리를 derived 으로 쿼리 변환시 문서에서 설명하는 형태로 left outer join 으로 변경하게 되면 아래와 같이 변환 할수 있게 됩니다.

SELECT t3.id AS id,
       derived_1_2.`COUNT(t.id)` AS c
FROM t3 LEFT JOIN
     (SELECT COUNT(t.id) AS `COUNT(t.id)`,
             t.id AS id
      FROM t4 t
      GROUP BY t.id) derived_1_2
      ON derived_1_2.id = t3.id;  


하지만 위와 같이 쿼리를 변경 하고 수행하면 원본 쿼리와 다른 결과가 나오게 됩니다(wrong result)

+------+------+
| id   | c    |
+------+------+
|    1 |    1 |
|    2 | NULL |
+------+------+


조인으로 변경된 후에는 조인 조건에 맞지 않기 때문에 NULL 로 결과가 나오게 됩니다.
따라서 COALESCE 나 IFNULL 로 처리 해주면 원하는 결과로 출력은 할 수 있습니다.

SELECT t3.id AS id,
       -- COALESCE(derived_1_2.`COUNT(t.id)`, 0) AS c
       IFNULL(derived_1_2.`COUNT(t.id)`, 0) AS c
FROM t3 LEFT JOIN
     (SELECT COUNT(t.id) AS `COUNT(t.id)`,
             t.id AS id
      FROM t4 t
      GROUP BY t.id) derived_1_2
      ON derived_1_2.id = t3.id;  
+------+---+
| id   | c |
+------+---+
|    1 | 1 |
|    2 | 0 |
+------+---+

서브 쿼리 표현식을 outer join query 로 이동하면서, 파생 테이블의 count 는 유지를 하고, 표현식의 count 에는 coalesce 로 치환하는 형태로 변환이 필요하지만, 이런 변환은 아직까지는 지원하지 않지 않습니다 라고는 worklog 에는 표기되어 있습니다.

하지만 subquery_to_derived 를 활성화 하고 테스틀 해보면 서브쿼리가 derived 으로 이동 및 left outer join 으로 수행되고 맞는 결과를 위해서 COALESCE 으로 래핑이 되는 형태로 Query Transformation 이 되는 것을 확인 할 수 있습니다.


아래 SQL 과 플랜은 건수로 처리 내역을 확인 하기 위해서 위에서 이전에 생성한 t1, t2 테이블로 조회하였습니다.

set session optimizer_switch='subquery_to_derived=on';

EXPLAIN SELECT t1.id, ( SELECT COUNT(t2.id)
                  FROM t2 AS t2
                  WHERE t2.id = t1.id ) AS c FROM t1;
+----+-------------+------------+-------+---------+---------+------+--------+----------+--------------------------------------------+
| id | select_type | table      | type  | key     | key_len | ref  | rows   | filtered | Extra                                      |
+----+-------------+------------+-------+---------+---------+------+--------+----------+--------------------------------------------+
|  1 | PRIMARY     | t1         | index | ix_a    | 5       | NULL | 300065 |   100.00 | Using index                                |
|  1 | PRIMARY     | <derived2> | ALL   | NULL    | NULL    | NULL |      5 |   100.00 | Using where; Using join buffer (hash join) |
|  2 | DERIVED     | t2         | index | PRIMARY | 4       | NULL |      5 |   100.00 | Using index                                |
+----+-------------+------------+-------+---------+---------+------+--------+----------+--------------------------------------------+
* 가로 길이에 의해 일부 컬럼이 편집되어 있습니다.

Note (Code 1003)
 /* select#1 */
select `tdb`.`t1`.`id` AS `id`
     , coalesce(`derived_1_2`.`COUNT(t2.id) ` ,0) AS `c`
  from `tdb`.`t1` left join
      ( /* select#2 */select count(`tdb`.`t2`.`id`) AS `COUNT(t2.id) `
             , `tdb`.`t2`.`id` AS `id`
          from `tdb`.`t2`
         group by `tdb`.`t2`.`id`) `derived_1_2` on((`derived_1_2`.`id` = `tdb`.`t1`.`id`))
        where true;

left outer join 임으로 t1->t2 순으로 테이블 조인 순서로 진행되게 되며,  아래 Note 내용에서 확인 가능한 내용처럼 동일한 값을 보장하기 위해서 COALESCE 으로 래핑 되어있는 것을 확인 할 수 있다.

서브쿼리가 derived 테이블로 변환 되었기 때문에 조인으로 수행 될 수 있게 되고, MySQL 8.0.20 이상 부터는 outer join 에서도 hash join 이 지원 되기 때문에  위의 실행 계획에서 hash join 이 확인 되고 있으며, 레코드 건수 등에 따라서 Hash Join 대신 Nested Loop 로 수행될 수도 있습니다.

Note

MySQL 8.0.19 버전 부터는 Hash Join 은 block_nested_loop 옵티마이저 모드로 사용 유무를 컨트롤 할 수 있습니다

MySQL의 Hash Join 은 아래 포스팅을 참고 하시면 됩니다


다른 집계 함수(대표적으로 SUM)의 경우 빈 결과 집합에서 null을 생성 하므로 coalesce 가 필요하지 않습니다.
그래서 worklog 에서 설명되어 있는 제한이 적용되지 않습니다.

set session optimizer_switch='subquery_to_derived=on';

explain
SELECT t3.id, ( SELECT SUM(t4.id)
                  FROM t4 AS t4
                  WHERE t4.id = t3.id ) AS c FROM t3;
Note
 /* select#1 */
select `tdb`.`t3`.`id` AS `id`
     , `derived_1_2`.`SUM(t4.id) ` AS `c`  <-- coalesce 이 없음
  from `tdb`.`t3` left join
      ( /* select#2 */select sum(`tdb`.`t4`.`id`) AS `SUM(t4.id) `
             , `tdb`.`t4`.`id` AS `id`
          from `tdb`.`t4`
         group by `tdb`.`t4`.`id`) `derived_1_2` on((`derived_1_2`.`id` = `tdb`.`t3`.`id`))
        where true


또한 모든 집계 쿼리에 대해서 각 그룹에 대해 단일 행만 있다는 것을 알고 있으므로 런타임시 카디널리티 검사가 추가되지 않습니다(reject_if 미사용)

그래서 모든 DB의 Query Transformation 의 기본 원칙은 당연하게 변경 전 쿼리와 변경 후 쿼리의 결과 값이 같아야 한다 라는 것이 가장 기본적 이면서 중요한 점일 것으로 생각 됩니다.


이어지는 다음 글

                  

Reference

Reference Link
mysql.com/worklog/13520
mysql.com/blog/mysql-8-0-24-release
• mysql.com/correlated-subqueries
mysql.com/subquery-materialization
mysql.com/transformation-scalar-in
mariadb.com/subqueries-scalar-subqueries
oracle.com/oracle/Scalar-Subquery-Expressions


연관된 다른 글

 

 

 

 

 

      

0
글에 대한 당신의 생각을 기다립니다. 댓글 의견 주세요!x