簡體   English   中英

有關sql group by的問題

[英]a question about sql group by

我有一個名為Visiting的表,看起來像這樣:

id | visitor_id | visit_time 
-------------------------------------
 1 |          1 | 2009-01-06 08:45:02 
 2 |          1 | 2009-01-06 08:58:11
 3 |          1 | 2009-01-06 09:08:23 
 4 |          1 | 2009-01-06 21:55:23
 5 |          1 | 2009-01-06 22:03:35

我想設計出一個SQL,它可以使用戶在一個會話中訪問多少次(連續訪問間隔少於1小時)。

因此,對於示例數據,我想得到以下結果:

visitor_id | count
-------------------
         1 |     3
         1 |     2

順便說一句,我使用PostgreSQL 8.3。 謝謝!

UPDATE :更新了示例數據表中的時間戳。 對困惑感到抱歉。
更新 :我不太在乎解決方案是否是單個sql查詢,使用存儲過程,子查詢等。我只在乎如何完成它:)

這個問題有點模棱兩可,因為您是在進行假設或要求小時數必須在設定點開始,即自然查詢也將指示在兩次訪問之間的所有訪問的結果記錄為(1,2)時間為08:58和09:58。 您將不得不“告知”查詢開始時間是出於某些確定的原因訪問了1和4,否則您將獲得自然的結果集:

visitor_id | count 
--------------------
         1 | 3
         1 | 2 <- extra result starting at visit 2
         1 | 1 <- extra result starting at visit 3
         1 | 2
         1 | 1 <- extra result starting at visit 5

對於我今天脆弱的頭腦來說,額外的邏輯將變得昂貴且過於復雜,Postgres上比我更好的人可能可以解決這個問題。

我通常想通過在表中添加一個sessionkey列來解決此問題,出於性能原因,我可以對其進行廉價地分組,但是我認為這也是一個邏輯問題。 從計時中獲取會話信息對我來說似乎很危險,因為我不認為在一個小時的活動之后用戶肯定會被注銷。 大多數會話系統都是通過在一段時間不活動后使會話期滿來工作的,即,很可能9:45之后的訪問將在同一會話中進行,因為您的時段將在9:08重置。

這個問題似乎有點模糊。

由於id 3在id 1和2的一小時之內,所以情況變得更加復雜,但是如果用戶在9:50進行了訪問,則該時間應該在2的一小時之內,但不是1。

您似乎希望獲得一個平滑的總數-對於一次給定的訪問,接下來的一個小時內有多少次訪問?

也許您應該問問有多少次訪問距離您不到一個小時的訪問? 如果上次訪問不到一個小時,那么它應該“計數”嗎?

因此,您可能想要的是鏈接數少於任意數量的鏈中有多少條鏈(因此,假設9:50的訪問將包含在以id 1開頭的鏈中)。

沒有簡單的解決方案

無法在單個SQL語句中執行此操作。
以下是兩個想法:一個使用循環來計算訪問次數,另一個使用visiting表的填充方式進行更改。

循環解

但是,使用循環可以做到這一點。
(我試圖使PostgreSQL語法正確,但我不是專家)

/* find entries where there is no previous entry for */ 
/* the same visitor within the previous hour:        */ 

select v1.* , 0 visits 
into temp_table
from visiting v1
where not exists ( select 1 
                   from   visiting v2
                   where  v2.visitor_id = v1.visitor_id 
                   and    v2.visit_time < v1.visit_time 
                   and    v1.visit_time - interval '1 hour' <     v2.visit_time 
                 )  
select @rows = @@rowcount 

while @rows > 0 
begin
    update temp_table
    set    visits = visits + 1 , 
           last_time = v.visit_time 
    from   temp_table t , 
           visiting   v 
    where  t.visitor_id = v.visitor_id 
    and    v.visit_time - interval '1 hour' < t.last_time
    and    not exists ( select 1 
                        from   visiting v2 
                        where  v2.visitor_id = t.visitor_id 
                        and    v2.visit_time between t.last_time and v.visit_time 
                      ) 

    select @rows = @@rowcount 
end

/* get the result: */ 

select visitor_id, 
       visits 
from temp_table 

這里的想法是這樣做:

  • 在一個小時之內沒有事先訪問的地方進行所有訪問。
    • 這可以識別會話
  • 循環,獲取這些“首次訪問”中的每一個的下一次訪問
    • 直到不再有“下次訪問”
  • 現在,您只需讀取每個會話中的訪問次數即可。

最好的解決方案?

我建議:

  • visiting表中添加一列: session_id int not null
  • 更改進行輸入的過程,以便檢查當前訪問者的上一次訪問是否少於一個小時前。 如果是這樣,它將session_id設置為與該較早訪問的session id相同。 如果不是,它將生成一個新的session_id
  • 您可以將此邏輯觸發。

然后,您的原始查詢可以通過以下方式解決:

SELECT session_id, visitor_id, count(*)
FROM   visiting 
GROUP BY session_id, visitor_id

希望這可以幫助。 如果我犯了錯誤(我確定有),請發表評論,我會予以糾正。

PostgreSQL 8.4將具有窗口功能,屆時我們可以消除僅創建臨時表來模擬行號的目的(順序)

create table visit
(
visitor_id int not null,
visit_time timestamp not null
);




insert into visit(visitor_id, visit_time) 
values
(1, '2009-01-06 08:45:02'),
(2, '2009-02-06 08:58:11'),
(1, '2009-01-06 08:58:11'),
(1, '2009-01-06 09:08:23'),
(1, '2009-01-06 21:55:23'),
(2, '2009-02-06 08:59:11'),
(2, '2009-02-07 00:01:00'),
(1, '2009-01-06 22:03:35');




create temp table temp_visit(visitor_id int not null, sequence serial not null, visit_time timestamp not null);
insert into temp_visit(visitor_id, visit_time) select visitor_id, visit_time from visit order by visitor_id, visit_time;


select 
    reference.visitor_id, count(nullif(reference.visit_time - prev.visit_time < interval '1 hour',false))
from temp_visit reference
left join temp_visit prev 
on prev.visitor_id = reference.visitor_id and prev.sequence = reference.sequence - 1
group by reference.visitor_id;

這些之一或兩者都可以工作? 但是,兩者最終都會給您提供比您要求更多的結果列。

SELECT visitor_id,
       date_part('year', visit_time),
       date_part('month', visit_time),
       date_part('day', visit_time),
       date_part('hour', visit_time),
       COUNT(*)
  FROM visiting
 GROUP BY 1, 2, 3, 4, 5;


SELECT visitor_id,
       EXTRACT(EPOCH FROM visit_time)-(EXTRACT(EPOCH FROM visit_time) % 3600),
       COUNT(*)
  FROM visiting
 GROUP BY 1, 2;

這不可能在單個SQL中完成。 更好的選擇是在存儲過程中處理它

如果是T-SQL,我將編寫為:

SELECT  visitor_id, COUNT(id), 
        DATEPART(yy, visit_time), DATEPART(m, visit_time), 
        DATEPART(d, visit_time), DATEPART(hh, visit_time)
FROM visiting
GROUP BY
    visitor_id, 
    DATEPART(yy, visit_time), DATEPART(m, visit_time), 
    DATEPART(d, visit_time), DATEPART(hh, visit_time)

這給了我:

1   3   2009    1   6   8
1   2   2009    1   6   21

我不知道如何或是否可以在postgre中編寫它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM