【问题标题】:How to select one's name day from first name and birth date?如何从名字和出生日期中选择一个名字的日子?
【发布时间】:2009-08-21 14:41:55
【问题描述】:

你好。非常感谢您帮助创建可以完成以下任务的查询。问题在于动态地获得我们在欧洲所谓的“命名日”。 Nameday 是欧洲和拉丁美洲许多国家的传统,在一年中的特定日子庆祝与一个人的名字 (http://en.wikipedia.org/wiki/Name_day) 相关。

我所拥有的是所有名称及其对应日期的表格(我以“1900-08-22”这种格式存储日期,但我们确实需要月份和日期)

nameday 表:

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

诀窍是每个名字可能有多个条目,并且某人的“nameday”总是在生日之后首先找到。因此,假设 Bob #1 出生于 8 月 5 日,他的命名日将在 9 月 4 日,但如果我们出生在 9 月 4 日之后,他的命名日将在 4 月 22 日。

显然我和我的用户有一张桌子

用户表:

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

所以,一切都是基于生日的,我们需要根据名字和生日来找出某个人庆祝他/她的“命名日”的时间。当我需要一个 SQL 服务器查询时,它将能够为我的数据库中的人获取我的姓名;)

如果您认为将“nameday”表中的日期拆分为:月和日更容易,请告诉我,我们可以这样做。我需要能够为此想出最有效的解决方案。

非常感谢任何帮助,

呸呸

【问题讨论】:

  • 我认为如果您在两个表中添加另一列并将月份和日期值作为 4 位数字值会更容易:nameday Bob 422、Bob 904、Frank 102;用户 Bob 805,Frank 101。然后您可以加入名称并更轻松地比较值。
  • 比较日期值,即。
  • 强烈建议不要使用 SQL 保留关键字作为表名 (USER) 或列名 (DATE),即使是在伪数据或经过清理的数据中也是如此。

标签: sql sql-server-2005 sql-server-2008


【解决方案1】:

我建议您保留基于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

有关性能详情,请参阅我的博客中的这篇文章:

【讨论】:

  • Quassnoi,看来这可以解决问题。我现在正在根据我的数据库确认这一点,如果它有效,我会告诉你:) 谢谢
  • +1 表示随机 1900 年是闰年知识。要么 Google 是你的朋友,要么你有一个怪异的知识库。
  • @Chance,很容易,所有能被 100 整除的年份都不是闰年,除非它们也能被 400 整除(所以 1800 和 1900 不是闰年,但 2000 是闰年,而 2400 是闰年)是)
【解决方案2】:

这似乎是一个简单的解决方案:

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)。

【讨论】:

    【解决方案3】:

    给你:

    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 表示。
      • 我什至建议使用此公式将(持久)计算列添加到“nameday”表中,但无论如何它不应该是繁重的计算
    • 将这些命名日延长至 2 年,以便如果人员的命名日在其生日之前(日历方式),则很容易找到第一个命名日。在这个技巧中,我在 MMDD 数字表示中添加了 1000 以保持自然排序
    • 为每个人选择他/她的生日月份日期之后的第一个命名日
    • 使用 LEFT JOIN,这样即使没有命名日,具有酷名称(如“Wojo”)的人仍会显示在结果列表中,是吗? :)

    【讨论】:

      【解决方案4】:

      SELECT Min(nameday.[date]), [user].first_name
      从命名日
      在 nameday.name 上的 INNER JOIN [user] = [user].first_name
      WHERE nameday.[date] >= DateAdd(year, -(DatePart(yyyy,birth_date)-1900),birth_date)
      GROUP BY [用户].first_name

      【讨论】:

        【解决方案5】:
        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
        

        【讨论】:

        • 所有的生日都将是 > nameday.date 因为所有的命名日都是 1900。不过,从帖子中并不是特别清楚。
        • 哎呀忘了包括所有nameday.dates都存储在1900年的事实,正在修改......
        • 这可能会为每个用户返回多个姓名日期,而您只需要每个用户的第一个日期。
        • @yodaj007,自从您发表评论后,我可能已经编辑了答案,但怎么会呢?我在那里有 MIN 函数,它是由用户分组的
        • 感谢 Nathan,但您的解决方案似乎只会让用户的生日(月+日)大于命名日...
        【解决方案6】:
        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
        

        【讨论】:

          【解决方案7】:

          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)
          

          【讨论】:

          • DAYOFYEAR 在闰年之间变化,因此您应该检查每个日期是否为闰并相应地更正日期。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-07
          • 2016-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多