在 SQL 如何根据当前行值 select 前几行?
In SQL how to select previous rows based on the current row values?
我有一个简单的 SQL table 看起来像这样-
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
);
我想找出 table 中的每一行是否有不同的 'from_person' 和 'from_location' 与最近 3 中当前行中的 'to_person' 进行了交互分钟。
例如,在上面 table 中,对于第 4 行,除了来自孟买的 mary(当前行),来自纽约的 nancy 和来自巴塞罗那的 bob 在过去 3 分钟内也向 charlie 发送了一条消息,所以计数是 2。
同样,对于第 2 行,除了来自巴塞罗那的 bob(当前行),只有来自纽约的 nancy 向 ca(当前行)的 charlie 发送了消息,因此计数为 1
示例所需输出-
0
1
0
2
我尝试使用 window 函数,但似乎在 frame 子句中我可以指定前后的行数,但我不能指定时间本身。
这应该或多或少做到了。根据您的要求,您可能需要修改 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
众所周知,Postgres 中的每个 table 都有一个主键。或者至少应该有。如果您有一个定义预期行顺序的主键,那就太好了。
示例数据:
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
(如前两行)的相同值引起的歧义。
根据您的实际问题,这将是一个正确答案:
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_person
、from_person
和 from_location
都已定义 NOT NULL
。
Returns:
1 -- !!
1
0
2
注意结果基本上是没有意义没有额外的列,任何唯一的列组合,最好是PK。 I return 当前物理顺序中的行 - 它可以随时更改而不会发出警告。关系 table 中没有行的自然顺序。如果没有明确的 ORDER BY
子句,结果行的顺序是不可靠的。
根据您的定义,前两行(根据您显示的顺序)需要具有相同的结果:1
- 或 0
如果您不计算相同的时间戳 - [=根据您的定义,19=] 和 1
是不正确的。
在没有任何唯一键的情况下,我使用 ctid
作为穷人的代理键。更多:
- In-order sequence generation
您应该仍然在table中定义了一个主键,但这绝不是强制性的。这不是您的 table 布局中唯一可疑的细节。您可能应该使用 timestamp with time zone
进行操作,有一些 NOT NULL
约束,并且只有 person_id
列在适当规范化的设计中引用 person
table。类似于:
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 来进行查询都是完全错误的。 "next" msg_id
甚至不必有更晚的时间戳。在多用户数据库中,序列不保证任何此类内容。
我有一个简单的 SQL table 看起来像这样-
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
);
我想找出 table 中的每一行是否有不同的 'from_person' 和 'from_location' 与最近 3 中当前行中的 'to_person' 进行了交互分钟。
例如,在上面 table 中,对于第 4 行,除了来自孟买的 mary(当前行),来自纽约的 nancy 和来自巴塞罗那的 bob 在过去 3 分钟内也向 charlie 发送了一条消息,所以计数是 2。
同样,对于第 2 行,除了来自巴塞罗那的 bob(当前行),只有来自纽约的 nancy 向 ca(当前行)的 charlie 发送了消息,因此计数为 1
示例所需输出-
0
1
0
2
我尝试使用 window 函数,但似乎在 frame 子句中我可以指定前后的行数,但我不能指定时间本身。
这应该或多或少做到了。根据您的要求,您可能需要修改 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
众所周知,Postgres 中的每个 table 都有一个主键。或者至少应该有。如果您有一个定义预期行顺序的主键,那就太好了。
示例数据:
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
(如前两行)的相同值引起的歧义。
根据您的实际问题,这将是一个正确答案:
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_person
、from_person
和 from_location
都已定义 NOT NULL
。
Returns:
1 -- !!
1
0
2
注意结果基本上是没有意义没有额外的列,任何唯一的列组合,最好是PK。 I return 当前物理顺序中的行 - 它可以随时更改而不会发出警告。关系 table 中没有行的自然顺序。如果没有明确的 ORDER BY
子句,结果行的顺序是不可靠的。
根据您的定义,前两行(根据您显示的顺序)需要具有相同的结果:1
- 或 0
如果您不计算相同的时间戳 - [=根据您的定义,19=] 和 1
是不正确的。
在没有任何唯一键的情况下,我使用 ctid
作为穷人的代理键。更多:
- In-order sequence generation
您应该仍然在table中定义了一个主键,但这绝不是强制性的。这不是您的 table 布局中唯一可疑的细节。您可能应该使用 timestamp with time zone
进行操作,有一些 NOT NULL
约束,并且只有 person_id
列在适当规范化的设计中引用 person
table。类似于:
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 来进行查询都是完全错误的。 "next" msg_id
甚至不必有更晚的时间戳。在多用户数据库中,序列不保证任何此类内容。