1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-10-28 10:57:26 +03:00

New optimizations for improved performance of UNION with LIMIT 1, with

or without ORDER BY.

FossilOrigin-Name: b5b8ede613c40cf4a1ad0fe2af9b98d410982e0680b1da00c67ff1c2c34e0d6b
This commit is contained in:
drh
2026-08-25 17:12:09 +00:00
9 changed files with 69 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
C Fix\sOOM\shandling\sin\slittle-used\sextension\sfile\s"expert.c".\sReport\s[bugs:/info/2026-08-25T16:02:31Z\s|\s2026-08-25T16:02:31Z].
D 2026-08-25T16:15:15.839
C New\soptimizations\sfor\simproved\sperformance\sof\sUNION\swith\sLIMIT\s1,\swith\nor\swithout\sORDER\sBY.
D 2026-08-25T17:12:09.970
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -743,12 +743,12 @@ F src/printf.c b480176bd0aaed9d64e804cfa3ca27fc585f65a73e7c7ca2fe9b9016db2cf6d5
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c 0e0990f60c93db38c735d85e5d1741130e72dd0670aec204b5a39118b53c8044
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
F src/select.c ba33125b1a93984f702883c730002a466860a7226dd9ce811bf75246d4ed021e
F src/shell.c.in be2c75686ba31e189d82f79f6003109a7a0ea34a1290d72b6d695b59f87eec04
F src/select.c a962dd2573404110b438d9226494d57f71609cbc5f8365e40d1f8fa25e12f6aa
F src/shell.c.in 3f015aa86179b6c4615f0cb2352a20af228aa2cad23fc0daa88aa82b36ce2d0f
F src/sqlite.h.in efc3f7aa00a2bb01c087435e6fa5f86103e310e6c3ea072b12cc110cf2715a75
F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
F src/sqlite3ext.h 0efd4723bad9124ea1f581d9f1ea0254ac1c6f3e5fb29e4f3dcf36c72485a456
F src/sqliteInt.h b4cb30785d99c6b04399a0ca35fa701d5f0b6e6df0ea8bdb7a40878cb87decc6
F src/sqliteInt.h d58ab43b8a44f3d8c216ea042f7790fe038fce2a3207a011a175110ad322f256
F src/sqliteLimit.h c433b3fdb5e52f4226bf78d7e8081dff8fd8fbb0ce0cb1a5fee282aaa8976813
F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -992,7 +992,7 @@ F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a
F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6
F test/collateB.test 9c840e21f9aead6fc533cea310f0bd202d5c11511088811b7e93ae7b47fccb24
F test/colmeta.test 248a644cec4c7c371cf1e107fd8fdba708dc290866c572672b6260e3466cce79
F test/colname.test 0e32125de701c6bd86247194c6f5639b740b4f71a0d88ee1e67ff3bda9ae99ca
F test/colname.test 30f7fff482c141bf5baecfbbba4e22686ac4c581fae53a511908be8439fe48a7
F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f
F test/conflict.test 3307ffdf988e04b01c4e942d8aa369a977f085bf629f43a627c9a77f39d65926
F test/conflict2.test 5557909ce683b1073982f5d1b61dfb1d41e369533bfdaf003180c5bc87282dd1
@@ -1083,7 +1083,7 @@ F test/e_createtable.test 31b9bcb6ac8876bc7ec342d86d9c231a84c62b442093a6651dfd0f
F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e
F test/e_droptrigger.test 235c610f8bf8ec44513e222b9085c7e49fad65ad0c1975ac2577109dd06fd8fa
F test/e_dropview.test 74e405df7fa0f762e0c9445b166fe03955856532e2bb234c372f7c51228d75e7
F test/e_expr.test 9bdb347b78b9f4eff9153ea97797facc179a821898588471a70808b4471a69b0
F test/e_expr.test 5225396116ed93d68b60933bbe1668f5652f268a4074ef0b7de008f1ae773f66
F test/e_fkey.test 2a4ad815372a4b57dad91cd37932e18e68bf545f6c946e9a4cb3e7bde92b8e84
F test/e_fts3.test 17ba7c373aba4d4f5696ba147ee23fd1a1ef70782af050e03e262ca187c5ee07
F test/e_insert.test f02f7f17852b2163732c6611d193f84fc67bc641fb4882c77a464076e5eba80e
@@ -1606,7 +1606,7 @@ F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972
F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384
F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5
F test/seekscan1.test 2148a174ba55fa378f18fdf62eeed1f3a3cc9b6f360a948db613cad0c80b1f04
F test/select1.test 692e84cfa29c405854c69e8a4027183d64c22952866a123fabbce741a379e889
F test/select1.test f5afdbe971a9dd05f0c094d44eb7ee157d61326e7e15357a2a84f20e6937ed52
F test/select2.test 352480e0e9c66eda9c3044e412abdf5be0215b56
F test/select3.test 152ce3978c8600c70413e921e39e4c5aeb8ff1e96dd722442a9981e1b8afc702
F test/select4.test 21941409ac6b65bfca83de020afb5d976d802d3d8ad216dc774a3fbbf55fe277
@@ -2092,7 +2092,7 @@ F test/window7.tcl 6a1210f05d40ec89c22960213a22cd3f98d4e2f2eb20646c83c8c30d4d761
F test/window7.test 1d31276961ae7801edc72173edaf7593e3cbc79c06d1f1f09e20d8418af403cd
F test/window8.tcl c57364e64d816f6e26df60437e1202e2c1031c7b818a1a67535d1006862a026a
F test/window8.test 3d931e58802b8ab8063da00f0cf30aa3351640238a952c0efb5a129e2349a4bb
F test/window9.test 7b98a7916dd87763ea35f56ea023e3b29e99744582204ccf2937a3bac411cd4d
F test/window9.test 234309555c3d449753e8773a0339cc380ec61e6f03d778cb54bbfa3fc42ba676
F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
F test/windowB.test bba3bee77c1d3321077ff8951d543161d74f08f78633d84433ced7d6261dc2c2
F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2
@@ -2219,8 +2219,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
P f3ad1b4394ff0902e4ae9e8d0ea36ee198149c5ce8745851eaf84105484fedf2
R 3bdc73eba82986d23e6b27dd2317c611
U dan
Z ab1005716313da03aef3665d5bfce64d
P c89f4f79676f34cd8399f4a8a3934ad63d4547b9528e736bad1ee0e6f5fe6305 e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53
R f0bf0f57d459e0ea6ee0b13b3376f8b2
T +closed e78b72fadfe4c263f5af56aa887891a49055edfccda9397267804b3aaf4afd53
U drh
Z 197d97631f296c0f81b1f94a1483a388
# Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
c89f4f79676f34cd8399f4a8a3934ad63d4547b9528e736bad1ee0e6f5fe6305
b5b8ede613c40cf4a1ad0fe2af9b98d410982e0680b1da00c67ff1c2c34e0d6b

