【问题标题】:SQL: How do I loop through the results of a SELECT statement?SQL:如何循环遍历 SELECT 语句的结果?
【发布时间】:2010-10-10 18:31:19
【问题描述】:

如何在 SQL 中循环遍历 SELECT 语句的结果?我的 SELECT 语句将只返回 1 列但有 n 个结果。

我在下面创建了一个虚构的场景,其中包含我正在尝试做的伪代码。

场景:

学生正在注册他们的课程。他们提交一份包含多个课程选择的表格(即一次选择 3 门不同的课程)。当他们提交注册时,我需要确保他们选择的课程仍有空间(请注意,在向他们展示课程选择 UI 之前,我会进行类似的检查,但我需要事后进行验证,以防其他人进入并刷卡补上剩余的点)。

伪代码:

DECLARE @StudentId = 1
DECLARE @Capacity = 20

-- Classes will be the result of a Select statement which returns a list of ints
@Classes = SELECT classId FROM Student.CourseSelections
WHERE Student.CourseSelections = @StudentId

BEGIN TRANSACTION
DECLARE @ClassId int
foreach (@classId in @Classes)
{
   SET @SeatsTaken = fnSeatsTaken @classId

   if (@SeatsTaken > @Capacity)
   {
       ROLLBACK;  -- I'll revert all their selections up to this point
       RETURN -1;
   }
   else
   {
       -- set some flag so that this student is confirmed for the class
   }
}

COMMIT
RETURN 0

我真正的问题是类似的“票务”问题。因此,如果这种方法看起来非常错误,请随时推荐一些更实用的方法。

编辑:

正在尝试实施以下解决方案。此时它不起作用。始终返回“保留”。

DECLARE @Students TABLE
(
 StudentId int
,StudentName nvarchar(max)
)

INSERT INTO @Students
 (StudentId ,StudentName)
VALUES
 (1, 'John Smith')
 ,(2, 'Jane Doe')
 ,(3, 'Jack Johnson')
 ,(4, 'Billy Preston')

-- Courses
DECLARE @Courses TABLE
(
 CourseId int
,Capacity int
,CourseName nvarchar(max)
)

INSERT INTO @Courses
 (CourseId, Capacity, CourseName)
VALUES
 (1, 2, 'English Literature'),
 (2, 10, 'Physical Education'),
 (3, 2, 'Photography')


-- Linking Table
DECLARE @Courses_Students TABLE
(
 Course_Student_Id int
,CourseId int
,StudentId int
)

INSERT INTO @Courses_Students
 (Course_Student_Id, StudentId, CourseId)
VALUES
 (1, 1, 1),
 (2, 1, 3),
 (3, 2, 1),
 (4, 2, 2),
 (5, 3, 2),
 (6, 4, 1),
 (7, 4, 2)

SELECT Students.StudentName, Courses.CourseName FROM @Students Students INNER JOIN
@Courses_Students Courses_Students ON Courses_Students.StudentId = Students.StudentId INNER JOIN
@Courses Courses ON Courses.CourseId = Courses_Students.CourseId

DECLARE @StudentId int = 4

-- Ideally the Capacity would be database driven
-- ie. come from the Courses.Capcity.
-- But I didn't want to complicate the HAVING statement since it doesn't seem to work already.
DECLARE @Capacity int = 1 

IF EXISTS (Select *
 FROM
  @Courses Courses INNER JOIN
  @Courses_Students Courses_Students ON Courses_Students.CourseId = Courses.CourseId
 WHERE
  Courses_Students.StudentId = @StudentId
 GROUP BY
  Courses.CourseId
 HAVING
  COUNT(*) > @Capacity)
BEGIN
 SELECT 'full' as Status
END
ELSE BEGIN
 SELECT 'reserved' as Status
END

【问题讨论】:

  • 您认为 fnSeatsTaken 的内容是什么?每个班级没有容量,还是总是 20 人?
  • fnSeatsTaken 假设检查“当前”有多少座位被填满。容量会因课程而异,但为简单起见,我将其设置为始终 = 20。

标签: sql loops for-loop ticket-system


【解决方案1】:

不需要循环。您正在查看包含 COUNT 和 GROUP 的标准聚合。

当然,需要一些细节,但原则是这样......

DECLARE @StudentId = 1
DECLARE @Capacity = 20

-- Classes will be the result of a Select statement which returns a list of ints
IF EXISTS (SELECT *
    FROM
        Student.CourseSelections CS
        JOIN
        ---this is where you find out course allocations somehow
        ClassTable C ON CS.classId = C.classId 
    WHERE
        Student.CourseSelections = @StudentId
    GROUP BY  --change this, it depends on where you find out course allocations
        ClassID
    HAVING
        COUNT(*) > @Capacity)
   'no'
ELSE
   'yes'

编辑:

我已更改链接表。链接表中通常不需要 Course_Student_ID。

现在加入

  • 获取该学生的课程
  • 然后查看该课程的所有学生并与容量进行比较

上面的删减版:

...
-- Linking Table
DECLARE @Courses_Students TABLE (
,CourseId int
,StudentId int)

INSERT INTO @Courses_Students
 (StudentId, CourseId)
VALUES (1, 1), (1, 3), (2, 1), (2, 2), (3, 2), (4, 1), (4, 2)

DECLARE @StudentId int = 4

--straight list
SELECT
     C.CourseName, C.Capacity, COUNT(*)
 FROM
  @Courses_Students CSThis
  JOIN
  @Courses C ON CSThis.CourseId = C.CourseId
  JOIN
  @Courses_Students CSOthers ON CSOthers.CourseId = C.CourseId
 WHERE
  CSThis.StudentId = @StudentId
 GROUP BY
  C.CourseName, C.Capacity

--oversubscribed list
  SELECT
     C.CourseName, C.Capacity, COUNT(*)
 FROM
  @Courses_Students CSThis
  JOIN
  @Courses C ON CSThis.CourseId = C.CourseId
  JOIN
  @Courses_Students CSOthers ON CSOthers.CourseId = C.CourseId
 WHERE
  CSThis.StudentId = @StudentId
 GROUP BY
  C.CourseName, C.Capacity
  HAVING
      COUNT(*) > C.Capacity

【讨论】:

  • 太棒了,这看起来很有希望。我有一些阅读要做才能完全理解(显然我还不是 SQL 大师)
  • 我已经尝试实现上述内容,但此时它似乎无法正常工作。总是返回“是”。我已将我的代码放在原始问题的底部。您应该能够将其复制并粘贴到查询窗口中,然后点击执行。
  • 简直太棒了。您在这里向我展示了很多扩展我的 SQL 工具带。非常感谢您提供如此详尽的回答并关注我的额外问题。
  • @Justin 要学习的关键之一是考虑关系/声明性而不是程序性。
【解决方案2】:

尽可能避免在 SQL 中循环遍历结果集。如果你真的不能(如果你真的是一个标准程序员,但专业引导你进入 SQL)使用cursors。它们闻起来不好闻,但有时是不可避免的。

【讨论】:

    【解决方案3】:

    另一种选择是在包含课程信息的表上实现CHECK Constraint。检查约束可以调用您现有的函数来检查是否有空闲座位。

    将您的所有插入/更新打包到一个事务中。如果任何插入/更新失败,则整个事务将被回滚。

    【讨论】:

    猜你喜欢
    • 2016-07-12
    • 1970-01-01
    • 2018-05-03
    • 1970-01-01
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    • 2017-07-24
    • 1970-01-01
    相关资源
    最近更新 更多