SQL视图可以有无限多行吗? (重复计划,每天每行?)

时间:2013-10-08 01:10:18

标签: sql postgresql views relational-database sql-view

我可以拥有无​​限行数的视图吗?我不想 一次选择所有行,但是可以有一个视图 代表一个重复的每周时间表,包含任何日期的行?

我有一个数据库,其中包含有关企业及其营业时间的信息 一周的不同日子。他们的名字是:

# 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

另一张表(未显示)有假期关闭。

所以我可以以a的形式创建一个用户定义的函数 将特定日期作为参数的存储过程 返回每家公司的营业时间:

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子句而不是在括号内传递参数来调用我已经拥有的函数。

3 个答案:

答案 0 :(得分:3)

可以使用recursive 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 demo.(每天不变)

更新了问题

除了这一行,你的功能看起来不错:

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子句中的输入构建的时间戳。不可能。你回到了覆盖所有日子的巨大桌子。除非你只有很少的公司和相当小的日期范围,否则我不会去那里。

答案 1 :(得分: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)
;

编辑:顺便说一下,你需要对这个逻辑进行一些处理 - 这更多的是关于如何使用自定义运算符来实现它的想法。对于初学者来说,如果公司定义了多个假期,那么您可能会为该公司获得多个结果。

答案 2 :(得分:0)

我在PostgreSQL邮件列表上发布了类似的回复。基本上,在这种情况下避免使用函数调用API可能是一个愚蠢的决定。函数调用是此用例的最佳API。如果你有一个具体的场景,你需要支持一个函数不能工作的地方,那么请提供它,也许这个场景可以解决,而不必妥协PostgreSQL API。到目前为止,你所有的评论都是关于规划一个未知的未来,很可能永远不会出现。