在动态查询的行之间选择

时间:2019-08-27 20:46:27

标签: sql sqlite subquery range

比方说我有一个书桌:

CREATE TABLE book (
    -- NOTE: the app guarantees that content is ordered by id
    id INTEGER PRIMARY KEY,
    section TEXT NOT NULL,
    verse INTEGER NOT NULL,
    content TEXT NOT NULL
);

INSERT INTO book (id, section, verse, content) VALUES
    (0, "Prelude", 0, "A long long time ago"),
    (1, "Prelude", 1, "I can still remember"),
    (2, "Chap",    0, "Something happened"),
    (3, "Chap",    1, "Something else happened"),
    (4, "Chap",    2, "A weighty climax"),
    (5, "End",     0, "The end")
;

我希望能够仅通过一个SQL查询就起始诗句和章节中的所有诗句进行查询。我可以使用以下SQL来做到这一点:

SELECT id, content
FROM book
WHERE
    id BETWEEN
        (SELECT id FROM book WHERE section == "Prelude" AND verse == 1 LIMIT 1)
    AND
        (SELECT id FROM book WHERE section == "Chap" AND verse == 2 LIMIT 1)

λ sqlite3 :memory: < tmp.sql
id          content
----------  --------------------
1           I can still remember
2           Something happened
3           Something else happe
4           A weighty climax

这涉及2个子查询,但我不确定这是最好的方法。我是否可以改进此查询以不包含子查询(以减少子查询更有效的想法)?

2 个答案:

答案 0 :(得分:1)

BETWEEN子句之后的代码正在扫描表两次以返回2个ID。
但是还有另一个问题:
您事先知道哪个ID最小,哪个ID最高吗?
如果不是(可能),那么您将无法安全地在AND之前或之后设置每个返回的ID。
例如,如果您这样做:

id BETWEEN
    (SELECT id FROM book WHERE section == "Chap" AND verse == 2 LIMIT 1)
AND
    (SELECT id FROM book WHERE section == "Prelude" AND verse == 1 LIMIT 1)

一无所获。
因此,您必须将最小ID设置为下限,将最大ID设置为上限。

使用CTE,以便仅对表进行一次扫描以获取开始和结束ID:

WITH cte AS (
  SELECT MIN(id) AS fromId, MAX(id) AS toId FROM book 
  WHERE (section = "Prelude" AND verse = 1) OR (section = "Chap" AND verse = 2)
)
SELECT id, content
FROM book
WHERE id BETWEEN (SELECT fromId FROM cte) AND (SELECT toId FROM cte)

请参见demo
或与CTE进行交叉加入:

WITH cte AS (
  SELECT MIN(id) AS fromId, MAX(id) AS toId FROM book 
  WHERE (section = "Prelude" AND verse = 1) OR (section = "Chap" AND verse = 2)
)
SELECT b.id, b.content
FROM book AS b CROSS JOIN cte AS c
WHERE b.id BETWEEN c.fromId AND c.toId

请参见demo
结果:

| id  | content                 |
| --- | ----------------------- |
| 1   | I can still remember    |
| 2   | Something happened      |
| 3   | Something else happened |
| 4   | A weighty climax        |

答案 1 :(得分:1)

您的查询很好(尽管它可能不完全是我写的样子)。您想要的性能是book(section, verse)上的索引:

create index idx_book_section_verse on book(section, verse);

有了这样的索引,我可能会将条件移到FROM子句:

SELECT b.id, b.content
FROM book b CROSS JOIN
     (SELECT id
      FROM book
      WHERE section = 'Prelude' AND verse = 1 
     ) id1 CROSS JOIN
     (SELECT id
      FROM book
      WHERE section = 'Chap' AND verse = 2 
     ) id2
WHERE b.id BETWEEN id1.id AND id2.id;

采用这种形式(或您自己的形式)的每个子查询实际上只是“浸入”索引以获取一个值。那太快了。然后WHERE子句将对主键进行过滤,这也很快。