Hi Ludo, On Sun, 11 Nov 2018 18:06:00 +0100 ludo@gnu.org (Ludovic Courtès) wrote: > Indeed, that solves the problem for this simple example, thanks! > > Now, if I go back to the big query that /api/latestbuilds makes¹, the > result is still pretty bad: > > --8<---------------cut here---------------start------------->8--- > sqlite> EXPLAIN QUERY PLAN SELECT * FROM ( > ...> SELECT Builds.derivation, Builds.rowid, Builds.timestamp, > Builds.starttime, ...> Builds.stoptime, Builds.log, Builds.status, > Builds.job_name, Builds.system, ...> Builds.nix_name, > Specifications.name ...> FROM Builds > ...> INNER JOIN Evaluations ON Builds.evaluation = Evaluations.id > ...> INNER JOIN Specifications ON Evaluations.specification = > Specifications.name ...> WHERE (:id IS NULL OR (:id = Builds.rowid)) > ...> AND (:derivation IS NULL OR (:derivation = Builds.derivation)) > ...> AND (:jobset IS NULL OR (:jobset = Specifications.name)) > ...> AND (:job IS NULL OR (:job = Builds.job_name)) > ...> AND (:system IS NULL OR (:system = Builds.system)) > ...> AND (:evaluation IS NULL OR (:evaluation = Builds.evaluation)) > ...> AND (:status IS NULL OR (:status = 'done' AND Builds.status > >= 0) ...> OR (:status = 'pending' AND > >Builds.status < 0) > ...> OR (:status = 'succeeded' AND > Builds.status = 0) ...> OR (:status = 'failed' > AND Builds.status > 0)) ...> AND (:borderlowtime IS NULL > OR :borderlowid IS NULL ...> OR ((:borderlowtime, :borderlowid) < > (Builds.stoptime, Builds.rowid))) ...> AND (:borderhightime IS NULL > OR :borderhighid IS NULL ...> OR ((:borderhightime, :borderhighid) > > (Builds.stoptime, Builds.rowid))) ...> ORDER BY > ...> CASE WHEN :borderlowtime IS NULL > ...> OR :borderlowid IS NULL THEN Builds.stoptime > ...> ELSE -Builds.stoptime > ...> END DESC, > ...> CASE WHEN :borderlowtime IS NULL > ...> OR :borderlowid IS NULL THEN Builds.rowid > ...> ELSE -Builds.rowid > ...> END DESC > ...> LIMIT :nr) > ...> ORDER BY stoptime, rowid ASC; > 1|0|0|SCAN TABLE Builds > 1|1|1|SEARCH TABLE Evaluations USING INTEGER PRIMARY KEY (rowid=?) > 1|2|2|SEARCH TABLE Specifications USING COVERING INDEX > sqlite_autoindex_Specifications_1 (name=?) 1|0|0|USE TEMP B-TREE FOR > ORDER BY 0|0|0|SCAN SUBQUERY 1 > 0|0|0|USE TEMP B-TREE FOR ORDER BY > --8<---------------cut here---------------end--------------->8--- > > I don’t really know what additional index to create (and I’d rather > let SQLite do it for me, if it were possible). I don't know if there is any automated process to assist you. I have the feeling that query optimization is more an art than science. Hm. This code smells ... It looks too complicated. I don't know if this brings you further concerning performance, here are some thoughts: One problematic part is this construct: :variable IS NULL OR :variable=my_column) Here is a very simple example: sqlite> CREATE TABLE tst ( ...> id INTEGER PRIMARY KEY AUTOINCREMENT, ...> name TEXT NOT NULL, ...> age INTEGER NOT NULL); sqlite> CREATE INDEX tst_name_age_idx ON tst(name, age); sqlite> EXPLAIN QUERY PLAN ...> SELECT * FROM tst WHERE (23=23 OR id=:id); 0|0|0|SCAN TABLE tst sqlite> EXPLAIN QUERY PLAN ...> SELECT * FROM tst WHERE id=:id; 0|0|0|SEARCH TABLE tst USING INTEGER PRIMARY KEY (rowid=?) sqlite> EXPLAIN QUERY PLAN ...> SELECT * FROM tst WHERE name=:name AND age < 42; 0|0|0|SEARCH TABLE tst USING COVERING INDEX tst_name_age_idx (name=? AND age