标准SQL以及上一行和下一行值

时间:2017-02-03 11:06:17

标签: mysql sql postgresql sqlite django-models

我知道这是一个常见的问题,我已经对它进行了一些阅读。我想要的是一种高性能的方式(在一个查询中最好),用于接收基于引用行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的webframework来结果,因此它应该与 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

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

在一般情况下,要组合两个查询的结果,您可以将它们用作子查询,并将它们分为两列(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。)

答案 2 :(得分:0)

与许多现代数据库一样,Postgres与MySQL相比,支持Analytic,Window函数,也称为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'