On Tue, Dec 14, 2010 at 05:09:04PM +0000, Simon Slavin wrote:
> I recently found out that when you use LIMIT in SQLite the engine
> still processes all applicable records even if it only has to return
> the number you asked for.  I suspect that this makes something I used
> to do inefficient.  So let me ask for what I want and see what the
> list comes up with.
> 
> I start with three things:
> 
> * an arbitrary table which definitely has an explicitly defined field
> 'id' which is always an alias to SQLite's use of 'rowid'.  The table
> may have tens of thousands of rows.
> 
> * the value of 'id' of a particular record

Use this to get the values of the columns desired for the ORDER BY.

> * an ORDER BY string that can be applied to this table, e.g.
> 'surname,age DESC'

Once you have those values do two SELECTs.  One SELECT will be for the
desired ORDER BY with WHERE matching all rows that would match the rows
following the one whose id you have, LIMIT 1.  You can build the WHERE
expressions based on the columns retrieved in the above step.  The other
SELECT will have the sort order in the ORDER BY reversed, and the
matching direction in the WHERE expressions reversed as well.

E.g.,

CREATE TABLE toy(id integer primary key, a int, b int, c int);
CREATE INDEX toy_abc ON toy (a ASC, b DESC, c ASC);
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());
INSERT INTO toy (a, b, c) VALUES (random(), random(), random());

SELECT * FROM toy ORDER BY a ASC, b DESC, c ASC LIMIT 1;

7|-9084516558209846371|5509077441613329789|-6039970258732513679
4|-8048386703128611944|6994995616875512667|8999476370168858025
3|-7424117500756575148|4942101875445443496|1450545533195196343
8|-5743400534550269237|389208812082260818|-6076412160162338146
5|-5670534775733401701|1663837058082466530|-4342108509867877155
6|-3182970629602473541|-5223987460270046935|1484365513264068016
1|-3041202902673535746|-2810235477737418946|-6552445179965515592
12|-2658836410255605115|1738314160220639562|-5959979395208249554
11|-189594377855007160|-8847915915461491005|3035343813815035327
9|61938152197633097|23466571579391398|5202656507889790288
2|872885551150985642|-6337095019136234251|-283494106466248099
14|3088016326372574532|-562863847042835080|-7774010663666943917
13|4053781913908032366|297289804958612507|3778649002432168869
10|7412852161679323896|-1652518713928674542|2034739374121838177


-- given row id, say, 6, get the values we need below
SELECT a, b, c FROM toy WHERE id = 6;
-- Just for this example I'm not using '?', so you can try it with the
-- command shell.
-- Get the row id of the row immediately following the above row:
SELECT * FROM toy
    WHERE
        a >= (SELECT a FROM toy WHERE id = 6) OR
        (a = (SELECT a FROM toy WHERE id = 6) AND
         b <= (SELECT b FROM toy WHERE id = 6)) OR
        (a = (SELECT a FROM toy WHERE id = 6) AND
         b = (SELECT b FROM toy WHERE id = 6) AND
         c >= (SELECT c FROM toy WHERE id = 6))
    ORDER BY a ASC, b DESC, c ASC LIMIT 1 OFFSET 1;
-- the above returns:
1|-3041202902673535746|-2810235477737418946|-6552445179965515592
-- get the row id of the row immediately preceding it:
SELECT * FROM toy
    WHERE
        a <= (SELECT a FROM toy WHERE id = 6) OR
        (a = (SELECT a FROM toy WHERE id = 6) AND
         b >= (SELECT b FROM toy WHERE id = 6)) OR
        (a = (SELECT a FROM toy WHERE id = 6) AND
         b = (SELECT b FROM toy WHERE id = 6) AND
         c <= (SELECT c FROM toy WHERE id = 6))
    ORDER BY a DESC, b ASC, c DESC LIMIT 1 OFFSET 1;
-- which return:
5|-5670534775733401701|1663837058082466530|-4342108509867877155

Now check the query plan.  I get this sort of thing:

0     0              TABLE toy VIA MULTI-INDEX UNION
0     0              TABLE toy WITH INDEX toy_abc
0     0              TABLE toy USING PRIMARY KEY
0     0              TABLE toy WITH INDEX toy_abc
0     0              TABLE toy USING PRIMARY KEY
0     0              TABLE toy USING PRIMARY KEY
0     0              TABLE toy WITH INDEX toy_abc
0     0              TABLE toy USING PRIMARY KEY
0     0              TABLE toy USING PRIMARY KEY
0     0              TABLE toy USING PRIMARY KEY

Which looks about right.

> I can guarantee that there is an index on whatever my ORDER BY string
> is.

Yes, you need that.

> What I want: to be able to find the ids of the records before and
> after the one I have.

See above.  I think the above should work and be efficient given the
necessary indexes.

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

Reply via email to