SQL - 为这个 SELECT CASE 工作的最佳方法是啥?

Posted

技术标签:

【中文标题】SQL - 为这个 SELECT CASE 工作的最佳方法是啥?【英文标题】:SQL - What is the best way to work for this SELECT CASE?SQL - 为这个 SELECT CASE 工作的最佳方法是什么? 【发布时间】:2014-02-04 14:36:05 【问题描述】:

我有一个考勤表和一个包含活动类型的表

我想将每个人每月的活动分组到不同的列中

示例表:

出席

个人 ID 日期 门入 门出 活动ID

活动

身份证 名称

值是 Activity。名称是:Present、Absence、Ill 和 Holiday

我想获得类似的视图(按个人 ID 和月份分组

个人ID 月 年份 缺席 假期 生病 少于 4 小时 超过 4 小时 超过 5 小时

创建此分组的最佳方法是什么? 我已经有两条 SQL 语句,但似乎都太不友好了

谁能帮帮我??? 谢谢

    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, COUNT(1) as Absence, 0 as Holiday, 0 as Ill, 0 as LessThan4Hours, 0 as MoreThan4Hours, 0 as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE act.Name = 'Absence'
    GROUP BY year(att.Date), month(att.Date), att.PersonID
    UNION
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, 0 as Absence, COUNT(1) as Holiday, 0 as Ill, 0 as LessThan4Hours, 0 as MoreThan4Hours, 0 as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE act.Name = 'Holiday'
    GROUP BY year(att.Date), month(att.Date), att.PersonID
    UNION
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, 0 as Absence, 0 as Holiday, count(1) as Ill, 0 as LessThan4Hours, 0 as MoreThan4Hours, 0 as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE act.Name = 'Ill'
    GROUP BY year(att.Date), month(att.Date), att.PersonID
    UNION
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, 0 as Absence, 0 as Holiday, 0 as Ill, COUNT(1) as LessThan4Hours, 0 as MoreThan4Hours, 0 as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE att.GateOut is not null
    AND (DATEDIFF(n, att.GateIn, att.GateOut)/60) <= 4
    AND act.Name = 'Present'
    GROUP BY year(att.Date), month(att.Date), att.PersonID
    UNION
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, 0 as Absence, 0 as Holiday, 0 as Ill, 0 as LessThan4Hours, COUNT(1) as MoreThan4Hours, 0 as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE att.GateOut is not null
    AND (DATEDIFF(n, att.GateIn, att.GateOut)/60) > 4
    AND (DATEDIFF(n, att.GateIn, att.GateOut)/60) <= 5
    AND act.Name = 'Present'
    GROUP BY year(att.Date), month(att.Date), att.PersonID
    UNION
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, 0 as Absence, 0 as Holiday, 0 as Ill, 0 as LessThan4Hours, 0 as MoreThan4Hours, COUNT(1) as MoreThan5Hours
    FROM Attendance att
    inner join Activities act on att.ActivityID = act.ID
    WHERE att.GateOut is not null
    AND (DATEDIFF(n, att.GateIn, att.GateOut)/60) > 5
    AND act.Name = 'Present'
    GROUP BY year(att.Date), month(att.Date), att.PersonID

我也在 SELECT CASE 中解决了这个问题,可能更糟?

    SELECT a.PersonID, a.cYear, a.cMonth, SUM(a.Absence) AS Absence, SUM(a.Holiday) AS Holiday, SUM(a.Ill) AS Ill, SUM(a.LessThan4Hours) AS LessThan4Hours, SUM(a.MoreThan4Hours) AS MoreThan4Hours, SUM(a.MoreThan5Hours) AS MoreThan5Hours
    FROM 
    (
    SELECT year(att.Date) as cYear, month(att.Date) as cMonth, att.PersonID, CASE 
        WHEN act.Name = 'Ill' THEN 1
        WHEN act.Name = 'Holiday' THEN 0
        WHEN act.Name = 'Absence' THEN 0
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 0
        ELSE 0 
      END AS Ill,
       CASE 
        WHEN act.Name = 'Ill' THEN 0
        WHEN act.Name = 'Holiday' THEN 1
        WHEN act.Name = 'Absence' THEN 0
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 0
        ELSE 0 
      END AS Holiday,
      CASE 
        WHEN act.Name = 'Ill' THEN 0
        WHEN act.Name = 'Holiday' THEN 0
        WHEN act.Name = 'Absence' THEN 1
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 0
        ELSE 0 
      END AS Absence,
      CASE 
        WHEN act.Name = 'Ill' THEN 0
        WHEN act.Name = 'Holiday' THEN 0
        WHEN act.Name = 'Absence' THEN 0
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 1
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 0
        ELSE 0
      END AS MoreThan5Hours,
      CASE 
        WHEN act.Name = 'Ill' THEN 0
        WHEN act.Name = 'Holiday' THEN 0
        WHEN act.Name = 'Absence' THEN 0
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 1
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 0
        ELSE 0 
      END AS MoreThan4Hours,
      CASE 
        WHEN act.Name = 'Ill' THEN 0
        WHEN act.Name = 'Holiday' THEN 0
        WHEN act.Name = 'Absence' THEN 0
        WHEN GateOut = null THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 5 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) > 4 THEN 0
        WHEN (DATEDIFF(n, gatein, gateout)/60) <= 4 THEN 1
        ELSE 0 
      END AS LessThan4Hours
  from Attendance att
  inner join Activities act on att.ActivityID = act.ID 
  ) a
  GROUP BY a.PersonID, a.cYear, a.cMonth
  ORDER BY a.cYear DESC, a.cMonth DESC, a.PersonID

