Как выбрать день имени из имени и даты рождения? - PullRequest
2 голосов
/ 21 августа 2009

Привет. Буду очень признателен за помощь в создании запроса, который позволит выполнить следующее. Проблема заключается в том, чтобы динамично развиваться, что мы называем «именными» в Европе. Именины - это традиция во многих странах Европы и Латинской Америки праздновать определенный день года, связанный с данным именем (http://en.wikipedia.org/wiki/Name_day).

У меня есть таблица всех имен и соответствующих им дат (я храню дату в этом формате «1900-08-22», но нам действительно нужны месяц и день)

Именины Таблица:

name   date
----------------
Bob    1900-04-22
Bob    1900-09-04
Frank  1900-01-02
...

Хитрость в том, что для каждого имени может быть несколько записей, и кто-то «именины» всегда первым встречается после дня рождения. Скажем, Боб № 1 родился 5 августа, его именинник выпадет 4 сентября, но если мы родимся после 4 сентября, его именин будет 22 апреля.

Очевидно, у меня есть таблица с моими пользователями

пользователь таблица:

id   first_name  birth_date
------------------------------------
1    Bob         1975-08-05
2    Frank       1987-01-01
...

Итак, все основано на дате рождения, нам нужно выяснить, когда данный человек празднует свой "именинник" на основе имени и даты рождения. Когда мне понадобится запрос SQL-сервера, я смогу узнать имена людей из моей базы данных;)

Если вы думаете, что было бы легче разделить даты в таблице «именины» на: месяц и день, дайте мне знать, мы можем это сделать. Мне нужно быть в состоянии найти наиболее эффективное решение для этого.

Любая помощь с благодарностью,

Wojo

Ответы [ 7 ]

2 голосов
/ 21 августа 2009

Я бы порекомендовал вам сохранять даты на 1904 основе, поскольку 1900 не был високосным годом и ему не хватало Feb 29th.

Однако вот запрос с вашими данными:

WITH    users AS
        (
        SELECT  1 AS id, 'Bob' AS first_name, CAST('1975-08-05' AS DATETIME) AS birth_date
        UNION ALL
        SELECT  2 AS id, 'Frank' AS first_name, CAST('1987-01-01' AS DATETIME) AS birth_date
        ),
        namedays AS
        (
        SELECT  'Bob' AS name, CAST('1900-04-22' AS DATETIME) AS date
        UNION ALL
        SELECT  'Bob' AS name, CAST('1900-09-04' AS DATETIME) AS date
        UNION ALL
        SELECT  'Frank' AS name, CAST('1900-01-02' AS DATETIME) AS date
        )
SELECT  *
FROM    users u
OUTER APPLY
        (
        SELECT  COALESCE(
                (
                SELECT  TOP 1 date
                FROM    namedays nd
                WHERE   nd.name = u.first_name
                        AND nd.date >= DATEADD(year, 1900 - YEAR(u.birth_date), birth_date)
                ORDER BY
                        nd.date
                ),
                (
                SELECT  TOP 1 date
                FROM    namedays nd
                WHERE   nd.name = u.first_name
                ORDER BY
                        nd.date
                )
                ) AS date
        ) dates

Смотрите этот пост в моем блоге для деталей производительности:

1 голос
/ 21 августа 2009

Это простое решение:

select  u.id, u.first_name, min(isnull(n.date, n2.date)) as nameday
from    users u
        left join namedays n on u.first_name = n.name and dateadd(year, year(u.birth_date) - 1900, n.date) > u.birth_date
        left join namedays n2 on u.first_name = n2.name and dateadd(year, year(u.birth_date) - 1899, n2.date) > u.birth_date
group by u.id, u.first_name

Пояснение: Соедините таблицу users с таблицей namedays (скорректировав столбец namedays.date к тому же году, что и день рождения). Если есть скорректированная дата после их дня рождения, эта дата используется.

Если нет, то таблица users снова соединяется с таблицей namedays, на этот раз в столбце date устанавливается год после дня рождения и используется минимальная дата после их дня рождения.

Это решение возвращает правильные даты в соответствии с данными вашего примера и ситуациями, перечисленными в проблеме (день рождения Боба изменен на 9/4, а день рождения Боба - 4/22).

1 голос
/ 21 августа 2009

Вот, пожалуйста:

