The current tip of trunk produced the same results demonstrated by Niklas in 
his original post for his original testcase for me.

I would have written the query as a simple query with correlated subqueries to 
get the concatenated data as it is a "more natural" declaration of what is 
wanted (in my opinion anyway), rather than re-writing into joins.

  select BookID,
         Title,
         (
           SELECT group_concat(ltrim("First Name" || ' ' || "Last Name"),',')
             FROM AuthorBooks JOIN Authors USING (AuthorID)
            WHERE BookID == Books.BookID
         ) as "Author(s)",
         (
           SELECT group_concat("Date read",', ')
             FROM DatesRead
            WHERE BookID == Books.BookID
         ) as "Date(s)",
         (
           SELECT group_concat(Genre,', ')
             FROM BookGenres JOIN Genres USING (GenreID)
            WHERE BookID == Books.BookID
         ) AS "Genre(s)"
    FROM Books
ORDER BY BookID;

---
The fact that there's a Highway to Hell but only a Stairway to Heaven says a 
lot about anticipated traffic volume.

>-----Original Message-----
>From: sqlite-users [mailto:sqlite-users-
>boun...@mailinglists.sqlite.org] On Behalf Of niklas
>Sent: Friday, 15 March, 2019 01:36
>To: sqlite-users@mailinglists.sqlite.org
>Subject: [sqlite] Query planner: Scanning subqueries vs using
>automatic covering index
>
>I recently noticed some very slow queries for my sqlite book
>database.
>Turns out that it happened due to the query planner decided to scan
>sub-queries instead of using an automatic covering index to search
>them.
>
>The database contains about 3000 entries and with two subqueries it
>took a noticable time to run, with three subqueries I had to give up
>and
>abort to save the CPU from overheating.
>
>Using indexes only take milliseconds in all cases.
>
>Dropping analyze (sqlite_stat1) information from the database made
>the problem go away, i.e. made sqlite use index instead of scanning.
>As did adding a few more columns to the ORDER BY part.
>
>These sql statements should be enough to demonstrate the problem.
>I am using sqlite 3.25.1
>
>**********
>create.txt
>**********
>
>BEGIN;
>
>CREATE TABLE "Authors" (
>    "AuthorID" INTEGER PRIMARY KEY,
>    "Last Name" TEXT NOT NULL,
>    "First Name" TEXT NOT NULL);
>
>CREATE TABLE "Books" (
>    "BookID" INTEGER PRIMARY KEY,
>    "Title" TEXT NOT NULL);
>
>CREATE TABLE "Genres"(
>    "GenreID" INTEGER PRIMARY KEY,
>    "Genre" TEXT UNIQUE NOT NULL);
>
>CREATE TABLE "DatesRead"(
>    "BookID" INTEGER,
>    "Date Read" TEXT,
>    PRIMARY KEY("BookID", "Date Read"),
>    FOREIGN KEY(BookID) REFERENCES Books(BookID)) WITHOUT ROWID;
>
>CREATE TABLE "AuthorBooks"(
>    "BookID" INTEGER,
>    "AuthorID" INTEGER,
>    PRIMARY KEY("BookID", "AuthorID" ),
>    FOREIGN KEY(BookID) REFERENCES Books(BookID),
>    FOREIGN KEY(AuthorID) REFERENCES Authors(AuthorID)) WITHOUT
>ROWID;
>
>CREATE TABLE "BookGenres"(
>    "BookID" INTEGER,
>    "GenreID" INTEGER,
>    PRIMARY KEY("BookID", "GenreID" ),
>    FOREIGN KEY(BookID) REFERENCES Books(BookID),
>    FOREIGN KEY(GenreID) REFERENCES Genres(GenreID)) WITHOUT ROWID;
>
>ANALYZE;
>INSERT INTO sqlite_stat1 VALUES('BookGenres','BookGenres','3190 2
>1');
>INSERT INTO sqlite_stat1 VALUES('DatesRead','DatesRead','3047 2 1');
>INSERT INTO sqlite_stat1 VALUES('AuthorBooks','AuthorBooks','3549 2
>1');
>INSERT INTO sqlite_stat1 VALUES('Authors',NULL,'1329');
>INSERT INTO sqlite_stat1 VALUES('Books',NULL,'2978');
>INSERT INTO sqlite_stat1
>VALUES('Genres','sqlite_autoindex_Genres_1','112
>1');
>
>COMMIT;
>
>************
>queries.txt
>************
>
>select "--------------------";
>select "order by bookid only";
>select "--------------------";
>
>EXPLAIN QUERY PLAN
>SELECT BookID,Title,"Author(s)","Date(s)","Genre(s)"
>FROM Books
>JOIN (SELECT BookID, group_concat(ltrim("First Name"||' '||"Last
>Name"),',
>') AS 'Author(s)' FROM Books JOIN AuthorBooks USING(BookID) JOIN
>Authors
>USING(AuthorID) GROUP BY BookID) USING(BookID)
>JOIN (SELECT BookID, group_concat("Date read",', ') AS 'Date(s)' FROM
>Books
>JOIN DatesRead USING(BookID) GROUP BY BookID) USING(BookID)
>JOIN (SELECT BookID, group_concat(Genre,', ') AS 'Genre(s)' FROM
>Books JOIN
>BookGenres USING(BookID) JOIN Genres USING(GenreID) GROUP BY BookID)
>USING(BookID)
>ORDER BY BookID;
>
>select "-------------------------------------------";
>select "order by bookid and some other fields too";
>select "------------------------------------------";
>
>EXPLAIN QUERY PLAN
>SELECT BookID,Title,"Author(s)","Date(s)","Genre(s)"
>FROM Books
>JOIN (SELECT BookID, group_concat(ltrim("First Name"||' '||"Last
>Name"),',
>') AS 'Author(s)' FROM Books JOIN AuthorBooks USING(BookID) JOIN
>Authors
>USING(AuthorID) GROUP BY BookID) USING(BookID)
>JOIN (SELECT BookID, group_concat("Date read",', ') AS 'Date(s)' FROM
>Books
>JOIN DatesRead USING(BookID) GROUP BY BookID) USING(BookID)
>JOIN (SELECT BookID, group_concat(Genre,', ') AS 'Genre(s)' FROM
>Books JOIN
>BookGenres USING(BookID) JOIN Genres USING(GenreID) GROUP BY BookID)
>USING(BookID)
>ORDER BY BookID, Title, "Author(s)", "Date(s)";
>
>
>************
>Test run:
>************
>
>del test.db
>type create.txt | sqlite3 test.db
>type queries.txt | sqlite3 test.db
>
>
>************
>Output:
>************
>
>--------------------
>order by bookid only
>--------------------
>QUERY PLAN
>|--MATERIALIZE 1
>|  |--SCAN TABLE Books
>|  |--SEARCH TABLE AuthorBooks USING PRIMARY KEY (BookID=?)
>|  `--SEARCH TABLE Authors USING INTEGER PRIMARY KEY (rowid=?)
>|--MATERIALIZE 2
>|  |--SCAN TABLE Books
>|  `--SEARCH TABLE DatesRead USING PRIMARY KEY (BookID=?)
>|--MATERIALIZE 3
>|  |--SCAN TABLE Books
>|  |--SEARCH TABLE BookGenres USING PRIMARY KEY (BookID=?)
>|  `--SEARCH TABLE Genres USING INTEGER PRIMARY KEY (rowid=?)
>|--SCAN SUBQUERY 1
>|--SCAN SUBQUERY 2
>|--SCAN SUBQUERY 3
>|--SEARCH TABLE Books USING INTEGER PRIMARY KEY (rowid=?)
>`--USE TEMP B-TREE FOR ORDER BY
>-------------------------------------------
>order by bookid and some other fields too
>------------------------------------------
>QUERY PLAN
>|--MATERIALIZE 1
>|  |--SCAN TABLE Books
>|  |--SEARCH TABLE AuthorBooks USING PRIMARY KEY (BookID=?)
>|  `--SEARCH TABLE Authors USING INTEGER PRIMARY KEY (rowid=?)
>|--MATERIALIZE 2
>|  |--SCAN TABLE Books
>|  `--SEARCH TABLE DatesRead USING PRIMARY KEY (BookID=?)
>|--MATERIALIZE 3
>|  |--SCAN TABLE Books
>|  |--SEARCH TABLE BookGenres USING PRIMARY KEY (BookID=?)
>|  `--SEARCH TABLE Genres USING INTEGER PRIMARY KEY (rowid=?)
>|--SCAN SUBQUERY 1
>|--SEARCH TABLE Books USING INTEGER PRIMARY KEY (rowid=?)
>|--SEARCH SUBQUERY 2 USING AUTOMATIC COVERING INDEX (BookID=?)
>|--SEARCH SUBQUERY 3 USING AUTOMATIC COVERING INDEX (BookID=?)
>`--USE TEMP B-TREE FOR ORDER BY
>
>
>As can be seen the first query (when analyze information is in place)
>will use three nested scans as opposed to the second query with more
>order by columns that instead uses indexes to search.
>
>Now, might this be a weakness in the query planner, or is there some
>precautions I could/should always take to avoid the scanning?
>
>
>
>
>
>
>--
>Sent from: http://sqlite.1065341.n5.nabble.com/
>_______________________________________________
>sqlite-users mailing list
>sqlite-users@mailinglists.sqlite.org
>http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users



_______________________________________________
sqlite-users mailing list
sqlite-users@mailinglists.sqlite.org
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to