Использование SQL версии 15.0.2000.5
У меня есть таблица с этими данными:
Я хочу добиться этого формата:
Я смог сделать это, используя этот запрос:
Select s.StudentScheduleId,
STRING_AGG(s.[Day], '/') AS Schedule,
s.StartTime,
s.EndTime
From
(
(Select s.StudentScheduleId, 'M' As [Day], s.MondayStartTime As StartTime, s.MondayEndTime As EndTime
From StudentSchedule s
Where s.Monday = 1)
UNION
(Select s.StudentScheduleId, 'T' As [Day], s.TuesDayStartTime As StartTime, s.TuesdayEndTime As EndTime
From StudentSchedule s
Where s.Tuesday = 1)
UNION
(Select s.StudentScheduleId, 'W' As [Day], s.WednesdayStartTime As StartTime, s.WednesdayEndTime As EndTime
From StudentSchedule s
Where s.Wednesday = 1)
UNION
(Select s.StudentScheduleId, 'Th' As [Day], s.ThursdayStartTime As StartTime, s.ThursdayEndTime As EndTime
From StudentSchedule s
Where s.Thursday = 1)
UNION
(Select s.StudentScheduleId, 'F' As [Day], s.FridayStartTime As StartTime, s.FridayEndTime As EndTime
From StudentSchedule s
Where s.Friday = 1)
) As s
Group By s.StudentScheduleId, s.StartTime, s.EndTime
Однако у меня есть одно беспокойство, а также одна проблема с результатами.
Спасибо за любую помощь!
Вы правы, спасибо за наводку!


