【问题标题】:Standard SQL and previous and next row values标准 SQL 以及上一行和下一行值
【发布时间】:2017-06-20 17:42:15
【问题描述】:

我知道这是一个常见问题,并且我已经阅读了一些内容。我想要的是一种基于参考行 id 接收下一个和上一个行 id 的高性能方式(最好在一个查询中)。我在 stackoverflow 找到了很多问题和答案,还有一个有价值的线程,答案非常好 https://stackoverflow.com/a/15992856/1230358。我所拥有的是基于此线程中的答案。

select id from test_1
where ( 
    id = IFNULL((select max(id) from test_1 where id < 2 order by starts_on, id), 0)
    or id = IFNULL((select min(id) from test_1 where id > 2 order by starts_on, id), 0) 
)

使用引用id=2查询返回的正是我需要的结果(第一行是前一个id,第二行是下一个id):

id
--
1
--
3

问题是,如果查询边缘情况id=1id=max(id),结果会错过前一个 或下一行 id,因为根本没有上一行或下一行。结果现在只有一行,不清楚这是否是前一个我们的下一行 id。

id
--
2       (next value)

但是,我需要这样的结果

id
--
NULL    (or 0 - previous value)
--
2       (next value)

我需要的是一个基于或类似于上层查询的性能的解决方案,它最好用 NULL 值(或 0)填充不存在的边缘情况 id。由于我正在使用支持不同 dbms 的 web 框架计算结果,它应该适用于 mysqlsqlitepostgres。它应该适用于以下架构:

drop table if exists test_1;
create table test_1 (id INTEGER PRIMARY KEY,starts_on DATETIME, ends_on DATETIME);
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');
insert into test_1 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-01 00:00:00');

drop table if exists test_2;
create table test_2 (id INTEGER PRIMARY KEY,starts_on DATETIME, ends_on DATETIME);
insert into test_2 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-07 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-02 00:00:00', '2017-01-08 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-03 00:00:00', '2017-01-09 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-04 00:00:00', '2017-01-10 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-05 00:00:00', '2017-01-11 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-06 00:00:00', '2017-01-12 00:00:00');
insert into test_2 (starts_on, ends_on) Values ('2017-01-07 00:00:00', '2017-01-13 00:00:00');


drop table if exists test_3;
create table test_3 (id INTEGER PRIMARY KEY,starts_on DATETIME, ends_on DATETIME);
insert into test_3 (starts_on, ends_on) Values ('2017-01-01 00:00:00', '2017-01-07 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-02 00:00:00', '2017-01-08 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-02 00:00:00', '2017-01-09 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-04 00:00:00', '2017-01-10 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-05 00:00:00', '2017-01-11 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-07 00:00:00', '2017-01-12 00:00:00');
insert into test_3 (starts_on, ends_on) Values ('2017-01-07 00:00:00', '2017-01-13 00:00:00');

更新:

一个可能的解决方案是:

select distinct 
(select max(id) from test_1 where id < 7 order by starts_on, id) as prev, 
(select min(id) from test_1 where id > 7 order by starts_on, id) as next 
from test_1

【问题讨论】:

  • 什么是 dbms?
  • 抱歉忘记提了。应该是 mysql、sqlite 和 postgres。
  • 窗口函数标准 SQL,但与 SQLite 使用的标准版本不同。
  • 知道了@CL。使用标准,我认为我在表达它适用于每个主要的数据库平台。我的坏

标签: mysql sql postgresql sqlite django-models


【解决方案1】:

Postgresql Window Functions

select
    lag(id) over (order by starts_on) as previous,
    lead(id) over (order by starts_on) as next
from test_1
where id = 2

【讨论】:

  • 好吧,问题是我必须使用 web 框架,它必须是支持 mysql、sqlite 和 postgres 的标准 sql。据我所知,sqlite3 不支持窗口函数。抱歉,它不是来自我的问题文本。我将添加该信息。感谢您提供此解决方案!
  • @hetsch 这个答案“标准 SQL”
  • @a_horse_with_no_name 是的,我的错。我误解了“标准”的含义。我天真地认为这意味着在许多 dbms 中工作的最小公分母。
  • @hetsch:该查询确实适用于“许多”DBMS。只有 MySQL(以及某种程度上的 SQLite)不支持modern SQL。现在连 MariaDB 都有窗口函数和通用表表达式
  • @a_horse_with_no_name。在玩弄了这个解决方案之后,它在 postgres 中似乎对我不起作用。如果您能快速浏览一下这个小要点rextester.com/QUHB68625,我将不胜感激。谢谢!
【解决方案2】:

在一般情况下,要合并两个查询的结果,您可以将它们用作子查询,或者将它们放入两列(如scalar subqueries):

SELECT (SELECT ...) AS a, (SELECT ...) AS b;

或分成两行:

SELECT * FROM (SELECT ...
               UNION ALL
               SELECT NULL
               LIMIT 1)
UNION ALL
SELECT * FROM (SELECT ...
               UNION ALL
               SELECT NULL
               LIMIT 1);

SELECT NULL LIMIT 1 构造确保在实际查询未返回行时返回 NULL。)

【讨论】:

  • 完美!所以在我的情况下,这似乎有效:select distinct IFNULL((select max(id) from test_1 where id &lt; 7 order by starts_on, id), 0) as prev, IFNULL((select min(id) from test_1 where id &gt; 7 order by starts_on, id), 0) as next from test_1您是否看到此查询在大型数据集上存在任何性能问题?谢谢
  • 这些可能比使用 OR 的单个查询更容易优化,但实际上它有多快是您必须自己衡量的。
【解决方案3】:

Postgres 与许多现代数据库一样,与 MySQL 不同,支持分析、窗口函数,也称为 OLAP 函数。

您需要的是分析 LEAD() 和 LAG() 函数的组合。您需要将它们与 COALESCE() 函数结合起来,因为据我所知,PostGres 不支持 NVL() 或 IFNULL(),如果您想要 NULL 以外的其他东西。如果您在starts_onends_on 日期工作,这里是starts_on 的示例。

SELECT
  COALESCE(LAG(starts_on) OVER (ORDER BY starts_on),'1900-01-01 00:00:00')
  AS neighbour_starts_on
FROM test_1
WHERE starts_on = '2017-01-07 00:00:00'
UNION ALL SELECT
  COALESCE(LEAD(starts_on) OVER (ORDER BY starts_on),'9999-12-31 23:59:59')
  AS neighbour_starts_on
FROM test_1
WHERE starts_on = '2017-01-07 00:00:00'

【讨论】:

    猜你喜欢
    • 2016-06-29
    • 2016-06-29
    • 2014-10-29
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    相关资源
    最近更新 更多