Как сравнить даты в полях даты и времени в Postgresql?

188

Я столкнулся со странным сценарием при сравнении дат в postgresql (версия 9.2.4 в windows). В моей таблице есть столбец с именем update_date с типом «отметка времени без часового пояса». Клиент может выполнять поиск в этом поле только с датой (например, 2013-05-03) или датой со временем (например, 2013-05-03 12:20:00). Этот столбец имеет значение как метка времени для всех строк в настоящее время и имеет ту же часть даты (2013-05-03), но разницу во времени.

Когда я сравниваю этот столбец, я получаю разные результаты. Как следующие:

select * from table where update_date >= '2013-05-03' AND update_date <= '2013-05-03' -> No results

select * from table where update_date >= '2013-05-03' AND update_date < '2013-05-03' -> No results

select * from table where update_date >= '2013-05-03' AND update_date <= '2013-05-04' -> results found

select * from table where update_date >= '2013-05-03' -> results found

Мой вопрос: как я могу сделать первый запрос возможным, чтобы получить результаты, я имею в виду, почему третий запрос работает, но не первый?

Кто-нибудь может мне помочь с этим? Заранее спасибо.

user2866264
источник

Ответы:

279

@Nicolai прав насчет приведения и почему условие ложно для любых данных. Я думаю, вы предпочитаете первую форму, потому что вы хотите избежать манипуляции с датой во входной строке, правильно? вам не нужно бояться

SELECT *
FROM table
WHERE update_date >= '2013-05-03'::date
AND update_date < ('2013-05-03'::date + '1 day'::interval);
просто кто-то
источник
Этот синтаксис ( '2013-05-03'::dateи '1 day'::interval) специфичен для PostgreSQL?
Замороженное пламя
5
@FrozenFlame да, это так. стандартный синтаксис будет CAST('2013-05-03' AS DATE) + CAST('1 day' AS INTERVAL)(IIRC). YMMV о существовании и поведении DATEи INTERVAL.
просто кто-то
@FrozenFlame правильный, ответ не работает без приведения строк к типам дат. Один актерский состав все еще отсутствует. необходимо ::DATEдобавить к первой части предложения
where
Не будет WHERE update_date::date = '2013-05-03' работать, а может быть, немного более читабельным?
MikeF
@MikeF OP сказал update_dateбыло timestamp without timezone. я предположил индекс по этому столбцу. ваш предикат не будет использовать этот индекс.
просто кто-то
46

При сравнении update_date >= '2013-05-03'postgres приводит значения к одному типу для сравнения значений. Итак, ваш «2013-05-03» был приведен к «2013-05-03 00:00:00».

Так что для update_date = '2013-05-03 14:45:00' ваше выражение будет таким:

'2013-05-03 14:45:00' >= '2013-05-03 00:00:00' AND '2013-05-03 14:45:00' <= '2013-05-03 00:00:00'

Это всегда false

Чтобы решить эту проблему, приведите update_date к date:

select * from table where update_date::date >= '2013-05-03' AND update_date::date <= '2013-05-03' -> Will return result
Nicolai
источник
1
приведение каждого update_dateв таблице к приведению одного значения параметра запроса ужасно неэффективно и гарантирует, что сервер не сможет использовать индексы для этого столбца. я склонен -1 это.
просто кто-то
3
Да, я согласен, что приведение каждого значения неэффективно, и вы можете дать -1 для этого решения. Но я описал причину проблемы и привел пример, который демонстрирует проблему. Теперь пользователь 2866264 знает, почему его запрос не возвращает ожидаемые строки, и решит, какое именно решение лучше для его уникального случая.
Николай
@Nicolai: Большое спасибо за ваш ответ. Это работает, следуя вашему ответу. Также спасибо за объяснение.
user2866264
1
@Nicolai - Учитывая то, что вы сказали о том, что Postgres расширил литерал даты до полуночи, если целью является поиск записей, отмеченных в одну дату (3 мая), будет ли этот код правильным и более эффективным: SELECT * FROM my_table WHERE update_date >= '2013-05-03' AND update_date < '2013-05-04'; (Обратите внимание на использование 4 мая а не 3-й и с МЕНЬШЕ ЗНАКА, а не меньше или равно.)
Василий Бурк
9

Используйте rangeтип. Если пользователь вводит дату:

select *
from table
where
    update_date
    <@
    tsrange('2013-05-03', '2013-05-03'::date + 1, '[)');

Если пользователь вводит метки времени, то вам не нужна ::date + 1часть

http://www.postgresql.org/docs/9.2/static/rangetypes.html

http://www.postgresql.org/docs/9.2/static/functions-range.html

Клодоальдо Нето
источник
Это лаконичный и интересный ответ!
yuriploc
2

Использовать преобразование даты для сравнения с датой: Попробуйте это:

select * from table 
where TO_DATE(to_char(timespanColumn,'YYYY-MM-DD'),'YYYY-MM-DD') = to_timestamp('2018-03-26', 'YYYY-MM-DD')
Йенки Бустаманте
источник