2016-02-05 103 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甚至不需要有更晚的時間戳。在一個多用戶數據庫中,一個序列並不能保證這種排序。

相關問題