3

これは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 ...
...

特定の日付範囲から1 日あたり最大 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では、1日に1つのレコードしか選択できませんでした:

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'
4

3 に答える 3

3

次の回答はすべて、タイムスタンプを日付に切り捨てるために使用するdate_trunc('day',date_time)か、単にキャストするだけです。date日付の書式設定と文字列を使用してフープをジャンプする必要はありません。マニュアルの日付/時刻機能を参照してください。

このSQLFiddleは、3つの可能な答えを示しています。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は句で使用されるサブクエリをサポートしていないLIMITINため、MySQLでは機能しません(少なくとも5.5以降)。ただし、SQLite3とPostgreSQLで機能し、他のほとんどのDBでも機能するはずです。

もう1つのオプションは、必要な日付の範囲を選択し、ウィンドウ関数を使用して範囲内の行に行番号で注釈を付け、出力をフィルター処理して余分な行を除外することです。

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一意でない場合は、決定論的な結果を取得する代わりに、rankまたはdense_rankウィンドウ関数のいずれかを使用することを検討するか、 inにrow_number追加の句を追加して同点を解除します。ORDER BYrow_number

を使用する場合rank、すべての行を収めることができない場合は、どの行も含まれません。使用する場合dense_rankは、1日あたり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]));
于 2012-11-16T05:41:44.377 に答える
3

特定の日付範囲から 1 日あたり最大 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つ以上のレコードが選択される可能性があります。

  • 1 日に必要な行を定義しないため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'

    @クレイグが提案したもの:

    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);
于 2012-11-16T09:54:05.983 に答える
-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; 
于 2012-11-16T04:27:57.487 に答える