Skip to content

Commit cf1c523

Browse files
committed
CTEs
1 parent ee6a100 commit cf1c523

File tree

3 files changed

+332
-0
lines changed

3 files changed

+332
-0
lines changed

MP COPY Subtrees.md

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
layout: default
3+
title: COPY Subtrees
4+
nav_order: 9
5+
parent: Materialized Paths
6+
permalink: /mat-paths/copy
7+
---
8+
9+
~~~sql
10+
WITH RECURSIVE
11+
json_ops(ops) AS (
12+
VALUES
13+
(json(
14+
'[' ||
15+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe00/", "path_new":"safe00/"},' ||
16+
'{"op":"move", "path_old":"safe00/", "path_new":"safe/"},' ||
17+
'{"op":"move", "path_old":"BAZ/dev/msys2", "path_new":"BAZ/dev/msys/"},' ||
18+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/preEEE/", "path_new":"preEEE/"},' ||
19+
'{"op":"move", "path_old":"safe/modules/", "path_new":"safe/modu/"},' ||
20+
'{"op":"move", "path_old":"safe/modu/mod2/", "path_new":"safe/modu/mod3/"},' ||
21+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/ssub00/", "path_new":"safe/ssub00/"},' ||
22+
'{"op":"move", "path_old":"BAZ/dev/msys/mingw32/", "path_new":"BAZ/dev/msys/nix/"},' ||
23+
'{"op":"move", "path_old":"safe/ssub00/modules/", "path_new":"safe/modules/"},' ||
24+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manYYY/", "path_new":"man000/"},' ||
25+
'{"op":"move", "path_old":"BAZ/dev/msys/nix/etc/", "path_new":"BAZ/dev/msys/nix/misc/"},' ||
26+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manZZZ/", "path_new":"BAZ/bld/tcl/tests/man000/"},' ||
27+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/man000/", "path_new":"man000/"},' ||
28+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe11/", "path_new":"safe11/"}' ||
29+
']'
30+
))
31+
),
32+
base_ops AS (
33+
SELECT
34+
"key" + 1 AS opid,
35+
json_extract(value, '$.op') AS op,
36+
trim(json_extract(value, '$.path_old'), '/') || '/' AS rootpath_old,
37+
trim(json_extract(value, '$.path_new'), '/') || '/' AS rootpath_new
38+
FROM json_ops AS jo, json_each(jo.ops) AS terms
39+
),
40+
subtrees_old AS (
41+
SELECT opid, ascii_id, path_old
42+
FROM base_ops, categories
43+
WHERE path like rootpath_old || '%'
44+
ORDER BY opid, path
45+
),
46+
LOOP_COPY AS (
47+
SELECT 0 AS opid, ascii_id, path_old AS path_new
48+
FROM subtrees_old
49+
UNION ALL
50+
SELECT ops.opid, ascii_id, path_new
51+
FROM LOOP_COPY AS BUFFER, base_ops AS ops
52+
WHERE ops.opid = BUFFER.opid + 1
53+
UNION ALL
54+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
55+
replace(path_new, rootpath_old, rootpath_new) AS path_new
56+
FROM LOOP_COPY AS BUFFER, base_ops AS ops
57+
WHERE ops.opid = BUFFER.opid + 1
58+
AND BUFFER.path_new like rootpath_old || '%'
59+
),
60+
subtrees_new_base AS (
61+
SELECT ascii_id, path_new
62+
FROM LOOP_COPY
63+
WHERE opid = (SELECT max(opid) FROM base_ops)
64+
AND length(ascii_id) > 8
65+
GROUP BY ascii_id, path_new
66+
ORDER BY path_new
67+
),
68+
subtrees_path AS (
69+
SELECT path_new FROM subtrees_new_base GROUP BY path_new
70+
),
71+
subtrees_new AS (
72+
SELECT
73+
json_extract('["' || replace(trim(path_new, '/'), '/', '", "') || '"]', '$[#-1]') AS name_new,
74+
path_new
75+
FROM subtrees_path LEFT JOIN categories AS cats ON path_new = path
76+
WHERE ascii_id IS NULL
77+
),
78+
new_paths AS (
79+
SELECT row_number() OVER (ORDER BY path_new) - 1 AS counter,
80+
substr(path_new, 1, length(path_new) - length(name_new) - 1) AS prefix_new,
81+
subtrees_new.*
82+
FROM subtrees_new
83+
),
84+
id_counts(id_counter) AS (SELECT count(*) FROM new_paths),
85+
json_templates AS (SELECT '[' || replace(hex(zeroblob(id_counter*8/2-1)), '0', '0,') || '0,0]' AS json_template FROM id_counts),
86+
char_templates(char_template) AS (VALUES ('-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_')),
87+
ascii_ids AS (
88+
SELECT group_concat(substr(char_template, (random() & 63) + 1, 1), '') AS ascii_id, "key"/8 AS counter
89+
FROM char_templates, json_templates, json_each(json_templates.json_template) AS terms
90+
GROUP BY counter
91+
),
92+
ids AS (
93+
SELECT counter, ascii_id,
94+
(unicode(substr(ascii_id, 1, 1)) << 8*7) +
95+
(unicode(substr(ascii_id, 2, 1)) << 8*6) +
96+
(unicode(substr(ascii_id, 3, 1)) << 8*5) +
97+
(unicode(substr(ascii_id, 4, 1)) << 8*4) +
98+
(unicode(substr(ascii_id, 5, 1)) << 8*3) +
99+
(unicode(substr(ascii_id, 6, 1)) << 8*2) +
100+
(unicode(substr(ascii_id, 7, 1)) << 8*1) +
101+
(unicode(substr(ascii_id, 8, 1)) << 8*0) AS bin_id
102+
FROM ascii_ids
103+
),
104+
new_nodes AS (SELECT bin_id AS id, name_new AS name, prefix_new AS prefix FROM new_paths, ids USING (counter))
105+
INSERT INTO categories (id, name, prefix)
106+
SELECT * FROM new_nodes;
107+
~~~

MP MOVE Subtrees.md

+98
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,101 @@ nav_order: 8
55
parent: Materialized Paths
66
permalink: /mat-paths/move
77
---
8+
9+
~~~sql
10+
CREATE TEMP TABLE IF NOT EXISTS move_targets (
11+
ascii_id, path_old, path_new, prefix_new, name_new, target_exists
12+
);
13+
DELETE FROM temp.move_targets;
14+
15+
WITH RECURSIVE
16+
json_ops(ops) AS (
17+
VALUES
18+
(json(
19+
'[' ||
20+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe00/", "path_new":"safe00/"},' ||
21+
'{"op":"move", "path_old":"safe00/", "path_new":"safe/"},' ||
22+
'{"op":"move", "path_old":"BAZ/dev/msys2", "path_new":"BAZ/dev/msys/"},' ||
23+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/preEEE/", "path_new":"preEEE/"},' ||
24+
'{"op":"move", "path_old":"safe/modules/", "path_new":"safe/modu/"},' ||
25+
'{"op":"move", "path_old":"safe/modu/mod2/", "path_new":"safe/modu/mod3/"},' ||
26+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/ssub00/", "path_new":"safe/ssub00/"},' ||
27+
'{"op":"move", "path_old":"BAZ/dev/msys/mingw32/", "path_new":"BAZ/dev/msys/nix/"},' ||
28+
'{"op":"move", "path_old":"safe/ssub00/modules/", "path_new":"safe/modules/"},' ||
29+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manYYY/", "path_new":"man000/"},' ||
30+
'{"op":"move", "path_old":"BAZ/dev/msys/nix/etc/", "path_new":"BAZ/dev/msys/nix/misc/"},' ||
31+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manZZZ/", "path_new":"BAZ/bld/tcl/tests/man000/"},' ||
32+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/man000/", "path_new":"man000/"},' ||
33+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe11/", "path_new":"safe11/"}' ||
34+
']'
35+
))
36+
),
37+
base_ops AS (
38+
SELECT
39+
"key" + 1 AS opid,
40+
json_extract(value, '$.op') AS op,
41+
trim(json_extract(value, '$.path_old'), '/') || '/' AS rootpath_old,
42+
trim(json_extract(value, '$.path_new'), '/') || '/' AS rootpath_new
43+
FROM json_ops AS jo, json_each(jo.ops) AS terms
44+
),
45+
subtrees_old AS (
46+
SELECT opid, ascii_id, path AS path_old
47+
FROM base_ops, categories
48+
WHERE path like rootpath_old || '%'
49+
ORDER BY opid, path_old
50+
),
51+
LOOP_MOVE AS (
52+
SELECT 0 AS opid, ascii_id, path_old AS path_new
53+
FROM subtrees_old
54+
UNION ALL
55+
SELECT ops.opid, ascii_id,
56+
replace(path_new, rootpath_old, rootpath_new) AS path_new
57+
FROM LOOP_MOVE AS BUFFER, base_ops AS ops
58+
WHERE ops.opid = BUFFER.opid + 1
59+
),
60+
subtrees_new_base AS (
61+
SELECT
62+
ascii_id, path_new,
63+
json_extract('["' || replace(trim(path_new, '/'), '/', '", "') || '"]', '$[#-1]') AS name_new
64+
FROM LOOP_MOVE
65+
WHERE opid = (SELECT max(base_ops.opid) FROM base_ops)
66+
),
67+
subtrees_path AS (
68+
SELECT
69+
trnew.ascii_id, path_old, path_new,
70+
substr(path_new, 1, length(path_new) - length(name_new) - 1) AS prefix_new,
71+
name_new
72+
FROM subtrees_new_base AS trnew, subtrees_old AS trold
73+
WHERE trnew.ascii_id = trold.ascii_id
74+
AND path_old <> path_new
75+
),
76+
new_paths AS (
77+
SELECT
78+
subtrees_path.*,
79+
(cats.ascii_id IS NOT NULL) + (row_number() OVER (PARTITION BY path_new) - 1) AS target_exists
80+
FROM subtrees_path LEFT JOIN categories AS cats ON path_new = path
81+
)
82+
INSERT INTO temp.move_targets (ascii_id, path_old, path_new, prefix_new, name_new, target_exists)
83+
SELECT * FROM new_paths ORDER BY target_exists, path_old;
84+
85+
86+
PRAGMA defer_foreign_keys = 1;
87+
SAVEPOINT "MOVE_CATS";
88+
89+
UPDATE categories SET (name, prefix) = (name_new, prefix_new)
90+
FROM temp.move_targets AS mvt
91+
WHERE mvt.path_old = categories.path AND mvt.target_exists = 0;
92+
93+
UPDATE files_categories SET cat_id = path_new
94+
FROM temp.move_targets AS mvt
95+
WHERE mvt.path_old = cat_id;
96+
97+
DELETE FROM categories
98+
WHERE path IN (
99+
SELECT path_old
100+
FROM temp.move_targets AS mvt
101+
WHERE mvt.target_exists = 1
102+
);
103+
104+
RELEASE "MOVE_CATS";
105+
~~~

