我可以拥有无限行数的视图吗?我不想 一次选择所有行,但是可以有一个视图 代表一个重复的每周时间表,包含任何日期的行?
我有一个数据库,其中包含有关企业及其营业时间的信息 一周的不同日子。他们的名字是:
# 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
子句而不是在括号内传递参数来调用我已经拥有的函数。
答案 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.(每天不变)
并排扩展SRF通常是不受欢迎的(并且有充分的理由,它不在SQL标准中,并且如果元素的数量不相同则表现出令人惊讶的行为)。即将发布的Postgres 9.4中的新功能WITH ORDINALITY
将允许更清晰的语法。考虑this related answer on dba.SE或类似地:
PostgreSQL unnest() with element number
我假设bit(7)
是days
最有效的数据类型。要使用它,我将它转换为第一个子查询sub
中的数组。
请注意difference between ISODOW
and DOW
as field pattern for EXTRACT()
。
除了这一行,你的功能看起来不错:
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。到目前为止,你所有的评论都是关于规划一个未知的未来,很可能永远不会出现。