У меня есть 2 таблицы в базе данных, первая содержит следующую информацию
Следует отметить, что секция будет иметь 1 запись для каждого дня заседания секции, 1 — понедельник, 2 — вторник и т. д. На примере снимка экрана раздел 390 собирается в понедельник и среду, а первый день собрания — 23.01.23, а конец — 17.05.23.
У меня есть второй стол с праздниками
У меня есть третья таблица, которая для номера секции показывает дни ее заседаний. Однако он не учитывает праздничные дни.
Я пытался решить проблему на питоне, но потом узнал, что SSRS поддерживает скрипты на питоне только в версии 2017, а у меня на работе используется 2016.
Какие существуют запросы SQL Server 2016, которые каким-то образом позволили бы мне перебирать строки раздела и проверять, попадает ли праздник между начальной и конечной датой и приходится ли на день, когда раздел встречается, и уменьшать количество дней собрания на 1 для каждый праздник, который соответствует этим критериям?
Используя данные в качестве примера, между датой начала и окончания раздела 390 есть 2 праздничных дня понедельника, поэтому NbrOfDays из третьей таблицы необходимо обновить до 31 с 33, поскольку в ней тип дня собрания 1 = понедельник.
В вашем подходе не используется то, в чем хорош SQL: транзакции на основе наборов. Нет необходимости перебирать записи. Вместо этого напишите запрос, который объединяет данные вместе с учетом всех условий. Оставьте цикл по записям языкам программирования, отличным от SQL. Работая с SQL, всегда думайте о множестве. (Я использую циклы в SQL, но экономно). Опубликуйте данные третьей таблицы, так как ваше описание немного расплывчато.
Я не думаю, что цифры, которые вы указываете, вполне складываются на основе ваших выборочных данных. например в вашей праздничной таблице есть только одна дата, которая попадает между 2023-01-23
и 2023-05-17
, а не две, как вы сказали.
Я не уверен, как вы получаете 16 как общее количество для раздела 655, если вы не считаете выходные?
в любом случае ... Не беспокойтесь о длине этого ответа, фактический бит ответа - это всего несколько строк кода.
В любом случае, я думаю, что вы можете просто создать представление, которое решит это для вас. В третьей таблице нет необходимости, представление заменит третью таблицу.
Извините, если я добавляю комментарии, которые предполагают, что вы не знакомы с базовыми запросами, я предполагаю это только из подхода, который вы хотели использовать.
Настройте данные для репликации вашего образца
Сначала я создал ваши образцы данных со следующими временными таблицами с именами #meets и #hols.
CREATE TABLE #meets(SECTION_NUMBER int, SECTION_ID int, MEETING_ID int, DAY_TYPE int, MEETING_NUMBER int, DATE_TIME_BEGIN DATETIME, DATE_TIME_END datetime)
INSERT INTO #meets VALUES
(390, 166316, 102451, 1, 1, '2023-01-23 9:30:00', '2023-05-17 10:50:00'),
(390, 166316, 102451, 3, 1, '2023-01-23 9:30:00', '2023-05-17 10:50:00'),
(655, 166314, 102452, 3, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00'),
(655, 166314, 102452, 7, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00'),
(283, 166315, 102453, 7, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00')
CREATE TABLE #hols([description] varchar(30), DATE_VALUE date, DayOfWeek int, Day char(1))
INSERT INTO #hols VALUES
('Winter Break' , '2023-01-02', 1, 'M'),
('MLK Day' , '2023-01-16', 1, 'M'),
('Lincoln''s Day' , '2023-02-17', 5, 'F'),
('Non-Teaching Day' , '2023-02-18', 6, 'S'),
('Washington''s Day' , '2023-02-20', 1, 'M')
Добавить таблицу даты/календаря
Затем я создал таблицу дат. Возможно, у вас уже есть один, так что используйте его, если вы это сделаете, но если нет, создайте его в своей базе данных, поскольку они невероятно полезны для таких вещей.
В этом посте показано, как создать один MakingADateTable
Я включил код здесь на случай, если ссылка не работает.
-- prevent set or regional settings from interfering with
-- interpretation of dates / literals
SET DATEFIRST 1 -- 1 = Monday, 7 = Sunday
DECLARE @StartDate date = '20100101'; -- << change this if required
DECLARE @CutoffDate date = DATEADD(DAY, -1, DATEADD(YEAR, 30, @StartDate));
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, @StartDate, @CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, @StartDate) FROM seq
),
src AS
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheISOWeek = DATEPART(ISO_WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = DATEPART(Quarter, d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
)
SELECT *
INTO myDateTable -- << CHANGE TABLE NAME HERE IF YOU NEED TO
FROM src
ORDER BY TheDate
OPTION (MAXRECURSION 0);
Теперь ответ!
Следующее даст вам каждый номер section_number, день недели для встречи и количество дней.
SELECT
SECTION_NUMBER
, TheDayName
, NbrOfDays = COUNT(*)
FROM #meets m
JOIN myDateTable d on d.TheDate BETWEEN CAST(m.DATE_TIME_BEGIN as date) AND CAST(m.DATE_TIME_END as date) and m.DAY_TYPE = d.TheDayOfWeek
LEFT JOIN #hols h on d.TheDate = h.DATE_VALUE
WHERE h.DATE_VALUE IS NULL
and d.TheDate >=CAST(GETDATE() as Date) -- optionaly if you want ignore past meetings
GROUP BY SECTION_NUMBER, DayOfWeek, TheDayName
ORDER BY SECTION_NUMBER, DayOfWeek
Все, что это делает, — это присоединение каждой даты в таблице myDateTable к таблице #meets, где даты попадают между начальной и конечной датами в таблице #meets, а также объединение в day_type, поэтому возвращаются только совпадающие дни. Затем он оставляет соединения с таблицей #hols, а затем мы включаем только те даты, для которых в таблице #hols не было найдено совпадений. Затем мы просто группируем результаты и подсчитываем, сколько записей в каждой группе.
дает нам это
Если вы просто хотите, чтобы результаты выглядели как в вашем примере, мы можем просто удалить группировку DayOfWeek следующим образом.
SELECT
SECTION_NUMBER
, NbrOfDays = COUNT(*)
FROM #meets m
JOIN myDateTable d on d.TheDate BETWEEN CAST(m.DATE_TIME_BEGIN as date) AND CAST(m.DATE_TIME_END as date) and m.DAY_TYPE = d.TheDayOfWeek
LEFT JOIN #hols h on d.TheDate = h.DATE_VALUE
WHERE h.DATE_VALUE IS NULL
and d.TheDate >=CAST(GETDATE() as Date) -- optionaly if you want ignore past meetings
GROUP BY SECTION_NUMBER
ORDER BY SECTION_NUMBER
что дает нам это...
Я оставил там строку для фильтрации прошлых встреч, но вы можете прокомментировать ее, если она вам не нужна.
Если вы хотите превратить эти запросы в постоянные представления, вы можете сделать это с помощью чего-то вроде
CREATE VIEW MeetingCountBySectionAndDay AS
[copy query from above here]
Затем вы можете просто запросить представление как таблицу с чем-то вроде
SELECT * FROM MeetingCountBySectionAndDay
Если праздники добавляются/удаляются или встречи добавляются/редактируются, представление будет автоматически отражать изменения без необходимости выполнять какую-либо работу.
Удивительно и спасибо. Я всегда плохо работал с датами в SQL, но ваша ссылка и пример таблицы дней проясняют ситуацию.
И спасибо, что нашли время преобразовать мои примеры таблиц в SQL, теперь я понимаю, насколько это было бы полезно, если бы я сделал это в своем первом посте.
Без проблем. Как прокомментировали другие, при работе в SQL, если вы обнаружите, что повторяете записи, это, вероятно, (но не всегда) неправильный подход к нему, плюс, как и многие люди в SO, мне просто нравится помогать другим.
«У меня есть третья таблица, в которой для номера секции указаны дни ее заседаний». - Пожалуйста, предоставьте его определение с некоторыми примерами данных. Без него трудно ответить на ваш вопрос.