簡體   English   中英

如何每天選擇1條以上的記錄?

[英]How to select more than 1 record per day?

這是一個postgresql問題。

PostgreSQL 8.3.3 on i686-redhat-linux-gnu, compiled by GCC gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9).

該表看起來像:

date_time           other_column
2012-11-01 00:00:00 ...
2012-11-02 01:00:00 ...
2012-11-02 02:00:00 ...
2012-11-02 03:00:00 ...
2012-11-02 04:00:00 ...
2012-11-03 05:00:00 ...
2012-11-03 06:00:00 ...
2012-11-05 00:00:00 ...
2012-11-07 00:00:00 ...
2012-11-07 00:00:00 ...
...

我想每天從特定日期范圍內最多選擇3 條記錄

例如,我想從 2012-11-02 到 2012-11-05 最多選擇 3 條記錄。 expected result是:

date_time           other_column
2012-11-02 01:00:00 ...
2012-11-02 02:00:00 ...
2012-11-02 03:00:00 ...
2012-11-03 05:00:00 ...
2012-11-03 06:00:00 ...
2012-11-05 00:00:00 ...

我在這上面花了幾個小時,但仍然無法弄清楚。 請幫我。 :(

更新:我嘗試的當前 sql 每天只能選擇一條記錄:

SELECT DISTINCT ON (TO_DATE(SUBSTRING((date_time || '') FROM 1 FOR 10), 'YYYY-MM-DD')) *
FROM myTable
WHERE  date_time >=  '20121101 00:00:00'  
AND  date_time <= '20121130 23:59:59'

以下答案均使用date_trunc('day',date_time)或僅date_trunc('day',date_time)date將時間戳截斷為日期。 沒有必要跳過日期格式和字符串的問題。 請參閱手冊中的日期/時間函數

這個 SQLFiddle 顯示了三個可能的答案: http ://sqlfiddle.com/#!12/0fd51/14,所有這些都為輸入數據產生相同的結果(但如果date_time可以有重復,則不一定是相同的結果)。

要解決您的問題,您可以使用具有限制的相關子查詢來生成 IN 列表以進行過濾:

SELECT a.date_time, a.other_column
FROM table1 a
WHERE a.date_time IN (
  SELECT b.date_time
  FROM table1 b
  WHERE b.date_time IS NOT NULL
    AND a.date_time::date = b.date_time::date
  ORDER BY b.date_time
  LIMIT 3
)
AND a.date_time::date BETWEEN '2012-11-02' AND '2012-11-05';

這應該是最可移植的方法 - 盡管它不適用於 MySQL(至少從 5.5 開始),因為MySQL 不支持IN子句中使用的子查詢中的LIMIT 不過,它適用於 SQLite3 和 PostgreSQL,並且應該適用於大多數其他數據庫。

另一種選擇是選擇您想要的日期范圍,使用窗口函數用行號注釋該范圍內的行,然后過濾輸出以排除多余的行:

SELECT date_time, other_column
FROM (
  SELECT 
    date_time, 
    other_column, 
    rank() OVER (PARTITION BY date_trunc('day',date_time) ORDER BY date_time) AS n
  FROM Table1
  WHERE date_trunc('day',date_time) BETWEEN '2012-11-02' AND '2012-11-05'
  ORDER BY date_time
) numbered_rows
WHERE n < 4;

如果可能存在聯系,即如果date_time不是唯一的,則考慮使用rankdense_rank窗口函數而不是row_number來獲得確定性結果,或者向row_numberORDER BY添加附加子句以打破平局。

如果您使用rank那么如果它不能容納所有行,它將不包含任何行; 如果您使用dense_rank ,即使它必須超過每天3 行的限制,它也會包括所有這些。

使用窗口規范也可以通過這種方式進行各種其他處理。


這是另一個使用數組聚合和切片的公式,它完全是 PostgreSQL 特有的,但很有趣。

SELECT b.date_time, b.other_column 
FROM (
  SELECT array_agg(a.date_time ORDER BY a.date_time)
  FROM table1 a
  WHERE a.date_time::date BETWEEN '2012-11-02' 
    AND '2012-11-05'
  GROUP BY a.date_time::date
) x(arr) 
INNER JOIN table1 b ON (b.date_time = ANY (arr[1:3]));

我想每天從特定日期范圍內最多選擇3 條記錄

SELECT date_time, other_column
FROM  (
   SELECT *, row_number() OVER (PARTITION BY date_time::date) AS rn
   FROM   tbl
   WHERE  date_time >= '2012-11-01 0:0'
   AND    date_time <  '2012-12-01 0:0'
   ) x
WHERE  rn < 4;

要點

  • 使用窗口函數row_number() 根據問題, rank()dense_rank()將是錯誤的 - 可能會選擇超過 3 條記錄,並且時間戳重復。

  • 由於您沒有定義每天需要哪些行,正確的答案是不在窗口函數中包含ORDER BY子句。 為您提供與問題匹配的任意選擇。

  • 我把你的WHERE子句從

    WHERE date_time >= '20121101 00:00:00' AND date_time <= '20121130 23:59:59'

    WHERE date_time >= '2012-11-01 0:0' AND date_time < '2012-12-01 0:0'

    對於像'20121130 23:59:59.123'這樣'20121130 23:59:59.123'情況,您的語法將失敗。

    @Craig 建議的內容:

     date_time::date BETWEEN '2012-11-02' AND '2012-11-05'

    .. 會正常工作,但在性能方面是一種反模式。 如果對表達式中的數據庫列應用強制轉換或函數,則不能使用普通索引。

PostgreSQL 8.3 解決方案

最佳解決方案升級到更新的版本,最好是當前版本 9.2。

其他解決方案

只有幾天你可以使用UNION ALL

SELECT date_time, other_column
FROM   tbl t1
WHERE  date_time >= '2012-11-01 0:0'
AND    date_time <  '2012-11-02 0:0'
LIMIT  3
)
UNION ALL 
(
SELECT date_time, other_column
FROM   tbl t1
WHERE  date_time >= '2012-11-02 0:0'
AND    date_time <  '2012-11-03 0:0'
LIMIT  3
)
...