Patterns Recursive CTEs.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
layout: default
3+
title: Recursive CTEs
4+
nav_order: 8
5+
parent: Design Patterns
6+
permalink: /patterns/rec-cte
7+
---
8+
9+
~~~sql
10+
WITH RECURSIVE
11+
json_ops(ops) AS (
12+
VALUES
13+
(json(
14+
'[' ||
15+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe00/", "path_new":"safe00/"},' ||
16+
'{"op":"move", "path_old":"safe00/", "path_new":"safe/"},' ||
17+
'{"op":"move", "path_old":"BAZ/dev/msys2", "path_new":"BAZ/dev/msys/"},' ||
18+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/preEEE/", "path_new":"preEEE/"},' ||
19+
'{"op":"move", "path_old":"safe/modules/", "path_new":"safe/modu/"},' ||
20+
'{"op":"move", "path_old":"safe/modu/mod2/", "path_new":"safe/modu/mod3/"},' ||
21+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/ssub00/", "path_new":"safe/ssub00/"},' ||
22+
'{"op":"move", "path_old":"BAZ/dev/msys/mingw32/", "path_new":"BAZ/dev/msys/nix/"},' ||
23+
'{"op":"move", "path_old":"safe/ssub00/modules/", "path_new":"safe/modules/"},' ||
24+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manYYY/", "path_new":"man000/"},' ||
25+
'{"op":"move", "path_old":"BAZ/dev/msys/nix/etc/", "path_new":"BAZ/dev/msys/nix/misc/"},' ||
26+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/manZZZ/", "path_new":"BAZ/bld/tcl/tests/man000/"},' ||
27+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/man000/", "path_new":"man000/"},' ||
28+
'{"op":"move", "path_old":"BAZ/bld/tcl/tests/safe11/", "path_new":"safe11/"}' ||
29+
']'
30+
))
31+
),
32+
base_ops AS (
33+
SELECT
34+
"key" + 1 AS opid,
35+
json_extract(value, '$.op') AS op,
36+
trim(json_extract(value, '$.path_old'), '/') || '/' AS rootpath_old,
37+
trim(json_extract(value, '$.path_new'), '/') || '/' AS rootpath_new
38+
FROM json_ops AS jo, json_each(jo.ops) AS terms
39+
),
40+
subtrees_old AS (
41+
SELECT opid, ascii_id, path AS path_old
42+
FROM base_ops, categories
43+
WHERE path like rootpath_old || '%'
44+
ORDER BY opid, path
45+
),
46+
LOOP_COPY AS (
47+
SELECT 0 AS opid, ascii_id, path_old AS path_new
48+
FROM subtrees_old
49+
UNION ALL
50+
SELECT ops.opid, ascii_id, path_new
51+
FROM LOOP_COPY AS BUFFER, base_ops AS ops
52+
WHERE ops.opid = BUFFER.opid + 1
53+
UNION ALL
54+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
55+
replace(path_new, rootpath_old, rootpath_new) AS path_new
56+
FROM LOOP_COPY AS BUFFER, base_ops AS ops
57+
WHERE ops.opid = BUFFER.opid + 1
58+
AND BUFFER.path_new like rootpath_old || '%'
59+
),
60+
61+
-------------------------------------------------------------------------------
62+
LOOP_COPY_INIT AS (
63+
SELECT 0 AS opid, ascii_id, path_old AS path_new
64+
FROM subtrees_old
65+
),
66+
LOOP_COPY_STEP_1 AS (
67+
SELECT ops.opid, ascii_id, path_new
68+
FROM LOOP_COPY_INIT AS BUFFER, base_ops AS ops
69+
WHERE ops.opid = BUFFER.opid + 1
70+
UNION ALL
71+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
72+
replace(path_new, rootpath_old, rootpath_new) AS path_new
73+
FROM LOOP_COPY_INIT AS BUFFER, base_ops AS ops
74+
WHERE ops.opid = BUFFER.opid + 1
75+
AND BUFFER.path_new like rootpath_old || '%'
76+
),
77+
LOOP_COPY_STEP_2 AS (
78+
SELECT ops.opid, ascii_id, path_new
79+
FROM LOOP_COPY_STEP_1 AS BUFFER, base_ops AS ops
80+
WHERE ops.opid = BUFFER.opid + 1
81+
UNION ALL
82+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
83+
replace(path_new, rootpath_old, rootpath_new) AS path_new
84+
FROM LOOP_COPY_STEP_1 AS BUFFER, base_ops AS ops
85+
WHERE ops.opid = BUFFER.opid + 1
86+
AND BUFFER.path_new like rootpath_old || '%'
87+
),
88+
LOOP_COPY_STEP_3 AS (
89+
SELECT ops.opid, ascii_id, path_new
90+
FROM LOOP_COPY_STEP_2 AS BUFFER, base_ops AS ops
91+
WHERE ops.opid = BUFFER.opid + 1
92+
UNION ALL
93+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
94+
replace(path_new, rootpath_old, rootpath_new) AS path_new
95+
FROM LOOP_COPY_STEP_2 AS BUFFER, base_ops AS ops
96+
WHERE ops.opid = BUFFER.opid + 1
97+
AND BUFFER.path_new like rootpath_old || '%'
98+
),
99+
LOOP_COPY_STEP_STOP AS (
100+
SELECT ops.opid, ascii_id, path_new
101+
FROM LOOP_COPY_STEP_3 AS BUFFER, base_ops AS ops
102+
WHERE ops.opid = (SELECT max(base_ops.opid) + 1 FROM base_ops)
103+
UNION ALL
104+
SELECT ops.opid, '~' || ascii_id AS ascii_id,
105+
replace(path_new, rootpath_old, rootpath_new) AS path_new
106+
FROM LOOP_COPY_STEP_3 AS BUFFER, base_ops AS ops
107+
WHERE ops.opid = (SELECT max(base_ops.opid) + 1 FROM base_ops)
108+
AND BUFFER.path_new like rootpath_old || '%'
109+
),
110+
-------------------------------------------------------------------------------
111+
112+
subtrees_new_base AS (
113+
SELECT ascii_id, path_new
114+
FROM LOOP_COPY
115+
WHERE opid = (SELECT max(opid) FROM base_ops)
116+
AND length(ascii_id) > 8
117+
GROUP BY ascii_id, path_new
118+
ORDER BY path_new
119+
)
120+
121+
-- SELECT * FROM subtrees_new_base;
122+
-- SELECT * FROM LOOP_COPY_INIT;
123+
-- SELECT * FROM LOOP_COPY_STEP_1;
124+
-- SELECT * FROM LOOP_COPY_STEP_2;
125+
-- SELECT * FROM LOOP_COPY_STEP_3;
126+
SELECT * FROM LOOP_COPY_STEP_STOP;
127+
~~~

0 commit comments

Comments
 (0)