Группа SQL-запросов любит значения в одной строке, а также объединяет и сортирует другие значения

Использование SQL версии 15.0.2000.5

У меня есть таблица с этими данными:

СтудентРасписаниеИд Понедельник Вторник Среда Четверг Пятница ПонедельникНачалоВремя ПонедельникВремяКонца ВторникНачалоВремя ВторникВремя окончания СредаНачалоВремя СредаВремяКонца ЧетвергНачалоВремя ЧетвергВремяКонца ПятницаНачалоВремя ПятницаВремяКонца 15 1 1 1 1 НУЛЕВОЙ 9:00 11:00 11:00 12:30 9:00 11:00 11:00 12:30 НУЛЕВОЙ НУЛЕВОЙ 31 1 НУЛЕВОЙ НУЛЕВОЙ 1 0 2:00 3:15 НУЛЕВОЙ НУЛЕВОЙ НУЛЕВОЙ НУЛЕВОЙ 2:00 3:15 НУЛЕВОЙ НУЛЕВОЙ

Я хочу добиться этого формата:

СтудентРасписаниеИд Расписание Время начала Время окончания 15 Т/Чт 11:00 12:30 15 М/Ж 9:00 11:00 31 М 9:00 11:00

Я смог сделать это, используя этот запрос:

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

Однако у меня есть одно беспокойство, а также одна проблема с результатами.

  1. Есть ли лучший, более эффективный способ сделать это с учетом производительности или просто быть более кратким? Я не ожидаю, что эта таблица достигнет миллионов записей, но я могу видеть, что со временем она вырастет до сотен тысяч.
  2. Я тестировал другие сценарии, используя большее количество дней недели с одинаковым временем начала и окончания, и результат столбца расписания может выводиться в любом порядке. (Пример: П/М/Т/Чт/Вт). Я бы хотел, чтобы это выводилось в логическом порядке: М/П/С/Ч/П. Я знаю о сортировке Within GROUP для STRING_AGG, но сортировать не по чему, иначе мне нужно будет добавить столбец сортировки, есть идеи?

Спасибо за любую помощь!

Я приложил все усилия, чтобы найти решение, но комментарий к вашему SQL таков: поскольку вы знаете, что ваши запросы возвращают отдельный набор строк, используйте UNION ALLL, а не UNION. UNION — это оператор множества, который возвращает отдельный набор. UNION ALL добавляет результаты каждого запроса, но в конце не выполняет отдельную операцию и в целом требует гораздо меньше ресурсов.

Søren Kongstad 15.03.2024 12:56

Вы правы, спасибо за наводку!

cmartin 15.03.2024 13:40
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
2
74
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Я понял, как отсортировать агрегированное расписание, используя 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);

db<>рабочий пример

Надеюсь, мне не нужно говорить вам, что ваши данные денормализованы: данные должны были храниться в отдельных строках, а не в столбцах.

у меня тут ощущение дежавю

siggemannen 15.03.2024 13:28

Извините, я увидел ваше сообщение только после того, как опубликовал свое.

Charlieface 15.03.2024 13:28

Интересный момент: эта таблица базы данных была только что обновлена ​​ИЗ хранения в отдельных строках, которые у меня уже работали правильно. Спасибо за ответы, тестируем!

cmartin 15.03.2024 13:39

Скажите тому, кто испортил вашу БД, чтобы он пошел и исправил ее снова.

Charlieface 15.03.2024 13:58

Другие вопросы по теме