Neues inOpen-Source-SQL-Datenbanken
@MarkusWinand • @ModernSQL
http://www.almaden.ibm.com/cs/people/chamberlin/sequel-1974.pdf
Neues inOpen-Source-SQL-Datenbanken
@MarkusWinand • @ModernSQL
http://www.almaden.ibm.com/cs/people/chamberlin/sequel-1974.pdf
SQL-92
CHECKConstraints
CHECK Constraints Since SQL-92CREATETABLEorder_lines(…qtyINTEGERNOTNULLCHECK(qty>0),…)
CHECK Constraints Since SQL-92CREATETABLEorder_lines(…qtyINTEGERNOTNULLCHECK(qty>0),…)
INSERT…(…,qty,…)VALUES(…,1,…)
CHECK Constraints Since SQL-92CREATETABLEorder_lines(…qtyINTEGERNOTNULLCHECK(qty>0),…)
INSERT…(…,qty,…)VALUES(…,1,…)INSERT…(…,qty,…)VALUES(…,3,…)
CHECK Constraints Since SQL-92CREATETABLEorder_lines(…qtyINTEGERNOTNULLCHECK(qty>0),…)
INSERT…(…,qty,…)VALUES(…,1,…)INSERT…(…,qty,…)VALUES(…,3,…)INSERT…(…,qty,…)VALUES(…,0,…)
CHECK Constraints Since SQL-92CREATETABLEorder_lines(…qtyINTEGERNOTNULLCHECK(qty>0),…)
INSERT…(…,qty,…)VALUES(…,1,…)INSERT…(…,qty,…)VALUES(…,3,…)INSERT…(…,qty,…)VALUES(…,0,…)
Before MySQL 8.0.16and MariaDB 10.2:
Syntax accepted,Constraint ignored
CHECK Constraints Since SQL-921999
2001
2003
2005
2007
2009
2011
2013
2015
2017
10.2 MariaDB8.0.16 MySQL
8.3 PostgreSQL3.5.7 SQLite
9.7 DB2 LUW11gR1 Oracle
2008R2 SQL Server
INTERSECT and EXCEPT
Since SQL-92INTERSECT & EXCEPT
UNION[ALL]
Concatenatestwo results
Since SQL-92INTERSECT & EXCEPT
UNION[ALL]
Concatenatestwo results
INTERSECT[ALL]
Common rows from two results
Since SQL-92INTERSECT & EXCEPT
UNION[ALL]
Concatenatestwo results
INTERSECT[ALL]
Common rows from two results
EXCEPT[ALL]
Remove rowsfrom first result
INTERSECT & EXCEPT Since SQL-921999
2001
2003
2005
2007
2009
2011
2013
2015
2017
10.3[0] MariaDBMySQL
8.3 PostgreSQL3.5.7[0] SQLite
9.7 DB2 LUW11gR1[0] Oracle
2008R2[0] SQL Server[0]Not [all]
SQL:1999
LATERAL
Select-list sub-queries must be scalar[0]:
LATERAL Before SQL:1999
SELECT…,(SELECTcolumn_1FROMt1WHEREt1.x=t2.y)AScFROMt2…
(an atomic quantity that can hold only one value at a time[1])
[0] Neglecting row values and other workarounds here; [1] https://en.wikipedia.org/wiki/Scalar
Select-list sub-queries must be scalar[0]:
LATERAL Before SQL:1999
SELECT…,(SELECTcolumn_1FROMt1WHEREt1.x=t2.y)AScFROMt2…
(an atomic quantity that can hold only one value at a time[1])
[0] Neglecting row values and other workarounds here; [1] https://en.wikipedia.org/wiki/Scalar
✗,column_2
More thanone column? ⇒Syntax error
Select-list sub-queries must be scalar[0]:
LATERAL Before SQL:1999
SELECT…,(SELECTcolumn_1FROMt1WHEREt1.x=t2.y)AScFROMt2…
(an atomic quantity that can hold only one value at a time[1])
[0] Neglecting row values and other workarounds here; [1] https://en.wikipedia.org/wiki/Scalar
✗,column_2
More thanone column? ⇒Syntax error
}More thanone row?
⇒Runtime error!
SELECT*FROMt1CROSSJOINLATERAL(SELECT*FROMt2WHEREt2.x=t1.x)derived_tableON(true)
LATERAL Since SQL:1999Lateral derived queries can see table names defined before:
SELECT*FROMt1CROSSJOINLATERAL(SELECT*FROMt2WHEREt2.x=t1.x)derived_tableON(true)
LATERAL Since SQL:1999
Valid due to
LATERAL
keyword
Lateral derived queries can see table names defined before:
SELECT*FROMt1CROSSJOINLATERAL(SELECT*FROMt2WHEREt2.x=t1.x)derived_tableON(true)
LATERAL Since SQL:1999
Valid due to
LATERAL
keyword
Useless, but still required
Lateral derived queries can see table names defined before:
SELECT*FROMt1CROSSJOINLATERAL(SELECT*FROMt2WHEREt2.x=t1.x)derived_tableON(true)
LATERAL Since SQL:1999
Valid due to
LATERAL
keyword
Lateral derived queries can see table names defined before:
Use CROSS JOIN to omit the ON clause
But WHY?
Use-CasesLATERAL
‣ Top-N per group
inside a lateral derived tableFETCHFIRST (or LIMIT, TOP)applies per row from left tables.
Use-CasesLATERAL
FROMtJOINLATERAL(SELECT…FROM…WHEREt.c=…ORDERBY…LIMIT10)derived_table
‣ Top-N per group
inside a lateral derived tableFETCHFIRST (or LIMIT, TOP)applies per row from left tables.
Use-CasesLATERAL
FROMtJOINLATERAL(SELECT…FROM…WHEREt.c=…ORDERBY…LIMIT10)derived_table
‣ Top-N per group
inside a lateral derived tableFETCHFIRST (or LIMIT, TOP)applies per row from left tables.
Use-CasesLATERAL
Add proper indexfor Top-N query
http://use-the-index-luke.com/sql/partial-results/top-n-queries
FROMtJOINLATERAL(SELECT…FROM…WHEREt.c=…ORDERBY…LIMIT10)derived_table
‣ Top-N per group
inside a lateral derived tableFETCHFIRST (or LIMIT, TOP)applies per row from left tables.
‣ Also useful to find most recent news from several subscribed topics (“multi-source top-N”).
Use-CasesLATERAL
Add proper indexfor Top-N query
http://use-the-index-luke.com/sql/partial-results/top-n-queries
FROMtJOINLATERAL(SELECT…FROM…WHEREt.c=…ORDERBY…LIMIT10)derived_table
‣ Top-N per group
inside a lateral derived tableFETCHFIRST (or LIMIT, TOP)applies per row from left tables.
‣ Also useful to find most recent news from several subscribed topics (“multi-source top-N”).
‣ Table function arguments
(TABLE often implies LATERAL)
Use-CasesLATERAL
FROMtJOINTABLE(your_func(t.c))
LATERAL is the "for each" loop of SQL
LATERAL plays well with outer and cross joins
LATERAL is great for Top-N subqueries
LATERAL can join table functions (unnest!)
LATERAL In a Nutshell
LATERAL Availability1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDB8.0.14 MySQL
9.3 PostgreSQLSQLite
9.1 DB2 LUW11gR1[0] 12cR1 Oracle
2005[1] SQL Server[0]Undocumented. Requires setting trace event 22829.[1]LATERAL is not supported as of SQL Server 2016 but [CROSS|OUTER]APPLY can be used for the same effect.
SQL:2003
BOOLEANTests
Before we start
SQL uses a three-valued logic. Boolean values are either
true, false or unknown(=null).
See: http://modern-sql.com/concept/three-valued-logic
BOOLEANAggregates
BOOLEANTests
Similar to isnull, there are tests for each Boolean value(of which there are three: true, false, unknown/null)
IS[NOT][TRUE|FALSE|UNKNOWN]
Since SQL:2003
CREATETABLEprices(…valid_fromDATENOTNULL,valid_toDATE,--null:openend…CHECK(valid_from<valid_to),);
WHEREvalid_from<CURRENT_DATEAND(valid_to<=CURRENT_DATE)ISNOTFALSE
BOOLEANTests Since SQL:2003
CREATETABLEprices(…valid_fromDATENOTNULL,valid_toDATE,--null:openend…CHECK(valid_from<valid_to),);
WHEREvalid_from<CURRENT_DATEAND(valid_to<=CURRENT_DATE)ISNOTFALSE
BOOLEANTests Since SQL:2003
CREATETABLEprices(…valid_fromDATENOTNULL,valid_toDATE,--null:openend…CHECK(valid_from<valid_to),);
WHEREvalid_from<CURRENT_DATEAND(valid_to<=CURRENT_DATE)ISNOTFALSE
BOOLEANTests Since SQL:2003
UNKNOWN ifVALID_TO is NULL
CREATETABLEprices(…valid_fromDATENOTNULL,valid_toDATE,--null:openend…CHECK(valid_from<valid_to),);
WHEREvalid_from<CURRENT_DATEAND(valid_to<=CURRENT_DATE)ISNOTFALSE
BOOLEANTests Since SQL:2003
UNKNOWN ifVALID_TO is NULL Takes TRUE and
UNKNOWN
BOOLEANTests Since SQL:20031999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDB5.0.51a MySQL8.3 PostgreSQL
3.23.0[0] SQLite
DB2 LUWOracleSQL Server
[0]No IS [NOT] UNKNOWN. Use IS [NOT] NULL instead.
BOOLEANType
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Without NOT NULLit is a
three-valued Boolean
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
SELECT…FROM…WHERENOT(deleted)
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
INSERT…(…,deleted,…)VALUES(…,true,…)
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
INSERT…(…,deleted,…)VALUES(…,true,…)INSERT…(…,deleted,…)VALUES(…,false,…)
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
INSERT…(…,deleted,…)VALUES(…,true,…)INSERT…(…,deleted,…)VALUES(…,false,…)INSERT…(…,deleted,…)VALUES(…,42,…)
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
INSERT…(…,deleted,…)VALUES(…,true,…)INSERT…(…,deleted,…)VALUES(…,false,…)INSERT…(…,deleted,…)VALUES(…,42,…)
UNIQUE,
BOOLEANTypeCREATETABLE…(…deletedBOOLEANNOTNULL,…)
Since SQL:2003
Alias for int
MySQL MariaDBSQLite
INSERT…(…,deleted,…)VALUES(…,true,…)INSERT…(…,deleted,…)VALUES(…,false,…)INSERT…(…,deleted,…)VALUES(…,42,…)
UNIQUE,
+----+|de|+----+|1||0||42|+----+
BOOLEANType Since SQL:2003
Note that boolean in base tables is often questionable:
BOOLEANType Since SQL:2003
Note that boolean in base tables is often questionable:
‣deleted flags are a poor mans temporal database model
BOOLEANType Since SQL:2003
Note that boolean in base tables is often questionable:
‣deleted flags are a poor mans temporal database model
‣States often need more than two (or three) values consider using an enum instead (can be evolved)
BOOLEANType Since SQL:2003
Note that boolean in base tables is often questionable:
‣deleted flags are a poor mans temporal database model
‣States often need more than two (or three) values consider using an enum instead (can be evolved)See: 3 Reasons I Hate Booleans In Databases by Jeff Potterhttps://medium.com/@jpotts18/646d99696580
BOOLEANType Since SQL:20031999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1[0] MariaDB5.0.51a[0] MySQL
8.4 PostgreSQL3.23.0[0] SQLite
DB2 LUWOracleSQL Server
[0]BOOLEAN, TRUE, FALSE are aliases for TINYINT(1), 1, 0 respectivley.
OVERand
PARTITIONBY
OVER (PARTITION BY) The ProblemTwo distinct concepts could not be used independently:
OVER (PARTITION BY) The ProblemTwo distinct concepts could not be used independently:
‣Merge rows with the same key properties
‣ GROUPBY to specify key properties
‣ DISTINCT to use full row as key
OVER (PARTITION BY) The ProblemTwo distinct concepts could not be used independently:
‣Merge rows with the same key properties
‣ GROUPBY to specify key properties
‣ DISTINCT to use full row as key
‣ Aggregate data from related rows ‣ Requires GROUPBY to segregate the rows
‣ COUNT, SUM, AVG, MIN, MAX to aggregate grouped rows
OVER (PARTITION BY) The Problem
OVER (PARTITION BY) The Problem
SELECTc1,c2FROMt
SELECTc1,c2FROMt
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No SELECTc1
,c2FROMt
SELECTc1,c2FROMt
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No SELECTc1
,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMt
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMt
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMt
SELECTc1,SUM(c2)totFROMtGROUPBYc1
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMt
SELECTc1,SUM(c2)totFROMtGROUPBYc1
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMtJOIN()taON(t.c1=ta.c1)
SELECTc1,SUM(c2)totFROMtGROUPBYc1
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMtJOIN()taON(t.c1=ta.c1)
SELECTc1,SUM(c2)totFROMtGROUPBYc1
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMtJOIN()taON(t.c1=ta.c1)
SELECTc1,SUM(c2)totFROMtGROUPBYc1
,tot
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) The Problem
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMtJOIN()taON(t.c1=ta.c1)
SELECTc1,SUM(c2)totFROMtGROUPBYc1
,tot
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) Since SQL:2003
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMtFROMt
SELECTc1,SUM(c2)totFROMtGROUPBYc1
OVER (PARTITION BY) Since SQL:2003
Yes ⇠
Mer
ge ro
ws ⇢
No
No ⇠ Aggregate ⇢ Yes
SELECTc1,c2FROMt
SELECTDISTINCTc1,c2FROMt
SELECTc1,c2FROMt
FROMt
,SUM(c2)OVER(PARTITIONBYc1)
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
Look here
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
OVER (PARTITION BY) How it works
dep salary1 1000 600022 1000 600022 1000 6000333 1000 6000333 1000 6000333 1000 6000
SELECTdep,salary,SUM(salary)OVER()FROMemp
OVER (PARTITION BY) How it works
)
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 100022 1000 200022 1000 2000333 1000 3000333 1000 3000333 1000 3000
OVER (PARTITION BY) How it works
)
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 100022 1000 200022 1000 2000333 1000 3000333 1000 3000333 1000 3000
OVER (PARTITION BY) How it works
)PARTITIONBYdep
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 100022 1000 200022 1000 2000333 1000 3000333 1000 3000333 1000 3000
OVER (PARTITION BY) How it works
)PARTITIONBYdep
SELECTdep,salary,SUM(salary)OVER()FROMemp
dep salary1 1000 100022 1000 200022 1000 2000333 1000 3000333 1000 3000333 1000 3000
OVER (PARTITION BY) How it works
)PARTITIONBYdep
OVERand
ORDERBY(Framing & Ranking)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) The Problem
SELECTid,value,FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) The Problem
SELECTid,value,FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) The Problem
SELECTid,value,FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) The Problem
SELECTid,value,
(SELECTSUM(value)FROMtransactionst2WHEREt2.id<=t.id)
FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) The Problem
SELECTid,value,
(SELECTSUM(value)FROMtransactionst2WHEREt2.id<=t.id)
FROMtransactionst
Range segregation (<=)not possible with
GROUP BY orPARTITION BY
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
(SELECTSUM(value)FROMtransactionst2WHEREt2.id<=t.id)
FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYid
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDING
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +30
22 3 -10 +20
333 4 +50 +70
333 5 -30 +40
333 6 -20 +20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10
22 2 +20
22 3 -10
333 4 +50
333 5 -30
333 6 -20
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
OVER (ORDER BY) Since SQL:2003
SELECTid,value,
FROMtransactionst
SUM(value)OVER(
)
acnt id value balance
1 1 +10 +10
22 2 +20 +20
22 3 -10 +10
333 4 +50 +50
333 5 -30 +20
333 6 -20 .0
ORDERBYidROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
PARTITIONBYacnt
‣ Aggregates without GROUPBY
Use CasesOVER (SQL:2003)
‣ Aggregates without GROUPBY
‣ Running totals, moving averages
Use Cases
AVG(…)OVER(ORDERBY…ROWSBETWEEN3PRECEDINGAND3FOLLOWING)moving_avg
OVER (SQL:2003)
‣ Aggregates without GROUPBY
‣ Running totals, moving averages
‣ Ranking
Use Cases
AVG(…)OVER(ORDERBY…ROWSBETWEEN3PRECEDINGAND3FOLLOWING)moving_avg
OVER (SQL:2003)
‣ Aggregates without GROUPBY
‣ Running totals, moving averages
‣ Ranking‣ Top-N per Group
Use Cases
SELECT*FROM(SELECTROW_NUMBER()OVER(PARTITIONBY…ORDERBY…)rn,t.*FROMt)numbered_tWHERErn<=3
AVG(…)OVER(ORDERBY…ROWSBETWEEN3PRECEDINGAND3FOLLOWING)moving_avg
OVER (SQL:2003)
‣ Aggregates without GROUPBY
‣ Running totals, moving averages
‣ Ranking‣ Top-N per Group
‣ Avoiding self-joins
Use Cases
SELECT*FROM(SELECTROW_NUMBER()OVER(PARTITIONBY…ORDERBY…)rn,t.*FROMt)numbered_tWHERErn<=3
AVG(…)OVER(ORDERBY…ROWSBETWEEN3PRECEDINGAND3FOLLOWING)moving_avg
OVER (SQL:2003)
‣ Aggregates without GROUPBY
‣ Running totals, moving averages
‣ Ranking‣ Top-N per Group
‣ Avoiding self-joins
[… many more …]
Use Cases
SELECT*FROM(SELECTROW_NUMBER()OVER(PARTITIONBY…ORDERBY…)rn,t.*FROMt)numbered_tWHERErn<=3
AVG(…)OVER(ORDERBY…ROWSBETWEEN3PRECEDINGAND3FOLLOWING)moving_avg
OVER (SQL:2003)
1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 10.2 MariaDB8.0 MySQL
8.4 PostgreSQL3.25.0 SQLite
7.0 DB2 LUW8i Oracle
2005[0] 2012 SQL Server[0]No framing
OVER (SQL:2003) Availability
1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 10.2 MariaDB8.0 MySQL
8.4 PostgreSQL3.25.0 SQLite
7.0 DB2 LUW8i Oracle
2005[0] 2012 SQL Server[0]No framing
OVER (SQL:2003) AvailabilityImpalaSpark
NuoDB
BigQuery Hive
OVERSQL:2003 frame exclusion
Since SQL:2003OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
OVER (frame exclusion)
Since SQL:2003OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
default
OVER (frame exclusion)
noothers
Since SQL:2003
x12223
OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
default
OVER (frame exclusion)
noothers
currentrow
Since SQL:2003
x12223
OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
default
OVER (frame exclusion)
currentrow
groupx=current_row
currentrow
Since SQL:2003
x12223
OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
default
OVER (frame exclusion)
group
tiesgroupx=current_row
currentrow
Since SQL:2003
x12223
OVER(ORDERBY…BETWEEN…exclude[noothers|currentrow|group|ties])
default
OVER (frame exclusion)
ties
OVER (frame exclusion) Since SQL:20111999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDBMySQL
11 PostgreSQL3.28.0 SQLite
DB2 LUWOracleSQL Server
FILTER
FILTER Before we start
In SQL, most aggregate functions* drop null arguments
prior to the aggregation.
*Exceptions: Some aggregate functions that return structured data: array_agg, json_objectagg, xmlagg
See: http://modern-sql.com/concept/null#aggregates
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
Optional:ELSE NULL is default
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
Optional:ELSE NULL is default
Aggregatesignore NULL*
*Exceptions:array_agg, json_objectagg, xmlagg See: https://modern-sql.com/concept/null#aggregates
SELECTYEAR,SUM(CASEWHENMONTH=1THENrevenueELSE0END)JAN,SUM(CASEWHENMONTH=2THENrevenueEND)FEB,…FROMsalesGROUPBYYEAR
FILTER The ProblemPivot table: Years on the Y axis, month on X:
SELECTYEAR,SUM(revenue)FILTER(WHEREMONTH=1)JAN,SUM(revenue)FILTER(WHEREMONTH=2)FEB,…FROMsalesGROUPBYYEAR;
FILTER Since SQL:2003SQL:2003 allows FILTER(WHERE…) after aggregates:
FILTER Since SQL:2003
Year
2016
2016
2016
2016
2016
Month
1
2
3
...
12
Revenue
1
23
345
...
1234
Year
2016
Jan
1
Feb
23
Mar
345
...
...
Dec
1234
SUM(…) F
ILTE
R(WHERE …)
SUM(…) F
ILTER(WHERE
month=2)
SUM(revenu
e) FIL
TER(WHERE
month=3)
SUM(reven
ue) FI
LTER(WHERE mo
nth=…)
SUM(reven
ue) FIL
TER(WH
ERE month=
12)
Pivot in SQL1. Use GROUP BY
to combine rows2. Use FILTER to pick
rows per column
See: https://modern-sql.com/use-case/pivot
FILTER Availability1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDBMySQL
9.4 PostgreSQL3.25.0[0] SQLite
DB2 LUWOracleSQL Server
[0]Only with OVER clause
Inverse Distribution Functions (percentiles)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
SELECTd1.valFROMdatad1JOINdatad2ON(d1.val<d2.valOR(d1.val=d2.valANDd1.id<d2.id))GROUPBYd1.valHAVINGcount(*)=(SELECTFLOOR(COUNT(*)/2)FROMdatad3)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
SELECTd1.valFROMdatad1JOINdatad2ON(d1.val<d2.valOR(d1.val=d2.valANDd1.id<d2.id))GROUPBYd1.valHAVINGcount(*)=(SELECTFLOOR(COUNT(*)/2)FROMdatad3)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
Number rows
SELECTd1.valFROMdatad1JOINdatad2ON(d1.val<d2.valOR(d1.val=d2.valANDd1.id<d2.id))GROUPBYd1.valHAVINGcount(*)=(SELECTFLOOR(COUNT(*)/2)FROMdatad3)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
Number rows
Pick middle one
SELECTd1.valFROMdatad1JOINdatad2ON(d1.val<d2.valOR(d1.val=d2.valANDd1.id<d2.id))GROUPBYd1.valHAVINGcount(*)=(SELECTFLOOR(COUNT(*)/2)FROMdatad3)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
Number rows
Pick middle one
SELECTd1.valFROMdatad1JOINdatad2ON(d1.val<d2.valOR(d1.val=d2.valANDd1.id<d2.id))GROUPBYd1.valHAVINGcount(*)=(SELECTFLOOR(COUNT(*)/2)FROMdatad3)
Inverse Distribution Functions The ProblemGrouped rows cannot be ordered prior to aggregation.
(how to get the middle value (median) of a set)
Number rows
Pick middle one
Since SQL:2003Inverse Distribution Functions
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Median
Since SQL:2003Inverse Distribution Functions
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Median
Which value?
Since SQL:2003Inverse Distribution Functions
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
0 0.25 0.5 0.75 11
2
3
4
PERCENTILE_DISC
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
0 0.25 0.5 0.75 11
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
0 0.25 0.5 0.75 11
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC(0.5)
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
0 0.25 0.5 0.75 11
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC(0.5)0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_CONT
PERCENTILE_DISC(0.5)
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
1
2
3
4
0 0.25 0.5 0.75 11
2
3
4
0 0.25 0.5 0.75 11
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_DISC(0.5)0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_CONT
PERCENTILE_DISC(0.5)0 0.25 0.5 0.75 1
1
2
3
4
PERCENTILE_CONT(0.5)
PERCENTILE_DISC(0.5)
SELECTPERCENTILE_DISC(0.5)WITHINGROUP(ORDERBYval)FROMdata
Since SQL:2003Inverse Distribution Functions
Two variants: ‣ for discrete values (categories) ‣ for continuous values (linear interpolation)
Inverse Distribution Functions Availability1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 10.3[0] MariaDBMySQL
9.4 PostgreSQLSQLite
11.1 DB2 LUW9iR1 Oracle
2012[0] SQL Server[0]Only as window function (requires OVER clause)
SQL:2011
OVERSQL:2011 groups option
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,range
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,range
x13
3.53.54
CURRENT ROW
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,range
rowscount(*)
x13
3.53.54
CURRENT ROW
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,range
rangexbetweencurrent_row-1
andcurrent_row+1
rowscount(*)
x13
3.53.54
CURRENT ROW
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,rangeNew in
SQL:2011 groups
rangexbetweencurrent_row-1
andcurrent_row+1
rowscount(*)
x13
3.53.54
CURRENT ROW
groupscount(distinctx)
OVER (groups option) Since SQL:2011ORDERBYx<frameunit>between1precedingand1following
rows,rangeNew in
SQL:2011 groups
rangexbetweencurrent_row-1
andcurrent_row+1
rowscount(*)
x13
3.53.54
CURRENT ROW
Since SQL:2011OVER (groups option)1999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDBMySQL
11 PostgreSQL3.28.0 SQLite
DB2 LUWOracleSQL Server
System Versioning (Time Traveling)
INSERTUPDATEDELETE
are DESTRUCTIVE
System Versioning The Problem
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
CREATETABLEt(...,
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
CREATETABLEt(...,start_tsTIMESTAMP(9)GENERATEDALWAYSASROWSTART,
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
CREATETABLEt(...,start_tsTIMESTAMP(9)GENERATEDALWAYSASROWSTART,end_tsTIMESTAMP(9)GENERATEDALWAYSASROWEND,
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
CREATETABLEt(...,start_tsTIMESTAMP(9)GENERATEDALWAYSASROWSTART,end_tsTIMESTAMP(9)GENERATEDALWAYSASROWEND,
PERIODFORSYSTEM_TIME(start_ts,end_ts)
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
CREATETABLEt(...,start_tsTIMESTAMP(9)GENERATEDALWAYSASROWSTART,end_tsTIMESTAMP(9)GENERATEDALWAYSASROWEND,
PERIODFORSYSTEM_TIME(start_ts,end_ts))WITHSYSTEMVERSIONING
System Versioning Since SQL:2011Table can be system versioned, application versioned or both.
ID Data start_ts end_ts1 X 10:00:00
UPDATE...SETDATA='Y'...
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00
DELETE...WHEREID=1
INSERT...(ID,DATA)VALUES(1,'X')
System Versioning Since SQL:2011
ID Data start_ts end_ts1 X 10:00:00
UPDATE...SETDATA='Y'...
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00
DELETE...WHEREID=1
INSERT...(ID,DATA)VALUES(1,'X')
System Versioning Since SQL:2011
ID Data start_ts end_ts1 X 10:00:00
UPDATE...SETDATA='Y'...
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00
DELETE...WHEREID=1
INSERT...(ID,DATA)VALUES(1,'X')
System Versioning Since SQL:2011
ID Data start_ts end_ts1 X 10:00:00
UPDATE...SETDATA='Y'...
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00
DELETE...WHEREID=1
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00 12:00:00
INSERT...(ID,DATA)VALUES(1,'X')
System Versioning Since SQL:2011
Although multiple versions exist, only the “current” one is visible per default.
After 12:00:00, SELECT*FROMt doesn’t return anything anymore.
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00 12:00:00
System Versioning Since SQL:2011
ID Data start_ts end_ts1 X 10:00:00 11:00:001 Y 11:00:00 12:00:00
With FOR…ASOF you can query anything you like: SELECT*FROMtFORSYSTEM_TIMEASOFTIMESTAMP'2019-05-0210:30:00'
ID Data start_ts end_ts
1 X 10:00:00 11:00:00
System Versioning Since SQL:2011
System Versioning Since SQL:20111999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 10.3 MariaDBMySQLPostgreSQLSQLite
10.1 DB2 LUW10gR1[0] 11gR1[1] Oracle
2016 SQL Server[0]Short term using Flashback.[1]Flashback Archive. Proprietery syntax.
FORPORTIONOF(UPDATE,DELETE)
CREATETABLEt(...,
Since SQL:2011Table can be system versioned, application versioned or both.
FOR PORTION OF
CREATETABLEt(...,start_tsTIMESTAMP(9),
Since SQL:2011Table can be system versioned, application versioned or both.
FOR PORTION OF
CREATETABLEt(...,start_tsTIMESTAMP(9),end_tsTIMESTAMP(9),
Since SQL:2011Table can be system versioned, application versioned or both.
FOR PORTION OF
CREATETABLEt(...,start_tsTIMESTAMP(9),end_tsTIMESTAMP(9),
PERIODFORapp(start_ts,end_ts))
Since SQL:2011Table can be system versioned, application versioned or both.
FOR PORTION OF
ID Data start_ts end_ts1 X 10:00:00 12:00:00
UPDATEtFORPORTIONOFappFROM'10:30:00'TO'11:30:00'SETDATA='Y'
ID Data start_ts end_ts1 X 10:00:00 10:30:001 Y 10:30:00 11:30:001 X 11:30:00 12:00:00
INSERTt(ID,DATA,start_ts,end_ts)VALUES(1,'X','10:00:00','12:00:00')
Since SQL:2011FOR PORTION OF
ID Data start_ts end_ts1 X 10:00:00 12:00:00
UPDATEtFORPORTIONOFappFROM'10:30:00'TO'11:30:00'SETDATA='Y'
ID Data start_ts end_ts1 X 10:00:00 10:30:001 Y 10:30:00 11:30:001 X 11:30:00 12:00:00
INSERTt(ID,DATA,start_ts,end_ts)VALUES(1,'X','10:00:00','12:00:00')
Since SQL:2011FOR PORTION OF
ID Data start_ts end_ts1 X 10:00:00 12:00:00
UPDATEtFORPORTIONOFappFROM'10:30:00'TO'11:30:00'SETDATA='Y'
ID Data start_ts end_ts1 X 10:00:00 10:30:001 Y 10:30:00 11:30:001 X 11:30:00 12:00:00
INSERTt(ID,DATA,start_ts,end_ts)VALUES(1,'X','10:00:00','12:00:00')
Since SQL:2011FOR PORTION OF
FORPORTIONOF Since SQL:20111999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDBMySQLPostgreSQLSQLite
10.5 DB2 LUWOracleSQL Server
10.4
FORPORTIONOF Since SQL:20111999
2001
2003
2005
2007
2009
2011
2013
2015
2017
5.1 MariaDBMySQLPostgreSQLSQLite
10.5 DB2 LUWOracleSQL Server
10.4
Since SQL:2011Application Versioning
For a useful application versioning, WITHOUTOVERLAPS
constraints are required.
Apparently this was moved to MariaDB 10.5: https://jira.mariadb.org/browse/MDEV-16978
https://commons.wikimedia.org/wiki/File:Tamias_striatus_CT.jpg
Mühsamernährtsich das
Eichhörnchen
@ModernSQL modern-sql.comMy other website:
This is the trainingyou are looking for:https://winand.at/