括號在這里不是可選的。

更多的日子里有generate_series()解決方法 - 就像我在這里發布的一樣(包括指向更多的鏈接)

在我們有窗口函數之前,我可能已經用plpgsql 函數解決了這個問題:

CREATE OR REPLACE FUNCTION x.f_foo (date, date, integer
                         , OUT date_time timestamp, OUT other_column text)
  RETURNS SETOF record AS
$BODY$
DECLARE
    _last_day date;          -- remember last day
    _ct       integer := 1;  -- count
BEGIN

FOR date_time, other_column IN
   SELECT t.date_time, t.other_column
   FROM   tbl t
   WHERE  t.date_time >= $1::timestamp
   AND    t.date_time <  ($2 + 1)::timestamp
   ORDER  BY t.date_time::date
LOOP
   IF date_time::date = _last_day THEN
      _ct := _ct + 1;
   ELSE
      _ct := 1;
   END IF;

   IF _ct <= $3 THEN
      RETURN NEXT;
   END IF;

   _last_day := date_time::date;
END LOOP;

END;
$BODY$ LANGUAGE plpgsql STABLE STRICT;

COMMENT ON FUNCTION f_foo(date3, date, integer) IS 'Return n rows per day
$1 .. date_from (incl.)
$2 .. date_to  (incl.)
$3 .. maximim rows per day';

稱呼:

SELECT * FROM f_foo('2012-11-01', '2012-11-05', 3);

我會使用子選擇和左外連接。 這應該可以解決問題:

select distinct(date_format(a.date_time,"%Y-%m-%d")) date_time, b.* from table a
left outer join (
  select date_format(date_time,"%Y-%m-%d") dt, * from table limit 3
) b 
on date_format(a.date_time,"%Y-%m-%d") = b.dt; 

暫無
暫無

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

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