SELECT with LIMIT OFFSET clause return unexpected result (with sample db and query)
(1.1) By LT (LT123456) on 2022-01-06 14:35:23 edited from 1.0 [source]
I'm working with an SQLite DB in my application. Some query returns unexpected results. Below I try to describe the issue with a minimal example:
I'm using SQLite v3.31 and I have tested the same problem with the latest v3.37.1 and the results are the same.
Suppose I have a DB called test.db created with the following script:
-- Table: seriesThis just produces a simply DB with one table "series" and with 4 records.BEGIN TRANSACTION;
DROP TABLE IF EXISTS "series";
CREATE TABLE IF NOT EXISTS "series" ( "id" INTEGER NOT NULL DEFAULT 1 PRIMARY KEY AUTOINCREMENT UNIQUE, "acqtime" TEXT );
INSERT INTO "series" ("id","acqtime") VALUES (1,'2021-01-02T22:33:44');
INSERT INTO "series" ("id","acqtime") VALUES (2,'2021-01-03T01:12:23');
INSERT INTO "series" ("id","acqtime") VALUES (3,'2021-01-03T01:02:41');
INSERT INTO "series" ("id","acqtime") VALUES (4,'2021-01-03T01:47:55');
COMMIT;
- Then I query this table with
SELECT (As you can see, this query just produces two columns based on the table contents.SELECT DISTINCT date(series.acqtime) FROM series ORDER BY date(series.acqtime) DESC LIMIT 1 OFFSET 400 -- note this supper large OFFSET value ) as subquery_result, date(series.acqtime) < ( SELECT DISTINCT date(series.acqtime) FROM series ORDER BY date(series.acqtime) DESC LIMIT 1 OFFSET 400 -- note this supper large OFFSET value ) as cmp_result
FROM series;
- The 1st column "subquery_result" contains the result of a subquery
- The 2nd column "cmp_result" contains the result of a comparison between the "acqtime" with the returned value of column 1.
Here is the problem:
According to the content of the table, I expect the subquery
returns NULL, because I put a ridiculously large number for the OFFSET value in the LIMIT clause. (There are only 4 records in the table). However, if you run the query above, you will see the output is( SELECT DISTINCT date(series.acqtime) FROM series ORDER BY date(series.acqtime) DESC LIMIT 1 OFFSET 400 -- note this supper large OFFSET value )
This indicates that the subquery does not return NULL but returns a valid record which I don't expect."subquery_result","cmp_result" "2021-01-03",1 "2021-01-03",0 "2021-01-03",0 "2021-01-03",0
In addition to this issue, I found other two problems:
- one is that if I replace the ordering keyword "DESC" with "ASC", the subquery does turn NULL.
- The 2nd is that if I add another record into the "series" table which contains a new different distinct date such as '2021-01-02T01:47:55', the result will be as what I expect. I.e.:
Note the "subquery_result" is NULL."subquery_result","cmp_result" NULL NULL NULL NULL NULL NULL NULL NULL
Questions
- Is that correct that I expect a NULL from the subquery? I didn't find clear documentation describing the behavior of the LIMIT OFFSET clause when the OFFSET value is beyond the total number of records. But I think a NULL should be a logical result. (Unless we can confirm this will lead to an undefined result)
- Can some of you give some explanation of the different behaviors when changing the number of distinct records or changing the ordering method?
- If the OFFSET value beyond the total number of records will lead to undefined behavior, can this be mentioned in the doc? If it will give a NULL, can you confirm that the unexpected error case I described above is an SQLite bug?
(2) By LT (LT123456) on 2022-01-12 16:22:51 in reply to 1.1 [link] [source]
Can this be confirmed as a bug? Anyone can give it a try?
(3) By Simon Slavin (slavin) on 2022-01-12 18:19:21 in reply to 1.1 [link] [source]
I fail to reproduce the above in SQLite 3.36.0:
simon@183 Desktop % sqlite3 test.db SQLite version 3.36.0 2021-06-18 18:58:49 Enter ".help" for usage hints. sqlite> CREATE TABLE IF NOT EXISTS "series" ( "id" INTEGER NOT NULL DEFAULT 1 PRIMARY KEY AUTOINCREMENT UNIQUE, "acqtime" TEXT ); sqlite> INSERT INTO "series" ("id","acqtime") VALUES (1,'2021-01-02T22:33:44'); sqlite> INSERT INTO "series" ("id","acqtime") VALUES (2,'2021-01-03T01:12:23'); sqlite> INSERT INTO "series" ("id","acqtime") VALUES (3,'2021-01-03T01:02:41'); sqlite> INSERT INTO "series" ("id","acqtime") VALUES (4,'2021-01-03T01:47:55'); sqlite> .mode table sqlite> SELECT * FROM series; +----+---------------------+ | id | acqtime | +----+---------------------+ | 1 | 2021-01-02T22:33:44 | | 2 | 2021-01-03T01:12:23 | | 3 | 2021-01-03T01:02:41 | | 4 | 2021-01-03T01:47:55 | +----+---------------------+ sqlite> SELECT DISTINCT date(series.acqtime) ...> FROM series ...> ORDER BY date(series.acqtime) DESC; +----------------------+ | date(series.acqtime) | +----------------------+ | 2021-01-03 | | 2021-01-02 | +----------------------+ sqlite> SELECT DISTINCT date(series.acqtime) ...> FROM series ...> ORDER BY date(series.acqtime) DESC ...> LIMIT 1 OFFSET 400; sqlite>
I get the expected zero-row result to the above SELECT whereas the OP says he gets four rows. Did I misunderstand the query ?
(4) By David Raymond (dvdraymond) on 2022-01-12 18:38:16 in reply to 3 [link] [source]
Did I misunderstand the query ?
Yes. It being a sub-query is part of whatever the bug is.
Can add that I'm seeing what they're seeing with the 3.37.2 CLI
(5) By Keith Medcalf (kmedcalf) on 2022-01-12 18:56:11 in reply to 2 [link] [source]
THe current tip of trunk displays the behaviour shown.
(6) By Ryan Smith (cuz) on 2022-01-12 19:10:05 in reply to 1.1 [link] [source]
Confirmed a bug.
Here is a minimal Query to reproduce:
WITH dt(cdate) AS (
SELECT '2021-01-01' UNION ALL
SELECT '2021-01-01'
)
SELECT CASE WHEN
(SELECT DISTINCT date(cdate)
FROM dt
ORDER BY date(cdate) DESC
LIMIT 1 OFFSET 10
) IS NULL THEN 'Pass' ELSE 'FAIL' END AS Test
FROM dt;
-- Test
-- ------
-- FAIL
-- FAIL
The problem seems to be with the ordering being DESC and the two dates being exactly the same.
Here are two similar examples, one with the ordering being ASC in stead of DESC, and the next with the dates being dissimilar:
WITH dt(cdate) AS (
SELECT '2021-01-01' UNION ALL
SELECT '2021-01-01'
)
SELECT CASE WHEN
(SELECT DISTINCT date(cdate)
FROM dt
ORDER BY date(cdate) ASC
LIMIT 1 OFFSET 10
) IS NULL THEN 'Pass' ELSE 'FAIL' END AS Test
FROM dt;
-- Test
-- ------
-- Pass
-- Pass
WITH dt(cdate) AS (
SELECT '2021-01-01' UNION ALL
SELECT '2021-01-02'
)
SELECT CASE WHEN
(SELECT DISTINCT date(cdate)
FROM dt
ORDER BY date(cdate) DESC
LIMIT 1 OFFSET 10
) IS NULL THEN 'Pass' ELSE 'FAIL' END AS Test
FROM dt;
-- Test
-- ------
-- Pass
-- Pass
Hope that helps.
(7) By Richard Hipp (drh) on 2022-01-12 19:33:00 in reply to 1.1 [link] [source]
Simplified test case:
CREATE TABLE t1(x); INSERT INTO t1 VALUES(1),(1),(1); SELECT (SELECT DISTINCT x FROM t1 ORDER BY +x LIMIT 1 OFFSET 100) FROM t1;
This returns three 1's, but ought to return three NULLs, I think.
(8) By Richard Hipp (drh) on 2022-01-12 19:40:56 in reply to 7 [link] [source]
Bisects to check-in 2a74129a21f9745f. I do not believe that is the source of the problem - I think it is just a change that enabled this particular test-case to work. Probably the problem goes back a lot further.
(9) By Richard Hipp (drh) on 2022-01-12 20:32:45 in reply to 7 [link] [source]
I think this is now fixed at check-in 9282bcde301cee2a.