4

行数が無限のビューを作成できますか? 一度にすべての行を選択したくありませんが、任意の日付の行を含む毎週の繰り返しスケジュールを表すビューを持つことは可能ですか?

私は、企業、曜日ごとの営業時間に関する情報を含むデータベースを持っています。彼らの名前:

# SELECT company_name FROM company;
     company_name
--------------------
 Acme, Inc.
 Amalgamated
...
(47 rows)

毎週のスケジュール:

# SELECT days, open_time, close_time
  FROM   hours JOIN company USING(company_id)
  WHERE  company_name='Acme, Inc.';
   days  | open_time | close_time
---------+-----------+-----------
 1111100 | 08:30:00  | 17:00:00
 0000010 | 09:00:00  | 12:30:00

表示されていない別のテーブルには、休業日があります。

したがって、特定の日付を引数として取り、各会社の営業時間を返すストアド プロシージャの形式で、ユーザー定義関数を簡単に作成できます。

SELECT company_name,open_time,close_time FROM schedule_for(current_date);

しかし、次のように、SQL 互換のホスト言語ライブラリとのインターフェイスに問題がないように、テーブル クエリとして実行したいと考えています。

SELECT company_name, open_time, close_time
FROM   schedule_view
WHERE  business_date=current_date;

リレーショナル データベース理論によると、テーブル (リレーション) は、各主キーから行 (タプル) への一意のマッピングであるという意味で関数です。明らかWHEREに、上記のクエリの句が省略された場合、テーブル (ビュー) の行数が無限になり、これは実際的な問題になります。WHERE しかし、行数を制限する句なしでそのようなビューをクエリしないことに同意したいと思います。

そのようなビューを (PostgreSQL で) どのように作成できますか? それとも、ビューは私がやりたいことをする方法でもありますか?

アップデート

ここに私のテーブルに関するいくつかの詳細があります。曜日はビットとして保存され、要求された曜日ごとに 1 ビットずつシフトされるビット マスクを使用して、適切な行を選択します。ウィット:

会社のテーブル:

# \d company
               Table "company"
     Column     |          Type          | Modifiers 
----------------+------------------------+-----------
 company_id     | smallint               | not null
 company_name   | character varying(128) | not null
 timezone       | timezone               | not null

時間表:

# \d hours
                  Table "hours"
   Column   |          Type          | Modifiers 
------------+------------------------+-----------
 company_id | smallint               | not null
 days       | bit(7)                 | not null
 open_time  | time without time zone | not null
 close_time | time without time zone | not null

休日テーブル:

# \d holiday 
           Table "holiday"
    Column     |   Type   | Modifiers 
---------------+----------+-----------
 company_id    | smallint | not null
 month_of_year | smallint | not null
 day_of_month  | smallint | not null

私が現在持っている機能は、(呼び出し以外に)私が望むことを次のように定義されています。

CREATE FUNCTION schedule_for(requested_date date)
RETURNS table(company_name text, open_time timestamptz, close_time timestamptz)
AS $$
WITH field AS (
  /* shift the mask as many bits as the requested day of the week */
  SELECT B'1000000' >> (to_char(requested_date,'ID')::int -1) AS day_of_week,
  to_char(requested_date, 'MM')::int AS month_of_year,
  to_char(requested_date, 'DD')::int AS day_of_month
)
  SELECT company_name,
         (requested_date+open_time) AT TIME ZONE timezone AS open_time,
         (requested_date+close_time) AT TIME ZONE timezone AS close_time
  FROM hours INNER JOIN company USING (company_id)
       CROSS JOIN field
       CROSS JOIN holiday
         /* if the bit-mask anded with the DOW is the DOW */
  WHERE (hours.days & field.day_of_week) = field.day_of_week
  AND NOT EXISTS (SELECT 1
                  FROM holiday h
                  WHERE h.company_id = hours.company_id
                  AND   field.month_of_year = h.month_of_year
                  AND   field.day_of_month = h.day_of_month);
$$
LANGUAGE SQL;

繰り返しますが、私の目標は、これを行うことで今日のスケジュールを取得できるようにすることです。

SELECT open_time, close_time FROM schedule_view
wHERE  company='Acme,Inc.' AND requested_date=CURRENT_DATE;

