【问题标题】:TSQL Counting number of Consecutive Absences in a rowSQL 统计连续缺勤次数
【发布时间】:2011-03-29 04:12:12
【问题描述】:

问题:* 我正在尝试计算每个学生在特定班级中连续缺勤的次数。但是,如果该学生确实上课了一天,则需要重新开始计数。

例如如果班级 MATH1234 周一和周五有课,而学生 001234 第 1 周的周一、周五和第 2 周的周一缺课,但第 2 周周五缺课,则周一和周五缺课对于第 3 周,他们对该课程的连续缺勤计数为:

(这是我的课程表的精简版)

Class Day Week    IsAbsent    ConsecutiveAbs
MATH1234  Mon 1   1       1
MATH1234  Fri 1   1       2
MATH1234  Mon 2   1       3
MATH1234  Fri 2   0       0
MATH1234  Mon 3   1       1
MATH1234  Fri 3   1       2

我有一个名为 Lessons 的表格,其中包含所有学生和他们注册的课程的运行列表,以及他们是否缺席任何课程:

课时([学号]、[班号]、[行号]、[学年]、[年]、[学期]、[周]、[日期间]、[ ClassDate], [IsAbsent], [ReasonCode], [ConsecutiveAbs])

鉴于上面的表格,我目前正在做的是像这样更新课程表来改变 ConsecutiveAbs 的值:

UPDATE Lessons
SET ConsecutiveAbs = 
(SELECT ISNULL(SUM(CAST(IsAbsent AS numeric)), 0)
 FROM Lessons AS L3
 WHERE L3.IsAbsent = 1
 AND L1.IsAbsent <> 0
 AND L3.[Student ID] = L1.[Student ID]
 AND L3.[Class Number] = L1.[Class Number]
 AND L3.[Line Number] = L1.[Line Number]
 AND L3.[Year] = L1.[Year]
 AND L3.[ClassDate] <= L1.[ClassDate]
 AND (L3.[ClassDate] > (SELECT MAX(L2.ClassDate)
      FROM Lessons AS L2
      WHERE L2.IsAbsent = 0
      AND L2.[Student ID] = L1.[Student ID]
      AND L2.[Class Number] = L1.[Class Number]
      AND L2.[Line Number] = L1.[Line Number]
      AND L2.[Year] = L1.[Year]
      AND L2.ClassDate < L1.[ClassDate]
 ) OR (SELECT MAX(L2.ClassDate)
       FROM Lessons AS L2
       WHERE L2.IsAbsent = 0
       AND L2.[Student ID] = L1.[Student ID]
       AND L2.[Class Number] = L1.[Class Number]
       AND L2.[Line Number] = L1.[Line Number]
       AND L2.[Year] = L1.[Year]
       AND L2.ClassDate < L1.[ClassDate]
 ) IS NULL))
 FROM Lessons AS L1

但这忽略了学生实际上课的课程,只是不停地计算:(

Class Day Week    IsAbsent    ConsecutiveAbs

MATH1234  Mon 1   1       1
MATH1234  Fri 1   1       2
MATH1234  Mon 2   1       3
MATH1234  Fri 2   0       4
MATH1234  Mon 3   1       5
MATH1234  Fri 3   1       6

有什么想法吗?

【问题讨论】:

标签: tsql subquery


【解决方案1】:

与您上一个问题的答案类似,只是这一次它会查找已参加过的课程,而不是仅将搜索限制为一周。

UPDATE allLessons
SET ConsecutiveAbs = results.ConsecutiveAbs
FROM 
    Lessons allLessons JOIN
(
    SELECT 
        LessonsAbsent.[Student ID],
        LessonsAbsent.[Class Number],
        LessonsAbsent.[Line Number],
        LessonsAbsent.[Year],
        LessonsAbsent.ClassDate,
        ISNULL(SUM(CAST(IsAbsent AS numeric)), 0) AS ConsecutiveAbs
    FROM 
        Lessons LessonsAbsent JOIN
        Lessons RunningTotalAbsent ON 
            RunningTotalAbsent.IsAbsent = 1
            AND LessonsAbsent.[Student ID] = RunningTotalAbsent.[Student ID]
            AND LessonsAbsent.[Class Number] = RunningTotalAbsent.[Class Number]
            AND LessonsAbsent.[Line Number] = RunningTotalAbsent.[Line Number]
            AND LessonsAbsent.[Year] = RunningTotalAbsent.[Year]
            AND LessonsAbsent.ClassDate >= RunningTotalAbsent.ClassDate

            -- Only include this date in the running total only if the student has not attended a class in-between the absences.
            AND NOT EXISTS (
                SELECT *
                FROM Lessons notAbsent
                WHERE 
                    LessonsAbsent.[Student ID] = notAbsent.[Student ID]
                    AND LessonsAbsent.[Class Number] = notAbsent.[Class Number]
                    AND LessonsAbsent.[Line Number] = notAbsent.[Line Number]
                    AND LessonsAbsent.[Year] = notAbsent.[Year]
                    AND notAbsent.IsAbsent = 0
                    AND notAbsent.ClassDate <= LessonsAbsent.ClassDate
                HAVING MAX(ClassDate) > RunningTotalAbsent.ClassDate
        )
    WHERE LessonsAbsent.IsAbsent = 1   
    GROUP BY
        LessonsAbsent.[Student ID],
        LessonsAbsent.[Class Number],
        LessonsAbsent.[Line Number],
        LessonsAbsent.[Year],
        LessonsAbsent.ClassDate
) results ON
    results.[Student ID] = allLessons.[Student ID]
    AND results.[Class Number] = allLessons.[Class Number]
    AND results.[Line Number] = allLessons.[Line Number]
    AND results.[Year] = allLessons.[Year]
    AND results.ClassDate = allLessons.ClassDate

【讨论】:

  • 不幸的是,这给出了与我相同的结果。当学生参加了一堂课但错过了下一堂课时,计数不会重置。
  • 其实在检查结果后,它并没有给出和我一样的结果,因为它跳过了学生上课的条目,而是在下一个缺席条目继续计数而不是重置计数.
  • 抱歉……我已经更正了逻辑,并验证了这次它给出了正确的答案。
  • 谢谢你,那太完美了:) 现在如果我能找到一种方法来加快速度就好了。目前运行更新查询大约需要 10 分钟。
  • 我已经更新了查询以尝试提高效率。让我知道这是否对您有用……如果是这样,我们总是很感激赞成票。 ;-)
