Rails / MySQL: как запросить запись по ее полной временной метке created_at? - PullRequest
2 голосов
/ 07 августа

Я хочу иметь возможность найти запись User по ее created_at метке времени (которая в данном случае: 2020-08-07 11:30:28.5934908).

Но результат такой:

User.where(created_at: '2020-08-07 11:30:28.5934908').first
=> nil

Причина того, что эта (существующая) запись пользователя не найдена, кажется очевидной в запросе MySQL, сгенерированном Rails:

User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`created_at` = '2020-08-07 11:30:28.593490' ORDER BY `users`.`id` ASC LIMIT 1

... где для по какой-то причине последний di git 8 удаляется из отметки времени 2020-08-07 11:30:28.5934908, используемой в запросе MySQL.

В чем проблема? Укорачивает ли Rails метку времени в запросе? Или MySQL это делает? Как решить эту проблему?

Ответы [ 3 ]

2 голосов
/ 08 августа

Основываясь на ваших комментариях, я думаю, что основная проблема заключается в преобразовании точного времени в микросекунды в Float. Float (хотя в ruby внутренне Double) не обладает достаточной точностью для полного представления всех дат / времени с точностью до микросекунд, как это задокументировано в классе Time (хотя они говорят о «наносекундах», что интересно). Такое преобразование только тогда пытается найти ближайшее возможное представление в Float. Округление полученного числа с плавающей запятой до 6 цифр может сработать, но я не уверен, что это всегда будет работать…

Предположим, что реальное время, хранящееся в БД, равно 2020-08-07 11:30:28.593491. Как вы заметили, это преобразуется в Float неточно:

>> Time.parse('2020-08-07 11:30:28.593491').to_f
=> 1596792628.5934908

Гарантированный метод заключался бы в использовании вместо этого рационального числа , т.е. to_r:

>> Time.parse('2020-08-07 11:30:28.593491').to_r
=> (1596792628593491/1000000)

Чтобы восстановить время обратно из рационального числа, вы можете использовать Time.at:

>> Time.at(Rational(1596792628593491, 1000000)).usec
=> 593491

Обратите внимание, что микросекунды здесь полностью сохранены.

Таким образом, точное сохранение времени created_at и его последующее использование для поиска записи предполагает использование числовой переменной Rational вместо Float:

>> user_created_at = User.first.created_at.to_r
=> (1596792628593491/1000000)

>> User.where(created_at: Time.at(user_created_at)).first == User.first
=> true

Альтернативным подходом может быть сохранение обоих целых секунд с начала эпохи. (User.first.created_at.to_i) и долю наносекунд (User.first.created_at.usec) отдельно в двух переменных. Их также можно использовать в Time.at, чтобы восстановить время назад.

В качестве примечания, это также обсуждалось в выпуске Rails с аналогичный вывод.

1 голос
/ 07 августа

Это не прямой ответ на ваш вопрос, а своего рода обходной путь. Вы можете запросить столбец created_at за очень маленькую временную дельту, как это.

User.where(created_at: '2020-08-07 11:30:28.593490'...'2020-08-07 11:30:28.593491')
0 голосов
/ 07 августа

Согласно комментарию @ BoraMa (спасибо!), Отметка времени MySQL created_at должна состоять только из 6 цифр. Таким образом, рабочий запрос будет выглядеть так:

User.where(created_at: '2020-08-07 11:30:28.5934908'.round(6)).first
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...