Вот вариант 2. Первая часть готовит счетчики. Поскольку у меня нет данных о студентах, я решил создать временную таблицу из строк @maxStudents только с одним идентификатором столбца.
First cte (Students) генерирует список студентов строк maxStudents. Секунда (-ы) извлекает студентов, присваивая им номер строки (очевидно, здесь нет необходимости, но это важно, когда вы подключаете запрос, который извлекает студентов). Также возвращает количество студентов.
Третья часть размещает студентов в группы. Учащиеся, принадлежащие к последней группе, будут перемещены в другую группу, если они принадлежат к последней группе, имеющей менее @minGroupSize членов. Первой версии можно добиться, заменив затем часть в операторе case, например, на 1, чтобы поместить их в первую группу.
declare @group_size int
set @group_size = 10
declare @maxStudents int
set @maxStudents = 104
declare @minGroupSize int
set @minGroupSize = 5
;with students as (
select 1 id
union all
select 2 * id + b
from students cross join (select 0 b union all select 1) b
where 2 * id + b <= @maxStudents
),
s as (
select students.id, row_number() over(order by students.id) - 1 rowNumber, count (*) over () TotalStudents
from students
)
select s.id StudentID,
case when TotalStudents % @group_size < @minGroupSize
and rowNumber >= (TotalStudents / @group_size * @group_size)
then rowNumber - (TotalStudents / @group_size * @group_size)
else rowNumber / @group_size
end + 1 Group_number
from s
order by 2, 1