Я понял, как отсортировать агрегированное расписание, используя Within GROUP и оператор case, который работает для известного набора значений. Дополнительную информацию можно найти здесь: STRING_AGG
Select s.StudentScheduleId,
STRING_AGG(s.[Day], '/') WITHIN GROUP (Order By CASE
WHEN [Day] = 'M' THEN 1
WHEN [Day] = 'T' THEN 2
WHEN [Day] = 'W' THEN 3
WHEN [Day] = 'Th' THEN 4
WHEN [Day] = 'F' THEN 5
END ASC) AS Schedule,
s.StartTime,
s.EndTime
From
(
(Select s.StudentScheduleId, 'M' As [Day], s.MondayStartTime As StartTime, s.MondayEndTime As EndTime
From StudentSchedule s
Where s.Monday = 1)
UNION
(Select s.StudentScheduleId, 'T' As [Day], s.TuesDayStartTime As StartTime, s.TuesdayEndTime As EndTime
From StudentSchedule s
Where s.Tuesday = 1)
UNION
(Select s.StudentScheduleId, 'W' As [Day], s.WednesdayStartTime As StartTime, s.WednesdayEndTime As EndTime
From StudentSchedule s
Where s.Wednesday = 1)
UNION
(Select s.StudentScheduleId, 'Th' As [Day], s.ThursdayStartTime As StartTime, s.ThursdayEndTime As EndTime
From StudentSchedule s
Where s.Thursday = 1)
UNION
(Select s.StudentScheduleId, 'F' As [Day], s.FridayStartTime As StartTime, s.FridayEndTime As EndTime
From StudentSchedule s
Where s.Friday = 1)
) As s
Group By s.StudentScheduleId, s.StartTime, s.EndTime
Я бы выполнил объединение набора данных с будними днями и порядком сортировки. Таким образом, вы анализируете исходную таблицу только один раз, а не один раз в будний день.
Я присоединяюсь в тот день, когда таблица будет поворачиваться, и добавляю по одной строке в день. Затем агрегируйте по расписанию и времени окончания начала, а также агрегируйте названия дней, упорядоченные по столбцу сортировки:
Первый CTE — это простой образец данных, CTE с именем cte — это сводная таблица, которая агрегируется на последнем этапе.
WITH tab
AS
(SELECT
*
FROM (VALUES
(15, 1, 1, 1, 1, NULL, '9:00', '11:00', '11:00', '12:30', '9:00', '11:00', '11:00', '12:30', NULL, NULL)
, (31, 1, NULL, NULL, 1, 0, '2:00', '3:15', 'NULL', 'NULL', 'NULL', 'NULL', '2:00', '3:15', NULL, NULL)
) a (StudentScheduleId, Monday, Tuesday, Wednesday, Thursday, Friday, MondayStartTime, MondayEndTime, TuesdayStartTime, TuesdayEndTime, WednesdayStartTime, WednesdayEndTime, ThursdayStartTime, ThursdayEndTime, FridayStartTime, FridayEndTime))
,cte AS(
SELECT
a.StudentScheduleId
,b.DayNm
,b.DayNum
,CASE
WHEN DayNm = 'M' THEN a.MondayStartTime
WHEN DayNm = 'T' THEN a.TuesdayStartTime
WHEN DayNm = 'W' THEN a.WednesdayStartTime
WHEN DayNm = 'Th' THEN a.ThursdayStartTime
WHEN DayNm = 'F' THEN a.FridayStartTime
END Starttime
,CASE
WHEN b.DayNm = 'M' THEN a.MondayEndTime
WHEN b.DayNm = 'T' THEN a.TuesdayEndTime
WHEN b.DayNm = 'W' THEN a.WednesdayEndTime
WHEN b.DayNm = 'Th' THEN a.ThursdayEndTime
WHEN b.DayNm = 'F' THEN a.FridayEndTime
END EndTime
FROM tab a
inner JOIN (VALUES ('M',1),('T',2),('W',3),('Th',4),('F',5))b(DayNm,DayNum)
ON
( b.DayNm = 'M' AND a.Monday = 1)
OR( b.DayNm = 'T' AND a.Tuesday = 1 )
OR( b.DayNm = 'W' AND a.Wednesday = 1)
OR( b.DayNm = 'Th' AND a.Thursday = 1)
OR( b.DayNm = 'F' AND a.Friday = 1 )
)
SELECT StudentScheduleId
,STRING_AGG(DayNm,',') WITHIN GROUP (ORDER BY daynum)
,Starttime
,EndTime
FROM cte
GROUP BY StudentScheduleId
,Starttime
,EndTime
Немного более короткое решение для вас:
SELECT *
INTO #data
FROM (
VALUES (15, 1, 1, 1, 1, NULL, N'9:00', N'11:00', N'11:00', N'12:30', N'9:00', N'11:00', N'11:00', N'12:30', cast(NULL AS nvarchar(10)), cast(NULL AS nvarchar(10)))
, (31, 1, NULL, NULL, 1, 0, N'2:00', N'3:15', NULL, NULL, NULL, NULL, N'2:00', N'3:15', NULL, NULL)
) t (StudentScheduleId,Monday,Tuesday,Wednesday,Thursday,Friday,MondayStartTime,MondayEndTime,TuesdayStartTime,TuesdayEndTime,WednesdayStartTime,WednesdayEndTime,ThursdayStartTime,ThursdayEndTime,FridayStartTime,FridayEndTime)
SELECT StudentScheduleId, starttime, endtime, STRING_AGG(shortcode, '/') WITHIN GROUP (ORDER BY daynumber)
FROM #data d
CROSS APPLY (
VALUES (1,'M',monday, mondaystarttime, mondayendtime)
, (2,'T',tuesday, tuesdaystarttime, tuesdayendtime)
, (3,'W',Wednesday, WednesdayStartTime, WednesdayEndTime)
, (4,'Th',Thursday, ThursdayStartTime, ThursdayEndTime)
, (5,'F',Friday, FridayStartTime, FridayEndTime)
) v (daynumber, shortcode, day, starttime, endtime)
WHERE v.day = 1
GROUP BY starttime, endtime,StudentScheduleId
ORDER BY StudentScheduleId, MIN(daynumber)
Вы можете разделить дни и время на строки, что значительно упрощает агрегирование.
Вам просто нужно развернуть его с помощью CROSS APPLY (VALUES, затем сгруппировать и собрать обратно.
Это будет более эффективно, чем соединения или объединения, поскольку используется только одно сканирование базовой таблицы.
SELECT
s.StudentScheduleId,
STRING_AGG(v.Day, '/') WITHIN GROUP (ORDER BY v.Ordering) AS Schedule,
v.StartTime,
v.EndTime
FROM StudentSchedule s
CROSS APPLY (VALUES
('M', 1, Monday, MondayStartTime, MondayEndTime ),
('T', 2, Tuesday, TuesdayStartTime, TuesdayEndTime ),
('W', 3, Wednesday, WednesdayStartTime, WednesdayEndTime),
('Th', 4, Thursday, ThursdayStartTime, ThursdayEndTime ),
('F', 5, Friday, FridayStartTime, FridayEndTime )
) v(Day, Ordering, IsValid, StartTime, EndTime)
WHERE v.IsValid = 1
GROUP BY
s.StudentScheduleId,
v.StartTime,
v.EndTime
ORDER BY
s.StudentScheduleId,
MIN(v.Ordering);
Надеюсь, мне не нужно говорить вам, что ваши данные денормализованы: данные должны были храниться в отдельных строках, а не в столбцах.
у меня тут ощущение дежавю
Извините, я увидел ваше сообщение только после того, как опубликовал свое.
Интересный момент: эта таблица базы данных была только что обновлена ИЗ хранения в отдельных строках, которые у меня уже работали правильно. Спасибо за ответы, тестируем!
Скажите тому, кто испортил вашу БД, чтобы он пошел и исправил ее снова.
Я приложил все усилия, чтобы найти решение, но комментарий к вашему SQL таков: поскольку вы знаете, что ваши запросы возвращают отдельный набор строк, используйте UNION ALLL, а не UNION. UNION — это оператор множества, который возвращает отдельный набор. UNION ALL добавляет результаты каждого запроса, но в конце не выполняет отдельную операцию и в целом требует гораздо меньше ресурсов.