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 中)?或者视图甚至是做我想做的事情的方式?

更新

以下是有关我的表的更多详细信息。星期几保存为位,我使用位掩码选择适当的行,该位掩码在请求的一周的每一天都有一个位移位。以机智:

公司表:

# \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 标准中,并且如果元素的数量不同,它会表现出令人惊讶的行为)。即将推出的 Postgres 9.4 中的新功能WITH ORDINALITY将允许更简洁的语法。考虑dba.SE或类似的相关答案:
    PostgreSQL unnest() with element number

  • 我假设bit(7)它是最有效的数据类型days。为了使用它,我将它转换为第一个子查询中的数组sub

  • 请注意as 的字段模式之间的区别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 回答