また、次のようにして、任意の日付のスケジュールを取得することもできます。

SELECT open_time, close_time FROM schedule_view
WHERE  company='Acme, Inc.' AND requested_date=CAST ('2013-11-01' AS date);

これには、ここで参照されているビューを作成する必要があるとschedule_view思いますが、それについては間違っているかもしれません。いずれにせよ、現在私が持っているユーザー定義関数にあるように、コマンドライン インターフェイスとクライアント言語データベース ライブラリで使用されないように、乱雑な SQL コードを隠しておきたいと考えています。

WHEREつまり、括弧内ではなく句で引数を渡すことによって、既に持っている関数を呼び出したいだけです。

4

3 に答える 3

3

再帰 CTEを使用して、無限の行を持つビューを作成できます。しかし、それでも開始点と終了条件が必要です。そうしないと、エラーが発生します。

セットを返す関数 (SRF) を使用したより実用的なアプローチ:

WITH x AS (SELECT '2013-10-09'::date AS day) -- supply your date
SELECT company_id, x.day + open_time  AS open_ts
                 , x.day + close_time AS close_ts
FROM   (
   SELECT *, unnest(arr)::bool AS open, generate_subscripts(arr, 1) AS dow
   FROM   (SELECT *, string_to_array(days::text, NULL) AS arr FROM hours) sub
   ) sub2
CROSS  JOIN x
WHERE  open
AND    dow = EXTRACT(ISODOW FROM x.day);
-- AND NOT EXISTS (SELECT 1 FROM holiday WHERE holiday = x.day)

-> SQLfiddle デモ。(一定日あり)

  • SRF を並べて展開することは、一般的に嫌われています (そして、正当な理由から、これは SQL 標準になく、要素の数が同じでない場合に驚くべき動作を示します)。WITH ORDINALITY今後の Postgres 9.4の新機能により、よりクリーンな構文が可能になります。dba.SEまたは同様に、この関連する回答を検討してください:
    PostgreSQL unnest() with element number

  • bit(7)の最も効果的なデータ型として想定していdaysます。それを操作するために、最初のサブクエリで配列に変換していsubます。

  • のフィールド パターンの違いにISODOWDOWEXTRACT()注意してください。

更新された質問

次の行を除いて、関数は適切に見えます。

CROSS JOIN holiday

それ以外の場合、ビット シフト ルートを使用すると、同様のクエリになります。

WITH x AS (SELECT '2013-10-09'::date AS day) -- supply your date
    ,y AS (SELECT day, B'1000000' >> (EXTRACT(ISODOW FROM day)::int - 1) AS dow
           FROM x)
SELECT c.company_name, y.day + open_time  AT TIME ZONE c.timezone AS open_ts
                     , y.day + close_time AT TIME ZONE c.timezone AS close_ts
FROM   hours   h
JOIN   company c USING (company_id)
CROSS  JOIN    y
WHERE  h.days & y.dow = y.dow;
AND    NOT EXISTS  ...
  • EXTRACT(ISODOW FROM requested_date)::intは、より高速な同等のものですto_char(requested_date,'ID')::int

WHERE句の「合格」日?

WHEREその作業を行うには、句でその日の行を選択する前に、考えられるすべての日をカバーする巨大な一時テーブルを生成する必要があります。可能ですが(採用しますgenerate_series())、非常に高価です。

最初のドラフトに対する私の答えは、これのより小さなバージョンですWHERE。句の日付に一致する日を選択する前に、パターンの週のみすべての行を展開します。WHERE注意が必要な部分は、句の入力から構築されたタイムスタンプを表示することです。ありえない。終日をカバーする巨大なテーブルに戻ります。あなたが少数の会社しか持たず、日付範囲がかなり狭い場合を除き、私はそこに行きません.

于 2013-10-08T02:50:12.510 に答える
1

これは、以前の回答に基づいて構築されています。

サンプルデータ:

CREATE temp TABLE company (company_id int, company text);
INSERT INTO company VALUES
  (1, 'Acme, Inc.')
 ,(2, 'Amalgamated');

