如何从名字和出生日期中选择一个名字?

时间:2009-08-21 14:41:56

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

嘿那里。我非常感谢您在创建可以完成以下任务的查询方面提供的帮助。问题在于动态地在欧洲推出我们称之为“namedays”的东西。 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”总是在生日之后首次找到。所以说鲍勃#1出生于8月5日,他的名字日将在9月4日落下,但如果我们在9月4日之后出生,他的名字日将在4月22日。

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

用户表:

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

所以,eveything是基于生日,我们需要找到一个特定的人基于first_name和birthdate庆祝他/她的“nameday”。当我需要的是一个SQL服务器查询,它将能够为我的数据库中的人们提供名称日期;)

如果您认为将“nameday”表格中的日期拆分为:month&让我知道,我们可以做到这一点。我需要能够为此提出最有效的解决方案。

非常感谢任何帮助,

Wojo

7 个答案:

答案 0 :(得分:2)

我建议您保留日期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 :(得分:1)

你走了:

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年,以便在人名的日期在其生日之前(日历方式),很容易找到第一个。在这个技巧中,我已经将MM添加到MMDD数字表示中以保持自然排序
  • 为每个人选择他/她的生日月日之后的第一个名字日
  • 使用LEFT JOIN,即使他们没有名字日也会在结果列表中显示名字如“Wojo”的人,是吗? :)

答案 2 :(得分:1)

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

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列调整为生日后的年份,并使用之后的最小日期他们的生日。

此解决方案根据您的示例数据返回正确的日期,问题中列出的情况(Bob的生日更改为9/4之后返回Bob的名称日为4/22)。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

SELECT Min(nameday。[date]),[user] .first_name
从名称日起     在nameday.name = [user] .first_name的INNER JOIN [user] 名称日期。[日期]&gt; = DateAdd(年, - (DatePart(yyyy,birth_date)-1900),birth_date)
GROUP BY [user] .first_name

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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)