【解决方案2】:

根据您的情况,这可能没有用,但它可能会帮助您找到解决方案

select * 
into #orderedlessons
from Lessons
order by [Student ID], [Class Number], [Line Number], [Year], [ClassDate]

declare @tot int
set @tot=0
update #orderedlessons
set @tot = ConsecutiveAbs = Case when IsAbsent=0 then 0 else @tot+1 END;

update lessons
set lessons.ConsecutiveAbs = ordered.ConsecutiveAbs
from lessons inner join  #orderedlessons ordered on
lessons.[Student ID] = ordered.[Student ID]
and lessons.[Class Number] = ordered.[Class Number]
and lessons.[Line Number] = ordered.[Line Number]
and lessons.[Year] = ordered.[Year]
and lessons.ClassDate = ordered.[ClassDate]

drop table #orderedlessons

【讨论】:

    【解决方案3】:

    这样的?

    update L2
    set L2.ConsecutiveAbs = 
            case 
            when L2.IsAbsent = 0 then 0
            else (  select  TOP 1 L1.ConsecutiveAbs 
                    from    Lessons L1
                    where   L2.[Student ID] = L1.[Student ID]
                        AND L2.[Class Number] = L1.[Class Number]
                        AND L2.[Line Number] = L1.[Line Number]
                        AND L2.[Year] = L1.[Year]
                        AND L2.ClassDate > L1.[ClassDate]
                    ORDER BY L1.ClassDate desc
                  )
            end as ConsecutiveAbs
    from    Lessons L2
    

    编辑: 添加您的更新。

    Edit2:添加更新

    【讨论】:

    • 嗨,约翰。当我尝试运行时,您建议的解决方案导致错误。我确实在它周围添加了更新。 “子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。”
    【解决方案4】:

    不确定您是否要坚持子查询的想法。这种问题类型 -IMO - 使用游标可以更好地解决(更快,更简单)。如果您选择走这条路,这就是 SQL 的样子。

    所以没有让我将代码粘贴到答案中。所以我在这里粘贴了代码 sn-p。 http://pastebin.com/ybesdX2G

    这是一篇向您展示如何使用光标的文章的参考。 http://msdn.microsoft.com/en-us/library/ms180169.aspx

    编辑: 游标方法一次只在一行上工作,并记住最后一行是否不存在/存在。所以正确排序数据很重要。

    请注意,代码 sn-p 中没有 ORDER BY。我根据您在问题中提供的数据样本测试了代码 sn-p。您的数据已预先排序。所以,它就像一个魅力。

    在您的数据库中,如果您的数据没有以预先排序的方式存储(我怀疑是这样),您将需要在第 8 行添加一个 ORDER BY,以将数据排序到序列中。

    希望这会有所帮助。

    【讨论】:

    • 当基于集合的解决方案仍有一线希望时,您必须尽一切可能避免进入光标的阴暗面...
    • :-)。我赞同同样的哲学。但并非所有操作都是基于集合的操作。 OPs 问题是不基于设置的操作示例。
    • 感谢您的建议。我以前没有使用过光标,所以我需要花一些时间来玩它:)
    • 通读 msdn 定义后,我认为光标不适用于我正在尝试做的事情:(如果我可以远离子查询,那么我会因为它产生过多的开销和更新约 50,000 行大约需要 11 分钟。
    • 嗨纳琳。谢谢你的sn-p。我确实尝试过它是否会起作用。不幸的是,当学生上课时,缺席计数不会重置:(
    猜你喜欢
    • 1970-01-01
    • 2010-12-06
    • 2012-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-23
    相关资源
    最近更新 更多