【问题标题】:Best way to store days of the week in a single column将星期几存储在单个列中的最佳方法
【发布时间】:2019-04-08 05:50:07
【问题描述】:

我有一个通知表,有人可以在其中设置要通知的星期几:

sun mon tue wed thu fri sat

现在我可以将它们创建为布尔类型的单独列。

是否可以将这些存储在单个列中?

我需要查询以下信息:

select * from notifications where start_date >= now and is_monday = true

制作一个不同的列会更明智还是某种整数/位掩码?不确定如何在 db 列中执行此操作,以及这是否是个好主意。

【问题讨论】:

  • 这个问题在姊妹站点 DBA Stack Exchange 中可能会更好。
  • 为了清楚起见,请编辑您的问题。如果您阅读答案和评论,我们会感到困惑。

标签: sql postgresql dayofweek


【解决方案1】:

你可以创建枚举类型:

create type day_of_week as enum (
    'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
);

并将其用作列类型,例如:

create table notifications (
    id serial primary key,
    event text,
    start_date date,
    day_of_week day_of_week
);

您的查询可能如下所示:

select * 
from notifications 
where start_date >= current_date
and day_of_week = 'mon'

请注意,类型的值是有序的,因此您可以,例如:

select * 
from notifications 
where day_of_week between 'mon' and 'thu'

使用函数to_char()从一个日期得到day_of_week

select to_char(current_date, 'dy')::day_of_week as dow

 dow 
-----
 sun
(1 row) 

更新。如果每个事件可能有更多通知,请使用 day_of_week 数组,例如:

create table notifications (
    id serial primary key,
    event text,
    start_date date,
    days_of_week day_of_week[]
);

insert into notifications values
(default, 'some event', '2018-10-01', '{mon, wed}');

select * 
from notifications 
where current_date >= start_date
and 'mon' = any(days_of_week);

【讨论】:

  • 仅供参考,日期时间处理的国际标准 ISO 8601 将一周定义为运行周一至周日。 Java 枚举 DayOfWeek 也是如此。
【解决方案2】:

我认为您不需要存储另一列,而是使用依赖于 start_date 列和您的日期参数为 sunmontue ... 等的排序规则。

with notifications(start_date) as
(
  select date'2018-11-04'      
)
select *
  from notifications
 where start_date <= now() 
   and substring(to_char(start_date, 'day'),1,3) = 'sun' -- 'mon', 'tue' ...

Rextester Demo

【讨论】:

  • 好点。除了日期之外还存储星期几是多余的,并且违反了规范化。
  • 如果我很好地理解了这个问题,通知的日期不会被存储。目标是:从某个日期开始每周一通知我。不清楚开始日期是否必须是星期一。此外,是否可以在不更改开始日期的情况下修改星期几。
  • @klin 问题不清楚清楚,但我认为问题中的这部分示例 SQL,where start_date &gt;= now 表示存储日期值的列。
  • @BasilBourque - 显然,该列存储开始日期,而不是通知日期。
【解决方案3】:

tl;博士

从 start_date >= now 和 is_monday = true 的通知中选择 *

SELECT * 
FROM event_ 
WHERE 
    when_ >= DATE '2018-01-22' 
    AND
    EXTRACT( isodow FROM when_ )  -- Access portion of date-value in the `isodow` field meaning the day-of-week as defined by the ISO 8601 standard.
    = 2                           -- 2 = Tuesday in ISO 8601 definition of a week.
;

如果要检查多个星期几的值,请使用数组。

SELECT * 
FROM event_ 
WHERE 
    when_ >= DATE '2018-01-22' 
    AND
    EXTRACT( isodow FROM when_ )  -- Access portion of date-value in the `isodow` field meaning the day-of-week as defined by the ISO 8601 standard.
    = 
    ANY( ARRAY[ 2 , 3 , 4 ] )     -- Define numbers for day-of-week per ISO 8601 where Monday-Sunday is 1-7. 
;

详情

存储星期几的最佳方式

不要。

正如Answer by Barbaros Özhan 中所建议的那样,除了日期之外还存储星期几是多余的,因此违反了规范化。

Postgres 提供EXTRACT 函数来返回一个值(一个“字段”),代表日期时间值的某些方面。 Ingres 样式的等效项也可用,date_part

Postgres offers two fields 确定星期几:

  • isodow
    根据ISO 8601 标准返回 1-7 表示星期几 Sunday-Monday as defined

  • dow
    返回 0 到 6 表示星期天到星期六,在某些地方(例如美国)定义为一周。

我建议你坚持第一个,标准定义。

让我们使用它们。

首先,找个日期。

SELECT DATE '2018-01-23' ;

2018-01-23

获取该日期的星期几。

