I. 概要
with as ステートメントは SQL の一般的な構文であり它可以为一个查询结果或子查询结果创建一个临时表
、この一時テーブルは後続のクエリで使用できます。一時テーブルはクエリの終了後にクリアされます。この構文を使用すると、複雑なクエリが単純になり、クエリの効率が向上します。
WITH AS 句はサブクエリ ファクタリングとも呼ばれ、SQL ステートメント全体で使用される SQL フラグメントを定義するために使用されます。このステートメントは共通テーブル式 (CTE、共通テーブル式) とみなされます。
with-as 意味:
1. 複数の繰り返しサブクエリの場合、テーブル スキャンとコードの書き換えの数を減らし、パフォーマンスを最適化し、コーディングをより簡潔にすることができます。また、データを提供する部分として UNION ALL のさまざまな部分で使用することもできます。 。
2. UNION ALL の場合は、WITH AS を使用して UNION ALL ステートメントを定義します。フラグメントが 2 回以上呼び出される場合、オプティマイザは WITH AS 指定によって取得されたデータを一時テーブルに自動的に配置します。プロンプト「meterize」は、WITH AS 句のデータをグローバル一時テーブルに強制的に入れます。この方法で多くのクエリを高速化できます。
with as ステートメントは、myql、oracle、db2、hive、sql サーバー、MariaDB、PostgreSQL およびその他のデータベースをサポートします。いくつかのデータベースのサポートされているバージョンは次のとおりです。
mysql版本:8以及8以上的
- SQL サーバー: SQL サーバー 2005 以降のバージョン
- oracle: Oracle 9i バージョン 2 データベース
2. 基本的な文法
with クエリ ステートメントは select ではなく、"WITH" キーワードで始まります。クエリが実行される前に一時テーブルが事前に構築され、その後の分析や処理に複数回使用できることがわかります。
CTE は WITH 句を使用して定義され、CTE 名 cte_name、CTE を定義するクエリ ステートメント inner_query_production、および CTE を参照する外部クエリ ステートメント external_query_production の 3 つの部分で構成されます。
CTE可以在select , insert , update , delete , merge语句的执行范围定义。
その形式は次のとおりです。
WITH cte_name1[(column_name_list)] AS (inner_query_definition_1)
[,cte_name2[(column_name_list)] AS (inner_query_definition_2)]
[,...]
outer_query_definition
column_name_list
に列リスト名を指定しますinner_query_definition
。このオプションを記述しない場合は、 の列に名前があり、一意であることを確認する必要があります。inner_query_definition
つまり、列名には内部命名と外部命名という 2 つの命名方法があります。
注意してくださいouter_quer_definition必须和CTE定义语句同时执行,因为CTE是临时虚拟表,只有立即引用它,它的定义才是有意义的。
示例:
-- 单个子查询
with tmp as(select username,userage from user)
select username from tmp
-- 多个子查询 多个CTE 之间加,分割
with tmp1 as (select * from father),
tmp2 as (select * from child)
select * from temp1,temp2 on tmp1.id = tmp2.parentId
注:
1. 全体として SQL クエリとして使用する必要があります。つまり、with as ステートメントの後にセミコロンを追加することはできません。追加しないと、エラーが報告されます。
2. with 句は、参照される select ステートメントの前に定義する必要があります。兄弟の with キーワードは 1 回のみ使用でき、複数はカンマでのみ区切ることができます。最後の with 句と次のクエリの間にカンマを入れることはできません。 3. with 句が
定義されていても、その後に CTE を使用する SQL ステートメント (select、insert、update など) が続いていない場合、エラーが発生します。報告。
4. 前の with 句で定義したクエリは、後続の with 句で使用できます。5. with 句が
定義されているがクエリで使用されていない場合、ora-32035 エラーが報告されます: with 句で定義されたクエリ名は引用符で囲まれていません。(少なくとも 1 つの with クエリの名前は参照されていません。解決策は、参照されていない with クエリを削除することです。) 注: 後で参照がある限り、メイン クエリで参照する必要はありません。たとえば、次のようになります。以降のクエリも引用しても大丈夫です。
6. クエリ ブロック名がテーブル名または他のオブジェクトと同じ場合、パーサーはサブクエリ ブロック名を優先して内側から外側に検索します。
7. with クエリの結果列には別名があり、参照する場合は別名または * を使用する必要があります。
3. 利用シーン
3.1. CTE を定義し、各列の名前を変更する
mysql 8.0.34 バージョンで次の SQL をテストします。
CREATE TABLE user(
id INT NOT NULL PRIMARY KEY,
sex CHAR(3),NAME CHAR(20)
);
INSERT INTO user VALUES
(1,'nan','陈一'),
(2,'nv','珠二'),
(3,'nv','张三'),
(4,'nan','李四'),
(5,'nv','王五'),
(6,'nan','赵六');
# 定义CTE,顺便为每列重新命名,且使用ORDER BY子句
WITH nv_user(myid,mysex,myname) AS (
SELECT * FROM user WHERE sex='nv' ORDER BY id DESC
)
# 使用CTE
SELECT * FROM nv_user;
+------+-------+-------------+
| myid | mysex | myname |
+------+-------+-------------+
| 5 | nv | 王五 |
| 3 | nv | 张三 |
| 2 | nv | 珠二 |
+------+-------+-------------+
3.2. 複数の参照/複数の定義
1. 複数の引用: 繰り返しの書き込みを避けます。
2. 複数の定義: 派生テーブルの入れ子の問題を回避します。
3. 再帰的 CTE を使用して再帰的クエリを実装できます。
# 多次引用,避免重复书写
WITH nv_t(myid,mysex,myname) AS (
SELECT * FROM user WHERE sex='nv'
)
SELECT t1.*,t2.*
FROM nv_t t1 JOIN nv_t t2
WHERE t1.myid = t2.myid+1;
# 多次定义,避免派生表嵌套
WITH
nv_t1 AS ( /* 第一个CTE */
SELECT * FROM user WHERE sex='nv'
),
nv_t2 AS ( /* 第二个CTE */
SELECT * FROM nv_t1 WHERE id>3
)
SELECT * FROM nv_t2;
上記のステートメントが CTE を使用せず、派生テーブルを使用する場合、次と同等です。
SELECT * FROM
(SELECT * FROM
(SELECT * FROM user WHERE sex='nv') AS nv_t1) AS nv_t2;
この書き方では見にくいことがわかります。
3.3. with と Union を一緒に使用する
前の with 句で定義されたクエリは、後続の with 句で使用できます。
with
sql1 as (select s_name from test_tempa),
sql2 as (select s_name from test_tempb where not exists (select s_name from sql1 where rownum=1))
select * from sql1
union all
select * from sql2
union all
select ‘no records’ from dual
where not exists (select s_name from sql1 where rownum=1)
and not exists (select s_name from sql2 where rownum=1);
3.4. with は複数の結果の値を返します
実際の使用では、複数の結果を含む値を返す必要があるシナリオに遭遇する可能性があります。
-- 分类表
CREATE TABLE category ( cid VARCHAR ( 32 ) PRIMARY KEY, cname VARCHAR ( 50 ) );
-- 商品表
CREATE TABLE products (
pid VARCHAR ( 32 ) PRIMARY KEY,
pname VARCHAR ( 50 ),
price INT,
category_id VARCHAR ( 32 ),
FOREIGN KEY ( category_id ) REFERENCES category ( cid )
);
-- 分类数据
INSERT INTO category(cid,cname) VALUES('c001','家电');
INSERT INTO category(cid,cname) VALUES('c002','鞋服');
INSERT INTO category(cid,cname) VALUES('c003','化妆品');
INSERT INTO category(cid,cname) VALUES('c004','汽车');
-- 商品数据
INSERT INTO products(pid, pname,price,category_id) VALUES('p001','小米电视机',5000,'c001');
INSERT INTO products(pid, pname,price,category_id) VALUES('p002','格力空调',3000,'c001');
INSERT INTO products(pid, pname,price,category_id) VALUES('p003','美的冰箱',4500,'c001');
INSERT INTO products (pid, pname,price,category_id) VALUES('p004','篮球鞋',800,'c002');
INSERT INTO products (pid, pname,price,category_id) VALUES('p005','运动裤',200,'c002');
INSERT INTO products (pid, pname,price,category_id) VALUES('p006','T恤',300,'c002');
INSERT INTO products (pid, pname,price,category_id) VALUES('p007','冲锋衣',2000,'c002');
INSERT INTO products (pid, pname,price,category_id) VALUES('p008','神仙水',800,'c003');
INSERT INTO products (pid, pname,price,category_id) VALUES('p009','大宝',200,'c003');
上の図のように、「家電」内の「Gree エアコン」と「Midea 冷蔵庫」の情報をクエリしたい場合は、次のように記述する必要はありません。
select * from category c
left join products p on c.cid = p.category_id
where c.cname = '家电' and p.pname in ('格力空调','美的冰箱');
as とともに使用すると、次のように記述できます。
with c as (select * from category where cname = '家电'),
p as (select * from products where pname in ('格力空调','美的冰箱'))
select * from c,p where c.cid = p.category_id;
②.「家電」の平均価格と全商品の最小値と最大値を問い合わせる
with tem as (select avg(price) as houseElecAvg from products p
left join category c on c.cid = p.category_id
where c.cname = '家电'),
tem1 as (select max(p1.price),min(p1.price) from products p1)
select * from tem,tem1;
実際、WITH 式は、SELECT で使用するだけでなく、次の組み合わせも使用できます。
挿入、更新、削除、再帰 (数値、日付などのシーケンスをシミュレートできます)、WITH は定義できます。複数のテーブル
3.5. 使用および挿入
insert into table2
with
s1 as (select rownum c1 from dual connect by rownum <= 10),
s2 as (select rownum c2 from dual connect by rownum <= 10)
select a.c1, b.c2 from s1 a, s2 b where...;
4. 再帰的クエリ
ハイブ、Oracle、DB2、SQL SERVER、PostgreSQL などの標準データベースでは、すべて再帰クエリの WITH AS ステートメントがサポートされています。Mysql8.0 以降では再帰がサポートされています。
共通テーブル式 (CTE) には、それ自体を参照できるという重要な利点があり、それによって再帰的な CTE が作成されます。再帰 CTE は、完全な結果セットが取得されるまで初期 CTE を繰り返し実行してデータのサブセットを返す共通テーブル式です。
クエリが再帰 CTE を参照する場合、それは再帰クエリと呼ばれます。再帰クエリは、階層データを返すためによく使用されます。たとえば、組織図や部品表スキーム (親製品に 1 つ以上のコンポーネントがあり、それらのコンポーネントにサブコンポーネントや他の親がある場合) のデータに従業員を表示する場合などです。製品のコンポーネントに含まれます)。
4.1. 文法
再帰的 CTE には 1 つ以上のアンカー メンバー、1 つ以上の再帰的メンバーが含まれており、最後のアンカー メンバーは最初の再帰的メンバーに結合するために「union [all]」(mariadb の再帰的 CTE は Union [all] セット アルゴリズムのみをサポートします) を使用する必要があります。メンバー。
CTE 再帰に関するその他の構文の考慮事項については、「再帰共通テーブル式」を参照してください。
with recursive cte_name as (
select_statement_1 /* 该cte_body称为定位点成员 */
union [all]
cte_usage_statement /* 此处引用cte自身,称为递归成员 */
)
outer_definition_statement /* 对递归CTE的查询,称为递归查询 */
で:
- select_statement_1: 「アンカー メンバー」と呼ばれます。これは、実行される再帰的 cte の最初の部分であり、再帰が開始されるときの再帰的メンバーのデータ ソースでもあります。
- cte_usage_statement: 「再帰メンバー」と呼ばれ、このステートメントでは cte 自体を参照する必要があります。これは、再帰 CTE で再帰が実際に開始される場所です。最初にアンカー ポイント メンバーから再帰データ ソースを取得し、次にそれを他のデータ セットと組み合わせて再帰を開始します。各再帰は再帰結果を次の再帰アクションに渡します。クエリは繰り返し繰り返され、最終的にデータが見つからなくなると再帰は終了します。
- external_defining_statement: 再帰的 cte 用のクエリであり、このクエリを「再帰クエリ」と呼びます。
4.2. 使用シナリオ
4.2.1. with を使用して 1 ~ 10 のデータを再帰的に構築する
# n迭代次数
with RECURSIVE c(n) as
(select 1 union all select n + 1 from c where n < 10)
select n from c;
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.00 sec)
4.2.2、with および insert は再帰的にデータを作成します
次の例のように、WITH 式を使用してデータを作成するのは非常に簡単です。テーブル y1 に 10 個のレコードを追加します。日付フィールドはランダムである必要があります。
-- 创建测试表
create table y1 (id serial primary key, r1 int,log_date date);
-- 插入数据
INSERT y1 (r1,log_date)
WITH recursive tmp (a, b) AS
(SELECT
1,
'2021-04-20'
UNION
ALL
SELECT
ROUND(RAND() * 10),
b - INTERVAL ROUND(RAND() * 1000) DAY
FROM
tmp
LIMIT 10)
select * from tmp;
結果:
4.2.3. と update でデータを更新する
WITH recursive tmp (a, b, c) AS
(SELECT
1,
1,
'2021-04-20'
UNION ALL
SELECT
a + 2,
100,
DATE_SUB(
CURRENT_DATE(),
INTERVAL ROUND(RAND() * 1000, 0) DAY
)
FROM
tmp
WHERE a < 10)
UPDATE
tmp AS a,
y1 AS b
SET
b.r1 = a.b
WHERE a.a = b.id;
4.2.4、with と delete 奇数の行を削除します
たとえば、奇数の ID を持つ行を削除するには、削除ステートメントの WITH DELETE 形式を使用できます。
WITH recursive tmp (a) AS
(SELECT
1
UNION
ALL
SELECT
a + 2
FROM
tmp
WHERE a < 10)
DELETE FROM y1 WHERE id IN (select * from tmp);
DELETE と一緒に使用する場合、注意すべき点が 1 つあります。WITH 式自体のデータは読み取り専用であるため、WITH 式を複数テーブルの DELETE に含めることはできません。たとえば、上記のステートメントを複数テーブルの削除形式に変更すると、WITH 式を更新できないというエラーが直接報告されます。
WITH recursive tmp (a) AS
(SELECT
1
UNION
ALL
SELECT
a + 2
FROM
tmp
WHERE a < 100)
delete a,b from y1 a join tmp b where a.id = b.a;
error: [HY000][1288] The target table b of the DELETE is not updatable
4.2.5、生成された日付シーケンスを使用
POSTGRESQL のgenerate_series テーブル関数と同様に、WITH 式を使用して日付系列を生成します。たとえば、「2020-01-01」から開始して、1 か月分の日付系列を生成します。
WITH recursive seq_date (log_date) AS
(SELECT
'2023-07-09'
UNION
ALL
SELECT
log_date + INTERVAL 1 DAY
FROM
seq_date
WHERE log_date + INTERVAL 1 DAY < '2023-07-20')
SELECT
log_date
FROM
seq_date;
+-----------+
| log_date|
+-----------+
| 2023-07-09|
| 2023-07-10|
| 2023-07-11|
| 2023-07-12|
| 2023-07-13|
| 2023-07-14|
| 2023-07-15|
| 2023-07-16|
| 2023-07-17|
| 2023-07-18|
| 2023-07-19|
+------+
参考ドキュメント
https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/with.html#common-table-expressions
https://dev.mysql.com/doc/refman/8.0/en/with .html#common-table-expressions-recursive
https://blog.csdn.net/weixin_43194885/article/details/122199299?utm_medium=distribute.pc_relevant.none-task-blog-2デフォルトbaidujs_baidulandingword~default-1-122199299-blog-74002447.235 v38 pc_relevant_anti_t3_base& spm=1001.2101.3001.4242。 2&utm_relevant_index=4
https://www.jb51.net/article/236061.htm