Хранить день недели и время?
У меня есть вопрос из двух частей о хранении дней недели и времени в базе данных. Я использую Rails 4.0, Ruby 2.0.0 и Postgres.
У меня есть определенные события, и у этих событий есть расписание. Например, для мероприятия "Прыжки с парашютом" у меня может быть вторник, среда и 15:00.
- Могу ли я сохранить запись для вторника и среды в одном ряду или мне нужно две записи?
- Как лучше всего хранить день и время? Есть ли способ сохранить день недели и время (не datetime) или это должны быть отдельные столбцы? Если они должны быть отдельными, как бы я сохранил день недели? Я думал о сохранении их как целочисленных значений, 0 для воскресенья, 1 для понедельника, так как это
wday
метод для класса Time делает это.
Любые предложения будут очень полезны.
4 ответа
Есть ли способ сохранить запись для вторника и среды в одну строку или мне нужно иметь две записи?
Есть несколько способов сохранить несколько временных диапазонов в одной строке. @bma уже предоставила пару из них. Это может быть полезно для экономии места на диске с помощью очень простых временных шаблонов. Чистый, гибкий и "нормализованный" подход заключается в сохранении одной строки за интервал времени.
Как лучше всего хранить день и время?
Использовать timestamp
(или же timestamptz
если вам приходится иметь дело с несколькими часовыми поясами). Выберите произвольную "промежуточную" неделю и просто игнорируйте часть даты при использовании аспекта дня и времени timestamp
, Самый простой и быстрый в моем опыте, и все проверки работоспособности, связанные с датой и временем, встроены автоматически. Я использую диапазон, начинающийся с 1996-01-01 00:00
по нескольким аналогичным приложениям по двум причинам:
- Первые 7 дней недели совпадают с днем месяца (для
sun = 7
). - Это самый последний високосный год (с 29 февраля для годовых моделей) в то же время.
Тип диапазона
Поскольку вы на самом деле имеете дело с временными диапазонами (а не просто "день и время"), я предлагаю использовать встроенный тип диапазона tsrange
(или же tstzrange
). Основное преимущество: вы можете использовать арсенал встроенных функций диапазона и операторов. Требуется Postgres 9.2 или более поздняя версия.
Например, вы можете создать на основе этого ограничения на исключение (реализованные внутри посредством полностью функционального индекса GiST, который может обеспечить дополнительное преимущество), чтобы исключить перекрывающиеся временные диапазоны. Рассмотрите этот связанный ответ для деталей:
Для этого конкретного исключения (без перекрывающихся диапазонов на событие) необходимо включить целочисленный столбец event_id
в ограничении, поэтому вам необходимо установить дополнительный модуль btree_gist. Установите один раз для каждой базы данных с:
CREATE EXTENSION btree_gist; -- once per db
Или вы можете иметь один простой CHECK
ограничение для ограничения разрешенного периода времени с помощью оператора "диапазон содержит" <@
,
Может выглядеть так:
CREATE TABLE event (event_id serial PRIMARY KEY, ...);
CREATE TABLE schedule (
event_id integer NOT NULL REFERENCES event(event_id)
ON DELETE CASCADE ON UPDATE CASCADE
, t_range tsrange
, PRIMARY KEY (event_id, t_range)
, CHECK (t_range <@ '[1996-01-01 00:00, 1996-01-09 00:00)') -- restrict period
, EXCLUDE USING gist (event_id WITH =, t_range WITH &&) -- disallow overlap
);
Для еженедельного расписания используйте первые семь дней, пн-вс или все, что вам подходит. Ежемесячные или годовые графики аналогичным образом.
Как извлечь день недели, время и т. Д.?
@CDub предоставил модуль для работы с этим на Ruby. Я не могу это комментировать, но вы можете делать все и в Postgres, с безупречной производительностью.
SELECT ts::time AS t_time -- get the time (practically no cost)
SELECT EXTRACT(DOW FROM ts) AS dow -- get day of week (very cheap)
Или аналогичным образом для типов диапазона:
SELECT EXTRACT(DOW FROM lower(t_range)) AS dow_from -- day of week lower bound
, EXTRACT(DOW FROM upper(t_range)) AS dow_to -- same for upper
, lower(t_range)::time AS time_from -- start time
, upper(t_range)::time AS time_to -- end time
FROM schedule;
ISODOW
вместо DOW
за EXTRACT()
возвращается 7
вместо 0
по воскресеньям Существует длинный список того, что вы можете извлечь.
Этот связанный ответ демонстрирует, как использовать оператор типа диапазона для вычисления общей продолжительности для временных диапазонов (последняя глава):
В зависимости от того, насколько сложны ваши потребности в планировании, вы можете захотеть взглянуть на RFC 5545, формат данных планирования iCalendar, для идей о том, как хранить данные.
Если вам нужно довольно просто, то это, вероятно, излишне. Postgresql имеет много функций для преобразования даты и времени в любой формат, который вам нужен.
Для простого способа сохранения относительных дат и времени вы можете сохранить день недели как целое число, а время как TIME
тип данных. Если у вас может быть несколько действующих дней недели, вы можете использовать ARRAY.
Например.
- ARRAY [2,3]:: INTEGER [] = вт, ср как день недели
- '15:00:00':: ВРЕМЯ = 15:00
[РЕДАКТИРОВАТЬ: Добавить несколько простых примеров]
/* Custom the time and timetz range types */
CREATE TYPE timerange AS RANGE (subtype = time);
--drop table if exists schedule;
create table schedule (
event_id integer not null, /* should be an FK to "events" table */
day_of_week integer[],
time_of_day time,
time_range timerange,
recurring text CHECK (recurring IN ('DAILY','WEEKLY','MONTHLY','YEARLY'))
);
insert into schedule (event_id, day_of_week, time_of_day, time_range, recurring)
values
(1, ARRAY[1,2,3,4,5]::INTEGER[], '15:00:00'::TIME, NULL, 'WEEKLY'),
(2, ARRAY[6,0]::INTEGER[], NULL, '(08:00:00,17:00:00]'::timerange, 'WEEKLY');
select * from schedule;
event_id | day_of_week | time_of_day | time_range | recurring
----------+-------------+-------------+---------------------+-----------
1 | {1,2,3,4,5} | 15:00:00 | | WEEKLY
2 | {6,0} | | (08:00:00,17:00:00] | WEEKLY
Первая запись может быть прочитана как: мероприятие действительно в 15:00 с понедельника по пятницу, причем это расписание происходит каждую неделю.
Вторая запись может быть прочитана как: событие действует в субботу и воскресенье с 8 утра до 5 вечера, происходящее каждую неделю.
Тип настраиваемого диапазона "timerange" используется для обозначения нижней и верхней границ вашего временного диапазона.
"(" Означает "включительно", а завершающий "]" означает "исключительно", или, другими словами, "больше или равно 8 утра и меньше 5 вечера".
Проверьте драгоценный камень ice_cube ( ссылка).
Он может создать для вас объект расписания, который вы можете сохранить в своей базе данных. Вам не нужно создавать две отдельные записи. Во второй части вы можете создать расписание на основе любого правила, и вам не нужно беспокоиться о том, как оно будет сохранено в базе данных. Вы можете использовать методы, предоставляемые гемом, чтобы получить любую информацию, которую вы хотите от объекта постоянного расписания.
Почему бы просто не сохранить дату и использовать встроенную функциональность для Date
получить день недели?
2.0.0p247 :139 > Date.today
=> Sun, 10 Nov 2013
2.0.0p247 :140 > Date.today.strftime("%A")
=> "Sunday"
strftime
Похоже, он может сделать все для вас. Вот конкретные документы для этого.
Специально для того, о чем вы говорите, звучит так, будто вам нужно Event
стол, который has_many :schedules
, где Schedule
будет иметь start_date
Отметка времени...