2016-02-05 106 views
3

我有一个简单的SQL表,它看起来像这 -在SQL中如何根据当前行值选择前一行?

CREATE TABLE msg (
    from_person character varying(10), 
    from_location character varying(10), 
    to_person character varying(10), 
    to_location character varying(10), 
    msglength integer, 
    ts timestamp without time zone 
); 

sample data

我想找出表中的每一行是否有不同的“from_person”和“出发地点”已与互动最近3分钟内当前行中的'to_person'。

例如,在上表中,除了孟买(当前行)的玛丽,纽约的南希和巴塞罗那的鲍勃以外,第4行还在最近3分钟内向查理发送了一条消息,因此计数为2。

类似地,对于行#2,比来自Barcelona(当前行)摆锤其他,从纽约仅南希在CA(当前行)发送的消息给Charlie所以计数为1

实施例期望的输出 -

0 
1 
0 
2 

我试过使用窗口函数,但它似乎是临时在框架子句中,我可以在前后指定行数,但我无法指定时间本身。

+1

如果您提供样本数据作为插入,将会容易得多。 –

+0

当你说“最后3分钟”时,你是指当前时间的最后3位还是记录的时间戳? –

+0

@TimJasko我的意思是记录的最后3分钟 – user375868

回答

4

众所周知的是,在Postgres的每个表都有一个主键。或者至少应该有。如果你有一个定义行的预期顺序的主键,那将是非常好的。

实施例的数据:

create table msg (
    id int primary key, 
    from_person text, 
    to_person text, 
    ts timestamp without time zone 
); 

insert into msg values 
(1, 'nancy', 'charlie', '2016-02-01 01:00:00'), 
(2, 'bob',  'charlie', '2016-02-01 01:00:00'), 
(3, 'charlie', 'nancy', '2016-02-01 01:00:01'), 
(4, 'mary', 'charlie', '2016-02-01 01:02:00'); 

查询:

select m1.id, count(m2) 
from msg m1 
left join msg m2 
on m2.id < m1.id 
and m2.to_person = m1.to_person 
and m2.ts >= m1.ts- '3m'::interval 
group by 1 
order by 1; 

id | count 
----+------- 
    1 |  0 
    2 |  1 
    3 |  0 
    4 |  2 
(4 rows) 

在缺乏可以使用功能row_number()主键的,例如:

with msg_with_rn as (
    select *, row_number() over (order by ts, from_person desc) rn 
    from msg 
    ) 
select m1.id, count(m2) 
from msg_with_rn m1 
left join msg_with_rn m2 
on m2.rn < m1.rn 
and m2.to_person = m1.to_person 
and m2.ts >= m1.ts- '3m'::interval 
group by 1 
order by 1; 

注意我已经使用row_number() over (order by ts, from_person desc)来获取您所呈现的行的序列问题。当然,你应该自己决定如何解决由于列ts(如前两行)的相同值而产生的歧义。

+0

依靠这样的代理PK的排序顺序是不正确的。 'from_person'和'from_location''没有在解决方案中表示。 –

+0

谢谢,我已经添加了一些解释。 – klin

1

这应该或多或少地做到这一点。根据您的要求,您可能需要修改中间的两个条件在where子句中:

select *, 
    (select count(*) from msg m2 
    where m2.to_person = m1.to_person 
     and m2.from_person != m1.from_person 
     and m2.from_location != m1.from_location 
     and abs(EXTRACT(EPOCH FROM (m1.ts - m2.ts))) <= 3*60) 
from msg m1 
+0

我得到错误 - 错误:语法错误处于或靠近“from” LINE 7:from msg; ^ 查询失败 PostgreSQL表示:语法错误处于或接近“from” – user375868

+0

错过了关闭''' –

+0

谢谢。我现在得到错误 - 错误:函数sum()不存在 线2:(从msg m2中选择sum(*) – user375868

1

大厦您实际问题,这将是一个正确的答案:

SELECT count(m2.to_person) AS ct_3min 
FROM msg m1 
LEFT JOIN msg m2 
    ON m2.to_person = m1.to_person 
    AND (m2.from_person, m2.from_location) <> (m1.from_person, m1.from_location) 
    AND m2.ts <= m1.ts -- including same timestamp (?) 
    AND m2.ts >= m1.ts - interval '3 min' 
GROUP BY m1.ctid 
ORDER BY m1.ctid; 

假设to_personfrom_personfrom_location都被定义NOT NULL

返回:

1 -- !! 
1 
0 
2 

注意,结果基本上是意义没有其他列,列的任何独特组合,理想情况下PK。我以当前的物理顺序返回行 - 可以随时改变而不会发出警告。关系表中没有行的自然顺序。没有明确的ORDER BY子句,结果行的顺序不可靠。

根据您的定义的前两行(根据你的显示顺序)需要有相同的结果:1 - 一个和10为其他是不正确的 - 或者0,如果你不指望相同的时间戳根据你的定义。

在没有任何唯一密钥的情况下,我使用ctid作为穷人的代理密钥。更多:

应该仍然在你的表中定义一个主键,但它绝不是强制性的。这不是你桌子布局中唯一可疑的细节。您应该使用timestamp with time zone进行操作,在适当的标准化设计中使用NOT NULL约束,并且只有person_id列引用person表。喜欢的东西:

CREATE TABLE msg (
    msg_id   serial PRIMARY KEY 
, from_person_id integer NOT NULL REFERENCES person 
, to_person_id integer NOT NULL REFERENCES person 
, msglength  integer 
, ts    timestamp with time zone 
); 

无论哪种方式,依靠您的查询的目的的替代PK将完全错误。 “下一个”msg_id甚至不需要有更晚的时间戳。在一个多用户数据库中,一个序列并不能保证这种排序。

相关问题