1

意味をなさない場合は、私の例を許してください。より多くの参加を促すために、簡略化したものを試してみるつもりです.

次のようなテーブルを検討してください。

  •        dt     |    mnth    |  foo
    --------------+------------+--------
      2012-12-01  |  December  |
        ...
      2012-08-01  |  August    |
      2012-07-01  |  July      |
      2012-06-01  |  June      |
      2012-05-01  |  May       |
      2012-04-01  |  April     |
      2012-03-01  |  March     |
        ...
      1997-01-01  |  January   |  
    

今日に最も近いレコードを検索する場合、事前に 3 つのレコード後の 7dtつのレコードを返す最良の方法は何でしょうか?

ウィンドウ関数を試すことにしました:

  • WITH dates AS (
       select  row_number() over (order by dt desc)
             , dt
             , dt - now()::date as dt_diff
       from    foo
    )
    , closest_date AS (
       select * from dates
       where dt_diff = ( select max(dt_diff) from dates where dt_diff <= 0 )
    )
    
    SELECT * 
    FROM   dates
    WHERE  row_number - (select row_number from closest_date) >= -3
       AND row_number - (select row_number from closest_date) <=  7 ;
    

ウィンドウ関数を使用して相対レコードを返すより良い方法があるに違いないと感じていますが、それらを調べてからしばらく経ちました。

4

3 に答える 3

3
create table foo (dt date);
insert into foo values
('2012-12-01'),
('2012-08-01'),
('2012-07-01'),
('2012-06-01'),
('2012-05-01'),
('2012-04-01'),
('2012-03-01'),
('2012-02-01'),
('2012-01-01'),
('1997-01-01'),
('2012-09-01'),
('2012-10-01'),
('2012-11-01'),
('2013-01-01')
;

select dt
from (
(
    select dt
    from foo
    where dt <= current_date
    order by dt desc
    limit 4
)
union all
(
    select dt
    from foo
    where dt > current_date
    order by dt
    limit 7
)) s
order by dt
;
     dt     
------------
 2012-03-01
 2012-04-01
 2012-05-01
 2012-06-01
 2012-07-01
 2012-08-01
 2012-09-01
 2012-10-01
 2012-11-01
 2012-12-01
 2013-01-01
(11 rows)
于 2012-06-05T23:36:38.613 に答える
2

ウィンドウ関数lead()を使用できます:

SELECT dt_lead7 AS dt
FROM  (
    SELECT *, lead(dt, 7) OVER (ORDER BY dt) AS dt_lead7
    FROM   foo
    ) d
WHERE  dt <= now()::date
ORDER  BY dt DESC
LIMIT  11;

多少短いですがUNION ALL、適切なインデックスを使用するとバージョンが高速になります。

これにより、「今日に最も近い日付」が最初の 7 行内にあるというコーナー ケースが残ります。これを処理するために、元のデータに の 7 行を埋め込むことができます-infinity

SELECT d.dt_lead7 AS dt
FROM  (
    SELECT *, lead(dt, 7) OVER (ORDER BY dt) AS dt_lead7
    FROM  (
        SELECT '-infinity'::date AS dt FROM generate_series(1,7)
        UNION ALL
        SELECT dt FROM foo
        ) x
    ) d
WHERE  d.dt <= now()::date -- same as: WHERE  dt <= now()::date1
ORDER  BY d.dt_lead7 DESC  -- same as: ORDER BY dt DESC 1
LIMIT  11;

何が起こるかを明確にするために、2 番目のクエリの列をテーブル修飾しました。下記参照。「今日に最も近い日付」がベース テーブルの最後の 7 行以内にある場合、
結果には値が含まれます。NULL必要に応じて、サブセレクトを追加してそれらをフィルタリングできます。


1コメント内の出力名列名に関する疑問に対処するには、マニュアルからの次の引用を検討してください。

出力列の名前を使用する場所:

出力列の名前は、 ORDER BYandGROUP BY句で列の値を参照するために使用できますが、 or句では使用できません。WHEREHAVING代わりに式を書き出す必要があります。

大胆強調鉱山。同じ名前の出力列ではなく、WHERE dt <= now()::date列を参照するため、意図したとおりに機能します。d.dt

競合の解決:

ORDER BY式が出力列名と入力列名の両方に一致する単純な名前である場合、ORDER BYはそれを出力列名として解釈しますGROUP BY これは、同じ状況で行われる選択の反対です。この矛盾は、SQL 標準との互換性を保つために行われています。

再び大胆な強調鉱山。ORDER BY dt DESCこの例では、意図したとおりに出力列の名前を参照しています。とにかく、どちらの列も同じようにソートされます。唯一の違いは、コーナー ケースの値にある可能性があります。NULLしかし、それも横ばいです。

デフォルトの動作はNULLS LASTASCが指定または暗示された場合、およびが指定されNULLS FIRSTた場合です。DESC

値は最大値のNULL来るため、順序はどちらの方法でも同じです。


または、なしでLIMIT(コメントのリクエストに従って):

WITH x AS (
    SELECT *
         , row_number() OVER (ORDER BY dt)  AS rn
         , first_value(dt) OVER (ORDER BY (dt > '2011-11-02')
                                         , dt DESC) AS dt_nearest
    FROM   foo
    )
, y AS (
    SELECT rn AS rn_nearest
    FROM   x
    WHERE  dt = dt_nearest
    )
SELECT dt
FROM   x, y
WHERE  rn BETWEEN rn_nearest - 3 AND rn_nearest + 7
ORDER  BY dt;

パフォーマンスが重要な場合でも、@Clodoaldo のUNION ALLバリアントを使用します。最速になります。データベースに依存しない SQL は、これまでのところしか役に立ちません。他の RDBMS にはまだウィンドウ関数がまったくない (MySQL) か、別の関数名 (のfirst_val代わりになどfirst_value) があります。(MS SQL)またはローカルの方言LIMITに置き換えることもできます。TOP n

于 2012-06-06T08:44:53.950 に答える
1

次のようなものを使用できます。

select * from foo 
where dt between now()- interval '7 months' and now()+ interval '3 months'

これこれがあなたを助けるかもしれません。

于 2012-06-05T23:23:05.817 に答える