View File

@@ -2943,11 +2943,33 @@ static int hasAnchor(Select *p){
}
/*
** This routine is called to process a compound query form from
** Return TRUE if p is a UNION with a LIMIT of exactly 1 and no OFFSET
** clause. This is a precondition for a couple of related optimizations.
**
** False negatives are harmless (apart from resulting in a slower query).
** False positives can result in incorrect answers, however. To provoke
** false negatives for testing purposes, disable the SQLITE_UnionLimit
** optimization.
*/
static int unionWithLimitOne(sqlite3 *db, Select *p){
int v;
if( p->op!=TK_UNION ) return 0;
if( p->pLimit==0 ) return 0;
if( p->pLimit->pRight ) return 0; /* No OFFSET */
v = 0;
assert( p->pLimit->pLeft!=0 );
if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0;
if( v!=1 ) return 0;
if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0;
return 1;
}
/*
** This routine is called to process a compound query formed from
** two or more separate queries using UNION, UNION ALL, EXCEPT, or
** INTERSECT
**
** "p" points to the right-most of the two queries. the query on the
** "p" points to the right-most of the two queries. The query on the
** left is p->pPrior. The left query could also be a compound query
** in which case this routine will be called recursively.
**
@@ -3031,7 +3053,7 @@ static int multiSelect(
/* If the compound has an ORDER BY clause, then always use the merge
** algorithm. */
return multiSelectByMerge(pParse, p, pDest);
}else if( p->op!=TK_ALL ){
}else if( p->op!=TK_ALL && !unionWithLimitOne(db,p) ){
/* If the compound is EXCEPT, INTERSECT, or UNION (anything other than
** UNION ALL) then also always use the merge algorithm. However, the
** multiSelectByMerge() routine requires that the compound have an
@@ -3043,7 +3065,8 @@ static int multiSelect(
p->pOrderBy->a[0].u.x.iOrderByCol = 1;
return multiSelectByMerge(pParse, p, pDest);
}else{
/* For a UNION ALL compound without ORDER BY, simply run the left
/* For a UNION ALL compound without ORDER BY, or for a UNION with a
** LIMIT of exactly 1 and no OFFSET and no ORDER BY, simply run the left
** query, then run the right query */
int addr = 0;
int nLimit = 0; /* Initialize to suppress harmless compiler warning */
@@ -3594,7 +3617,7 @@ static int multiSelectByMerge(
/* Compute the limit registers */
computeLimitRegisters(pParse, p, labelEnd);
if( p->iLimit && op==TK_ALL ){
if( p->iLimit && (op==TK_ALL || unionWithLimitOne(db,p)) ){
regLimitA = ++pParse->nMem;
regLimitB = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,

View File

@@ -12594,6 +12594,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
{ 0x10000000, 1, "OrderBySubq" },
{ 0x20000000, 1, "StarQuery" },
{ 0x40000000, 1, "ExistsToJoin" },
{ 0x80000000, 1, "UnionLimit" },
{ 0xffffffff, 0, "All" },
};
unsigned int curOpt;

View File

@@ -1968,6 +1968,7 @@ struct sqlite3 {
#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
#define SQLITE_UnionLimit 0x80000000 /* Optimizations for UNION + LIMIT */
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*

View File

@@ -403,7 +403,7 @@ do_execsql_test colname-9.330 { -- added 2019-08-10 to invalidate
DROP TABLE IF EXISTS t1; -- a couple assert()s that were
CREATE TABLE t1(a); -- added by ticket 3b44500725
INSERT INTO t1 VALUES(17),(2),(99),(-3),(7);
SELECT (SELECT avg(a) UNION SELECT min(a) OVER()) FROM t1;
SELECT (SELECT avg(a) UNION SELECT min(a) OVER() ORDER BY 1) FROM t1;
} {17}
# Issue detected by OSSFuzz on 2017-12-24 (Christmas Eve)

View File

@@ -1896,7 +1896,7 @@ do_expr_test e_expr-35.1.3 { (SELECT count(*) FROM t2) } integer 3
do_expr_test e_expr-35.1.4 { (SELECT 4 FROM t2) } integer 4
do_expr_test e_expr-35.1.5 {
(SELECT b FROM t2 UNION SELECT a+1 FROM t2)
(SELECT b FROM t2 UNION SELECT a+1 FROM t2 ORDER BY 1)
} null {}
do_expr_test e_expr-35.1.6 {
(SELECT a FROM t2 UNION SELECT COALESCE(b, 55) FROM t2 ORDER BY 1)

View File

@@ -1210,4 +1210,25 @@ do_execsql_test select1-21.1 {
SELECT a,(+a)b,(+a)b,(+a)b,NOT EXISTS(SELECT null FROM t2),CASE z WHEN 487 THEN 992 WHEN 391 THEN 203 WHEN 10 THEN '?k<D Q' END,'' FROM t1 LEFT JOIN v1a ON z=b;
} {}
# branch union-to-union-all
#
reset_db
do_execsql_test select1-22.1 {
CREATE TABLE t1(a); INSERT INTO t1 VALUES(1),(1),(1),(2),(3);
CREATE TABLE t2(b); INSERT INTO t2 VALUES(10),(11);
SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY 1 LIMIT 3;
} {1 2 3}
do_execsql_test select1-22.2 {
SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY 1 LIMIT 1;
} {1}
do_execsql_test select1-22.3 {
SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY 1 LIMIT 1 OFFSET 2;
} {3}
do_execsql_test select1-22.3 {
SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 1;
} {1}
do_execsql_test select1-22.4 {
SELECT a FROM t1 UNION SELECT b FROM t2 LIMIT 2;
} {1 2}
finish_test

View File

@@ -261,6 +261,7 @@ do_execsql_test 8.4 {
SELECT(
SELECT x UNION
SELECT sum( avg((SELECT x FROM v1)) ) OVER()
ORDER BY 1
)
FROM v1;
} {0.0 0.0}