SQL:如何循环遍历SELECT语句的结果?

4
如何在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人? - gbn
fnSeatsTaken 应该检查“当前”有多少个座位被占用。容量取决于课程,但为了简单起见,我将其设置为始终等于20。 - Justin
3个回答

6

无需循环。您正在查看使用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。

现在的JOIN操作:

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

以上内容的简化版本:

...
-- 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大师)。 - Justin
我尝试实现上述内容,但目前似乎并没有起作用。始终返回“是”。我已经将我的代码放在原始问题的底部。您应该能够将其复制并粘贴到查询窗口中,然后只需执行即可。 - Justin
真是太棒了。你向我展示了很多东西,扩展了我的SQL工具包。非常感谢你如此详尽的回答,并回答了我的额外问题。 - Justin
2
@Justin,学习的关键之一是要学会以关系/声明式思维而非过程式思维来思考。 - Earlz

0
尽可能避免在SQL中循环结果集。如果你真的无法避免(如果你是一名标准程序员,但职业需要你使用SQL),那么请使用cursors。它们不太好用,但有时是不可避免的。

0

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

将所有插入/更新操作封装到一个事务中。如果任何一个插入/更新失败,则整个事务将被回滚。


该检查/UDF可能不可靠。http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/06/25/scalar-udfs-wrapped-in-check-constraints-are-very-slow-and-may-fail-for-multirow-updates.aspx - gbn
@gbn - 感谢提供链接。里面有很有趣的东西。顺便说一下,你发布的解决方案很不错。 - codingbadger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接