SELECT EXTRACT ( isodow FROM DATE '2018-01-23' ) ;

返回一个双精度数值 2,表示星期二。

2

array 中定义您的目标星期几值。例如,周二-周三-周四,ISO 8601 星期编号中的 2-4。

ARRAY[ 2 , 3 , 4 ]

询问数组是否包含某个数字。

SELECT ARRAY[ 2 , 3 , 4 ] @> ARRAY[ 2 ] ;

是的

或者,获得相同结果的另一种方法。

SELECT 2 = ANY ( ARRAY[ 2 , 3 , 4 ] ) ;

是的

在该声明中应用某个日期。

SELECT EXTRACT ( isodow FROM DATE '2018-01-23' )  = ANY ( ARRAY[ 2 , 3 , 4 ] ) ;

是的

我们可以把所有这些放在一起。我们将创建一个表,插入 3 行,然后进行查询。我们预计我们的第 2 行和第 3 行可以找到 ISO 8601 星期 2-4,而 1 月 22 日的第 1 行是星期一(第 1 天),应该省略。

DROP TABLE IF EXISTS event_
;

CREATE TABLE IF NOT EXISTS event_ (
    pkey_       INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY ,
    when_       DATE NOT NULL
);

INSERT INTO event_ ( when_ ) 
VALUES ( DATE '2018-01-22' ) , ( DATE '2018-01-23' ) , ( DATE '2018-01-24' )
;

SELECT * FROM event_ 
WHERE EXTRACT( isodow FROM when_ ) = ANY( ARRAY[ 2 , 3 , 4 ] )
;

运行时,果然第一行省略了。

2“2018-01-23”

3“2018-01-24”

在 x86_64-apple-darwin 上使用 pgAdmin 3.5 和 PostgreSQL 11.0 进行演示,由 Apple LLVM 版本 6.0 (clang-600.0.54)(基于 LLVM 3.5svn)编译,64 位。

存储星期值列表

你的问题不是很清楚。

也许您还问如何存储每周 7 天值中的哪一个是每个用户的偏好。

首先为一周中的 7 天值定义一个枚举。这提供了类型安全并确保了有效值。

CREATE TYPE ISO_DOW_ AS ENUM ( 
    'MONDAY' , 'TUESDAY' , 'WEDNESDAY' , 'THURSDAY' , 'FRIDAY' , 'SATURDAY' , 'SUNDAY' 
) ;

定义一个表来存储用户偏好。我们添加一个数组类型的列,一个整数数组。

CREATE TABLE user_pref_ (
    pkey_       INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY ,
    dows_       ISO_DOW_[] NOT NULL -- User can select 0, 1, or up to 7 day-of-week values to be used for searching notifications.
) ;

请务必使用 Postgres 中漂亮的 COMMENT 功能来记录我们的意图。我将把它作为练习留给读者。

插入一些行。

INSERT INTO user_pref_ ( dows_ )
VALUES ( ARRAY[]::ISO_DOW_[] ) , ( ARRAY[ 'TUESDAY' , 'WEDNESDAY' , 'THURSDAY' ]::ISO_DOW_[] ) , ( ARRAY[ 'FRIDAY' ]::ISO_DOW_[] )
;

最后一步,我还不能完全完成。我还没有确定在子查询中检索数组以传递给外部查询的ANY 函数的方法。应该是这样的:

SELECT * FROM event_
WHERE EXTRACT( isodow FROM when_ ) = ANY(
    SELECT dows_ FROM user_pref_ WHERE pkey_ = 2 ;
) ;

【讨论】:

  • 用户可以选择一周中的 0 天或更多天。所以它可能是 0 天、1 天或 5 天或每天。
  • @Blankman 因此,请分别使用 0、1、5 或 7 个元素的 ARRAY。空数组仍然是有效数组。我错过了你评论的重点吗?
  • 在您的评论中“除了日期之外存储星期几是多余的,因此违反了规范化”。开始日期是通知开始的时间。但是他们跑哪一天是另一回事,所以它不是多余的。
  • @Blankman 现在你又把我们带回到你的问题令人困惑的模棱两可了。您是否在问如何存储用户存储的星期几的偏好?这将是另一个表中ARRAY 类型的不同字段(不是notification 表),您未指定(类似于user_pref 表,列user_idiso_dows 类型为ARRAY )。您似乎将(a)提取存储日期值的星期几)与(b)用户选择的 0-7 星期几值混为一谈。我建议重新考虑,然后编辑您的问题以澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-03
  • 1970-01-01
  • 2013-06-16
  • 2010-09-23
  • 2012-07-01
  • 2010-10-05
  • 2011-04-16
相关资源
最近更新 更多