在SQL中如何根据当前行值选择以前的行?

时间:2016-02-05 20:03:27

标签: sql postgresql window-functions

我是一个简单的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'和' from_location'与#to to-person'进行了互动。在最后3分钟的当前行。

例如,在上表中,对于第4行,除了来自孟买(当前行)的玛丽,来自纽约的南希和来自巴塞罗那的鲍勃也在最后3分钟向查理发送了一条消息,因此计数为2。 / p>

同样地,对于第2行,除了来自巴塞罗那的bob(当前行)之外,只有来自纽约的nancy已向ca(当前行)发送了一条消息,因此计数为1

示例所需输出 -

0
1
0
2

我尝试使用窗口函数,但似乎在frame子句中我可以指定前后的行数,但我不能指定时间本身。

3 个答案:

答案 0 :(得分: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列的相同值引起的歧义(如前两行)。

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

答案 2 :(得分: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 - 或0如果您不计算相同的时间戳 - {{1}根据你的定义,对于一个和0对另一个是不正确的。

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

应该仍然在你的表中定义了一个主键,但它绝不是强制性的。这不是表格布局中唯一可疑的细节。您应该使用1操作,有一些timestamp with time zone个约束,并且只有NOT NULL列在正确规范化的设计中引用person_id表。类似的东西:

person

无论哪种方式,为了您的查询而依赖代理PK将是明显错误的。 “下一个”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 ); 甚至不必具有更晚的时间戳。在多用户数据库中,序列不保证任何类型。

相关问题