CREATE temp TABLE hours(company_id int, days bit(7), open_time time, close_time time);
INSERT INTO hours VALUES
  (1, '1111100', '08:30:00', '17:00:00')
 ,(2, '0000010', '09:00:00', '12:30:00');

create temp table holidays(company_id int, month_of_year int, day_of_month int);
insert into holidays values
  (1, 1, 1),
  (2, 1, 1),
  (2, 1, 12) -- this was a saturday in 2013
;

まず、提供したロジックを使用して、日付の曜日を時間テーブルの曜日と照合するだけです。

select *
from company a
       left join hours b
         on a.company_id = b.company_id
       left join holidays c
         on b.company_id = c.company_id
where (b.days & (B'1000000' >> (to_char(current_date,'ID')::int -1)))
        = (B'1000000' >> (to_char(current_date,'ID')::int -1))
;

Postgres ではカスタム演算子を作成して where 句のような式を単純化できるため、ビット文字列と日付の間の曜日に一致する演算子が必要になる場合があります。まず、テストを実行する関数:

CREATE FUNCTION match_day_of_week(bit, date)
    RETURNS boolean
    AS $$
    select ($1 & (B'1000000' >> (to_char($2,'ID')::int -1))) = (B'1000000' >> (to_char($2,'ID')::int -1))
    $$
    LANGUAGE sql IMMUTABLE STRICT;

where 句を "where match_day_of_week(days, some-date)" のようにすることもできます。カスタム オペレータを使用すると、これが少し見栄えがよくなります。

CREATE OPERATOR == (
    leftarg = bit,
    rightarg = date,
    procedure = match_day_of_week
);

これで、その述語を単純化する構文シュガーができました。ここで、次のテストにも追加しました (休日の month_of_year と day_of_month は、指定された日付と一致しません)。

select *
from company a
       left join hours b
         on a.company_id = b.company_id
       left join holidays c
         on b.company_id = c.company_id
where b.days == current_date
  and extract(month from current_date) != month_of_year
  and extract(day from current_date) != day_of_month
;

簡単にするために、休日の月と日をカプセル化するために、追加のタイプ (別の素晴らしい postgres 機能) を追加することから始めます。

create type month_day as (month_of_year int, day_of_month int);

上記のプロセスを繰り返して、別のカスタム オペレータを作成します。

CREATE FUNCTION match_day_of_month(month_day, date)
    RETURNS boolean
    AS $$
    select extract(month from $2) = $1.month_of_year
             and extract(day from $2) = $1.day_of_month
    $$
    LANGUAGE sql IMMUTABLE STRICT;

CREATE OPERATOR == (
    leftarg = month_day,
    rightarg = date,
    procedure = match_day_of_month
);

最後に、元のクエリは次のように縮小されます。

select *
from company a
       left join hours b
         on a.company_id = b.company_id
       left join holidays c
         on b.company_id = c.company_id
where b.days == current_date
  and not ((c.month_of_year, c.day_of_month)::month_day == current_date)
;

それをビューに減らすと、次のようになります。

create view x
as
select b.days,
       (c.month_of_year, c.day_of_month)::month_day as holiday,
       a.company_id,
       b.open_time,
       b.close_time
from company a
       left join hours b
         on a.company_id = b.company_id
       left join holidays c
         on b.company_id = c.company_id
;

そして、次のように使用できます。

select company_id, open_time, close_time
from x
where days == current_date
  and not (holiday == current_date)
;

編集: ところで、このロジックに少し取り組む必要があります。これは、カスタム オペレーターを使用してそれを行う方法のアイデアを示すことに関するものでした。まず、会社に複数の休日が定義されている場合、その会社について複数の結果が返される可能性があります。

于 2013-10-24T13:09:44.747 に答える
0

同様の回答を PostgreSQL メーリング リストに投稿しました。基本的に、この状況で関数呼び出し API の使用を避けるのは、愚かな決定である可能性があります。関数呼び出しは、このユース ケースに最適な API です。関数が機能しない場所をサポートする必要がある具体的なシナリオがある場合は、それを提供してください。おそらく、そのシナリオは PostgreSQL API を危険にさらすことなく解決できます。これまでのあなたのコメントはすべて、決して実現しないかもしれない未知の未来の計画に関するものです。

于 2013-10-10T17:46:20.367 に答える