Programming the SQL Way With Common Table Expressions 1668063853
Programming the SQL Way With Common Table Expressions 1668063853
BRUCE MOMJIAN
1 / 96
Outline
2 / 96
1. Imperative vs. Declarative
https://www.flickr.com/photos/visit_cape_may/
3 / 96
Imperative Programming Languages
http://en.wikipedia.org/wiki/Imperative_programming
4 / 96
Declarative Programming Languages
The term is used in opposition to declarative programming, which expresses what the
program should accomplish without prescribing how to do it in terms of sequence.
5 / 96
Imperative
BASIC:
10 PRINT "Hello";
20 GOTO 10
C:
while (1)
printf("Hello\n");
Perl:
print("Hello\n") while (1);
6 / 96
Declarative
SQL:
SELECT ’Hello’
UNION ALL
SELECT ’Hello’
UNION ALL
SELECT ’Hello’
UNION ALL
SELECT ’Hello’
…
An infinite loop is not easily implemented in simple SQL.
7 / 96
Imperative Database Options
8 / 96
2. Syntax
https://www.flickr.com/photos/kylewhitney/
9 / 96
Common Table Expression (CTE) Syntax
10 / 96
Keep Your Eye on the Red (Text)
https://www.flickr.com/photos/alltheaces/
11 / 96
A Simple CTE
WITH source AS (
SELECT 1
)
SELECT * FROM source;
?column?
----------
1
The CTE created a source table that was referenced by the outer SELECT.
WITH source AS (
SELECT 1 AS col1
)
SELECT * FROM source;
col1
------
1
The CTE returned column is source.col1.
13 / 96
The Column Can Also Be Named in the WITH Clause
14 / 96
Columns Can Be Renamed
15 / 96
Multiple CTE Columns Can Be Returned
WITH source AS (
SELECT 1, 2
)
SELECT * FROM source;
?column? | ?column?
----------+----------
1 | 2
16 / 96
UNION Refresher
SELECT 1
UNION
SELECT 1;
?column?
----------
1
SELECT 1
UNION ALL
SELECT 1;
?column?
----------
1
1
17 / 96
Possible To Create Multiple CTE Results
WITH source AS (
SELECT 1, 2
),
source2 AS (
SELECT 3, 4
)
SELECT * FROM source
UNION ALL
SELECT * FROM source2;
?column? | ?column?
----------+----------
1 | 2
3 | 4
18 / 96
CTE with Real Tables
WITH source AS (
SELECT lanname, rolname
FROM pg_language JOIN pg_roles ON lanowner = pg_roles.oid
)
SELECT * FROM source;
lanname | rolname
----------+----------
internal | postgres
c | postgres
sql | postgres
plpgsql | postgres
19 / 96
CTE Can Be Processed More than Once
WITH source AS (
SELECT lanname, rolname
FROM pg_language JOIN pg_roles ON lanowner = pg_roles.oid
ORDER BY lanname
)
SELECT * FROM source
UNION ALL
SELECT MIN(lanname), NULL
FROM source;
lanname | rolname
----------+----------
c | postgres
internal | postgres
plpgsql | postgres
sql | postgres
c |
20 / 96
CTE Can Be Joined
WITH class AS (
SELECT oid, relname
FROM pg_class
WHERE relkind = ’r’
)
SELECT class.relname, attname
FROM pg_attribute, class
WHERE class.oid = attrelid
ORDER BY 1, 2
LIMIT 5;
relname | attname
--------------+--------------
pg_aggregate | aggfinalfn
pg_aggregate | aggfnoid
pg_aggregate | agginitval
pg_aggregate | aggsortop
pg_aggregate | aggtransfn
21 / 96
Imperative Control With CASE
CASE
WHEN condition THEN result
ELSE result
END
For example:
SELECT col,
CASE
WHEN col > 0 THEN ’positive’
WHEN col = 0 THEN ’zero’
ELSE ’negative’
END
FROM tab;
22 / 96
3. Recursive CTEs
https://www.flickr.com/photos/rbh/
23 / 96
Looping
24 / 96
This Is an Infinite Loop
25 / 96
Flow Of Rows
RESET statement_timeout;
27 / 96
UNION without ALL Avoids Recursion
28 / 96
CTEs Are Useful When Loops Are Constrained
29 / 96
Output
counter
---------
1
2
3
4
5
6
7
8
9
10
Of course, this can be more easily accomplished using generate_series(1, 10).
30 / 96
Perl Example
31 / 96
Perl Using Recursion
sub f
{
my $arg = shift;
print "$arg\n";
f($arg + 1) if ($arg < 10);
}
f(1);
32 / 96
Perl Recursion Using an Array
my @table;
sub f
{
my $arg = shift // 1;
push @table, $arg;
f($arg + 1) if ($arg < 10);
}
f();
map {print "$_\n"} @table;
This is the most accurate representation of CTEs because it accumultes results in an array (similar
to a table result).
33 / 96
4. Examples
https://www.flickr.com/photos/82134796@N03/
34 / 96
Ten Factorial Using CTE
35 / 96
Output
counter | product
---------+---------
1 | 1
2 | 2
3 | 6
4 | 24
5 | 120
6 | 720
7 | 5040
8 | 40320
9 | 362880
10 | 3628800
36 / 96
Only Display the Desired Row
37 / 96
Ten Factorial in Perl
my @table;
sub f
{
my ($counter, $product) = @_;
my ($counter_new, $product_new);
if (!defined($counter)) {
$counter_new = 1;
$product_new = 1;
} else {
$counter_new = $counter + 1;
$product_new = $product * ($counter + 1);
}
push(@table, [$counter_new, $product_new]);
f($counter_new, $product_new) if ($counter < 10);
}
f();
map {print "@$_\n" if ($_->[0]) == 10} @table;
38 / 96
String Manipulation Is Also Possible
39 / 96
Output
str
------------
a
aa
aaa
aaaa
aaaaa
aaaaaa
aaaaaaa
aaaaaaaa
aaaaaaaaa
aaaaaaaaaa
40 / 96
Characters Can Be Computed
41 / 96
Output
str
------------
a
ab
abc
abcd
abcde
abcdef
abcdefg
abcdefgh
abcdefghi
abcdefghij
42 / 96
ASCII Art Is Even Possible
43 / 96
Output
?column?
--------------
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
XX
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
44 / 96
How Is that Done?
45 / 96
Output
counter | ?column?
---------+--------------
-10 | X X
-9 | X X
-8 | X X
-7 | X X
-6 | X X
-5 | X X
-4 | X X
-3 | X X
-2 | X X
-1 | X X
0 | XX
1 | X X
2 | X X
3 | X X
4 | X X
5 | X X
6 | X X
7 | X X
8 | X X
9 | X X
10 | X X
46 / 96
ASCII Diamonds Are Even Possible
47 / 96
A Diamond
?column?
--------------
XX
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
XX
48 / 96
More Rounded
49 / 96
An Oval
?column?
------------------------
XX
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
XX
50 / 96
A Real Circle
51 / 96
Output
?column?
--------------------------------------------
XX
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
X X
XX
52 / 96
Prime Factors
The prime factors of X are the prime numbers that must be multiplied to equal a X, e.g.:
10 = 2 * 5
27 = 3 * 3 * 3
48 = 2 * 2 * 2 * 2 * 3
66 = 2 * 3 * 11
70 = 2 * 5 * 7
100 = 2 * 2 * 5 * 5
53 / 96
Prime Factorization in SQL
54 / 96
Output
55 / 96
Only Return Prime Factors
56 / 96
Output
57 / 96
Factors of 322434
58 / 96
Output
59 / 96
Prime Factors of 66
60 / 96
Inefficient
61 / 96
Skip Evens >2, Exit Early with a Final Prime
WITH RECURSIVE source (counter, factor, is_factor) AS (
SELECT 2, 66, false
UNION ALL
SELECT CASE
WHEN factor % counter = 0 THEN counter
-- is ’factor’ prime?
WHEN counter * counter > factor THEN factor
-- now only odd numbers
WHEN counter = 2 THEN 3
ELSE counter + 2
END,
CASE
WHEN factor % counter = 0 THEN factor / counter
ELSE factor
END,
CASE
WHEN factor % counter = 0 THEN true
ELSE false
END
FROM source
WHERE factor <> 1
)
SELECT * FROM source;
62 / 96
Output
63 / 96
Return Only Prime Factors
WITH RECURSIVE source (counter, factor, is_factor) AS (
SELECT 2,66, false
UNION ALL
SELECT CASE
WHEN factor % counter = 0 THEN counter
-- is ’factor’ prime?
WHEN counter * counter > factor THEN factor
-- now only odd numbers
WHEN counter = 2 THEN 3
ELSE counter + 2
END,
CASE
WHEN factor % counter = 0 THEN factor / counter
ELSE factor
END,
CASE
WHEN factor % counter = 0 THEN true
ELSE false
END
FROM source
WHERE factor <> 1
)
SELECT * FROM source WHERE is_factor;
64 / 96
Output
65 / 96
Optimized Prime Factors of 66 in Perl
my @table;
sub f
{
my ($counter, $factor, $is_factor) = @_;
my ($counter_new, $factor_new, $is_factor_new);
if (!defined($counter)) {
$counter_new = 2;
$factor_new = 66;
$is_factor_new = 0;
} else {
$counter_new = ($factor % $counter == 0) ?
$counter :
($counter * $counter > $factor) ?
$factor :
($counter == 2) ?
3 :
$counter + 2;
$factor_new = ($factor % $counter == 0) ?
$factor / $counter :
$factor;
$is_factor_new = ($factor % $counter == 0);
}
push(@table, [$counter_new, $factor_new, $is_factor_new]);
f($counter_new, $factor_new) if ($factor != 1);
}
f();
map {print "$_->[0] $_->[1] $_->[2]\n" if ($_->[2]) == 1} @table;
66 / 96
Recursive Table Processing: Setup
67 / 96
Use CTEs To Walk Through Parts Heirarchy
Using UNION without ALL here would avoid infinite recursion if there is a loop in the
data, but it would also cause a part with multiple parents to appear only once.
68 / 96
Add Dashes
69 / 96
The Parts in ASCII Order
70 / 96
The Parts in Numeric Order
71 / 96
Full Output
72 / 96
CTE for SQL Object Dependency
73 / 96
CTE for SQL Object Dependency
74 / 96
Output
75 / 96
Do Not Show deptest
76 / 96
Output
77 / 96
Add a Primary Key
78 / 96
Output With Primary Key
79 / 96
Output
80 / 96
Add a SERIAL Column
81 / 96
Output with SERIAL Column
82 / 96
Output
83 / 96
Show Full Output
84 / 96
Output
85 / 96
5. Writable CTEs
https://www.flickr.com/photos/dmelchordiaz//
86 / 96
Writable CTEs
87 / 96
Use INSERT, UPDATE, DELETE in WITH Clauses
WITH source AS (
INSERT INTO retdemo
VALUES (random()), (random()), (random()) RETURNING x
)
SELECT AVG(x) FROM source;
avg
---------------------
0.46403147140517833
88 / 96
Use INSERT, UPDATE, DELETE in WITH Clauses
WITH source AS (
DELETE FROM retdemo RETURNING x
)
SELECT MAX(x) FROM source;
max
---------------------
0.93468171451240821
89 / 96
Supply Rows to INSERT, UPDATE, DELETE Using WITH Clauses
90 / 96
Recursive WITH to Delete Parts
91 / 96
Using Both Features
92 / 96
Chaining Modification Commands
CREATE TEMPORARY TABLE items (order_id INTEGER, part_id SERIAL, name text);
93 / 96
Mixing Modification Commands
94 / 96
6. Why Use CTEs
95 / 96
Conclusion
https://momjian.us/presentations https://www.flickr.com/photos/theophilusphotography/
96 / 96