WITH "nameday_long" ("name", "date", "p_month_day") AS (
    SELECT  n."name",
            n."date",
            DATEPART(month, n."date") * 100 + DATEPART(day, n."date")
    FROM    "nameday" n
    UNION ALL
    SELECT  n."name",
            n."date",
            DATEPART(month, n."date") * 100 + DATEPART(day, n."date") + 10000
    FROM    "nameday" n
)

SELECT      u."id",
            u."first_name",
            u."birth_date",
            n."date" AS nameday
FROM        "user" u
LEFT JOIN   "nameday_long" n
        ON  u."first_name" = n."name"
        AND n."p_month_day" = (SELECT MIN(x."p_month_day") FROM "nameday_long" x WHERE x."p_month_day" > DATEPART(month, u."birth_date") * 100 + DATEPART(day, u."birth_date"))

Хитрость:

  • конвертировать даты в формат, который не имеет лет и его легко сравнивать. Я выбрал целое число, которое будет похоже на MMDD-представление частей дня месяца.
    • Я бы даже предложил добавить (постоянный) вычисляемый столбец в таблицу "именин" с этой формулой, но в любом случае он не должен быть тяжелым для вычислений
  • расширить эти именные дни, чтобы охватить 2 года, чтобы было легко найти первый, если у человека день рождения до его дня рождения (по календарю). В этом трюке я добавил 1000 к числовому представлению MMDD, чтобы сохранить естественную сортировку
  • для каждого человека выберите первый день рождения, который следует после даты его / ее дня рождения
  • использовать LEFT JOIN, чтобы люди с такими крутыми именами, как "Wojo", все равно отображались в списке результатов, даже если у них нет именины, не так ли? :)
0 голосов
/ 21 августа 2009

DatePart (DayOfYear) - хороший способ сравнить даты. И кто знал, что я когда-нибудь узнаю что-то новое в культурном отношении от SO? : -)

declare @nameday table
(
  Name varchar(30),
  Date smalldatetime
)

declare @user table
(
  UserName varchar(30),
  Birthday smalldatetime
)

insert into @nameday
  select 'Bob', '1900-04-22' union
  select 'Bob', '1900-09-04' union
  select 'Frank', '1900-01-02'

insert into @user
  select 'Bob', '1975-08-05' union
  select 'Frank', '1987-01-01'


select UserName, 
       NameDayDate = Date
  from @user as u
    inner join @nameday as nd
      on nd.Name = u.UserName
      and datepart(dayofyear, nd.Date) >= datepart(dayofyear, u.Birthday)
      and not exists (select *
                        from @nameday as ndOlder
                        where ndOlder.Name = nd.Name
                          and datepart(dayofyear, ndOlder.Date) >= datepart(dayofyear, u.Birthday)
                          and ndOlder.Date < nd.Date)
0 голосов
/ 21 августа 2009
WITH namedays AS (
 SELECT UPPER(nd.name),
        nd.date,
        MONTH(nd.date) 'month',
        DAY(nd.date) 'day'
   FROM NAMEDAYS nd)
     birthdays AS (
 SELECT u.id,
        UPPER(u.first_name) 'first_name',
        MONTH(u.birth_date) 'month',
        DAY(u.birth_date) 'day'
   FROM USERS u)
SELECT t.id,
       t.first_name,
       MIN(nd.date) 'name_day'
  FROM USERS t
  JOIN birthdays bd ON bd.id = t.id
  JOIN namedays nd ON nd.month >= bd.month AND nd.day >= bd.day AND nd.name = bd.firstname
GROUP BY t.id, t.first_name
0 голосов
/ 21 августа 2009

ВЫБРАТЬ Мин. (Именин. [Дата]), [пользователь] .первое_имя
С именины
ВНУТРЕННИЙ РЕЙТИНГ [пользователь] на имя_дня.имя = [пользователь] .первое_имя
ГДЕ именины. [Date]> = DateAdd (год, - (DatePart (гггг, дата рождения -1900), дата рождения)
GROUP BY [пользователь] .first_name

0 голосов
/ 21 августа 2009
SELECT user.id, First_name, MIN(nameday.date)
FROM user
    INNER JOIN nameday ON user.id = nameday.userid
WHERE user.birth_date < DateAdd(year, YEAR(u.birth_date) - year(nameday.date), nameday.date)
GROUP BY user.id, first_name
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...