Найдите следующие дни рождения, чтобы отпраздновать

У меня есть модель пользователя в моем проекте phoenixframework с атрибутом дня рождения, который содержит значение Timex.Ecto.Date.

defmodule MyProject.User do
  use MyProject.Web, :model

  schema "users" do
    field :active, :boolean
    field :birthday, Timex.Ecto.Date
    field :login, :string
    field :email, :string
    field :password, :string, virtual: true
    field :password_hash, :string
    field :name, :string
    field :nickname, :string

    timestamps
  end

  # ... changeset and other code ...
end

Теперь я пытаюсь найти всех пользователей, которые будут отмечать свой день рождения в ближайшие 30 дней. Предположим, что существует запись пользователя с датой рождения 1980-02-01. Сегодня 2017-01-13.

Что у меня есть сейчас:

{:ok, date_from} =
  Timex.local
  |> Timex.Ecto.Date.cast

{:ok, date_to} =
  Timex.local
  |> Timex.add(Timex.Duration.from_days(30))
  |> Timex.Ecto.Date.cast

MyProject.Repo(from(u in MyProject.User, where: u.birthday >= ^date_from, where: u.birthday <= ^date_to))

Из-за года он не может работать. Как мне построить экто-запрос?


person guitarman    schedule 13.01.2017    source источник
comment
Хотя я не знаю ответа, может быть полезнее спросить у сообщества базу данных, которую вы используете. например, задайте вопрос с тегом SQL, если вы используете базу данных SQL. Как только вы узнаете, как выполнять необработанный запрос, будет довольно просто перевести его в экто.   -  person Justin Wood    schedule 14.01.2017
comment
О, извините, я забыл сказать, что использую базу данных PostgreSQL.   -  person guitarman    schedule 14.01.2017


Ответы (2)


В PostgreSQL я бы использовал age, чтобы получить интервал между датой рождения и настоящим, усечь его до года, добавить его к дате рождения, чтобы получить следующий день рождения, а затем посмотреть, будет ли он в течение следующих 30 дней:

postgres=# select current_date;
    date
------------
 2017-01-14
(1 row)

postgres=# select '2000-01-20'::date + date_trunc('year', age('2000-01-20'::date)) + interval '1 year' <= current_date + interval '30 days';
 ?column?
----------
 t
(1 row)

postgres=# select '2000-02-20'::date + date_trunc('year', age('2000-02-20'::date)) + interval '1 year' <= current_date + interval '30 days';
 ?column?
----------
 f
(1 row)

С Ecto это должно выглядеть так (не проверено):

from(u in User, where: fragment("? + date_trunc('year', age(?)) + interval '1 year' <= current_date + interval '30 days'", u.birthday, u.birthday))

Теперь также тривиально изменить интервал на '1 month', и PostgreSQL правильно добавит количество дней в текущем месяце и даст вам пользователей, у которых день рождения в течение 1 месяца, а не в течение 30 дней.

person Dogbert    schedule 14.01.2017
comment
ОП: этот ответ определенно лучше моего. - person Aleksei Matiushkin; 14.01.2017
comment
Это очень хорошее решение, и оно работает, если я использую его, как вы предложили. Но если я использую другой параметр, просто для гибкости, он не работает: days = 30; from(u in User, where: fragment("? + date_trunc('year', age(?)) + interval '1 year' <= current_date + interval '? days'", u.birthday, u.birthday, ^days)) Это приводит к ошибке: (ArgumentError) parameters must be of length 0 for query %Postgrex.Query - person guitarman; 15.01.2017
comment
@гитарман попробуй interval '1 day' * ?. Также проверьте этот ответ, который я написал несколько дней назад: stackoverflow.com/a/41571998/320615. - person Dogbert; 15.01.2017
comment
Большое вам спасибо за вашу помощь. - person guitarman; 15.01.2017

Я не знаю о какой-либо возможности сделать это явно с помощью Ecto, но следующий необработанный SQL должен работать для MySQL (только предложение WHERE):

WHERE DAY(bd) > DAY(NOW()) AND MONTH(db) = MONTH(NOW()) 
   OR DAY(bd) <= DAY(NOW()) AND MONTH(db) = MOD(MONTH(NOW()), 12) + 1 

Для PostgreSQL измените DAY(XXX) на EXTRACT(DAY FROM XXX):

WHERE EXTRACT(DAY FROM bd) > EXTRACT(DAY FROM NOW()) ....

Эти предложения могут использоваться во фрагментах Ecto как есть.

person Aleksei Matiushkin    schedule 14.01.2017
comment
Хорошо, теперь я прочитал о фрагментах в экто-запросах. Я попробую позже вместе с вашим ответом. - person guitarman; 14.01.2017
comment
Блин, я забыл упомянуть фрагменты в своем ответе, но вы, кажется, умеете читать между строк :) - person Aleksei Matiushkin; 14.01.2017
comment
Технически это вернет дни рождения в течение следующего 1 месяца, а не 30 дней :) - person Dogbert; 14.01.2017