【问题讨论】:

【参考方案1】:

这可能更容易阅读和理解。内部“PreChk”查询遍历记录并将它们标记为不同的类型和时间,然后应用按您的要求分组的摘要。

SELECT
      PreChk.PersonID, 
      PreChk.cYear, 
      PreChk.cMonth,
      SUM(PreChk.Absence) AS Absence, 
      SUM(PreChk.Holiday) AS Holiday, 
      SUM(PreChk.Ill) AS Ill, 
      SUM( case when PreChk.HrsDif > 5 then 1 else 0 end ) as MoreThan5Hours,
      SUM( case when PreChk.HrsDif > 4 AND PreChk.HrsDif < 5 then 1 else 0 end ) as MoreThan4Hours,
      SUM( case when PreChk.HrsDif < 4 then 1 else 0 end ) as LessThan4Hours
   from 
      ( SELECT 
              att.PersonID, 
              year(att.Date) as cYear, 
              month(att.Date) as cMonth, 
              CASE WHEN act.Name = 'Ill' THEN 1 else 0 end as Ill,
              CASE WHEN act.Name = 'Holiday' THEN 1 else 0 end as Holiday,
              CASE WHEN act.Name = 'Absence' THEN 1 else 0 end as Absence,
              CASE when GateOut = null 
                   THEN 0
                   ELSE DATEDIFF(n, gatein, gateout)/60) end as HrsDif
           from 
              Attendance att
                 inner join Activities act 
                    on att.ActivityID = act.ID ) PreChk
   GROUP BY 
      PreChk.PersonID, 
      PreChk.cYear, 
      PreChk.cMonth
   ORDER BY 
      PreChk.cYear DESC, 
      PreChk.cMonth DESC, 
      PreChk.PersonID

【讨论】:

我不知道您可以将 CASE 作为不同的列名!太棒了! @Cedric,单击帮助/游览选项以了解礼仪和问题标记。这有助于其他人知道问题何时“解决”,这样其他人就不会浪费时间尝试解决已经完成的问题,并有助于其他人在工作中遇到类似问题时了解 DID 的工作原理。【参考方案2】:

这里有一个sqlfiddle 展示了这个方法

select personid, 
         datepart(month, date) as Month, 
         datepart(year, date) as year, 
         case when [present] is not null then 1 else 0 end as present,
         case when [absence] is not null then 1 else 0 end as absence,
         case when [III] is not null then 1 else 0 end as III,
         case when [holiday] is not null then 1 else 0 end as holiday,
         sum(case when datediff(hour, gatein, gateout) <= 4 then 1 else 0 end) as LessThan4Hours,
         sum(case when datediff(hour, gatein, gateout) > 4 and datediff(hour, gatein, gateout) <= 5 then 1 else 0 end) as GreaterThan4Hours,
         sum(case when datediff(hour, gatein, gateout) > 5 then 1 else 0 end) as GreaterThan5Hours
  from 
  (
    select a.*, act.name
    from attendance a
    join activity act on a.activityid = act.activityid
  ) as source
  pivot
  (
    max(name)
    FOR name in ([present],[absence],[III],[holiday])
  ) as p
  group by personid, 
           datepart(month, date), 
           datepart(year, date), 
           case when [present] is not null then 1 else 0 end,
           case when [absence] is not null then 1 else 0 end,
           case when [III] is not null then 1 else 0 end,
           case when [holiday] is not null then 1 else 0 end

【讨论】:

很有趣,但返回的结果并不完全符合我的预期。但这鼓励我了解有关在 sql 中进行透视的更多信息。还是谢谢

以上是关于SQL - 为这个 SELECT CASE 工作的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

SELECT CASE SQL 中的 DISTINCT COUNT

select case when,用来判断查询

SQL Server 2008 - SELECT 子句中的 Case / If 语句 [重复]

sqlserver case when问题

具有 CASE 和子查询的 sql to LINQ

spark2.1:使用df.select(when(a===b,1).otherwise)替换(case when a===b then 1 else 0 end)