powered by simpleCommunicator - 2.0.51     © 2025 Programmizd 02
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Вопрос про оптимизацию SQL-запроса
12 сообщений из 12, страница 1 из 1
Вопрос про оптимизацию SQL-запроса
    #40056214
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Здравствуйте!

Есть такие заданные таблицы
SQL
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
DECLARE @ГрафикиРаботы TABLE
(
	[ID] iNT,
	[Вариант графика] VARCHAR(1),	
	[Время начала] TIME,
	[Время окончания] TIME	
);

INSERT INTO @ГрафикиРаботы
	([ID], [Вариант графика], [Дата начала], [Дата окончания])
VALUES
	(0, 'Д', '09:00:00', '18:00:00'),
	(1, 'Н', '18:00:00', '09:00:00'),
	(2, 'С', '09:00:00', '09:00:00'),
	(3, 'В', null, null);


ID Вариант графика Время начала Время окончания0 Д 09:00:00 18:00:001 Н 18:00:00 09:00:002 С 09:00:00 09:00:003 В NULL NULL
SQL
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
DECLARE @ГрафикиРаботыСотрудников TABLE
(
	[ID] INT,
	[Сотрудник] NVARCHAR(10),
	[График работы] NVARCHAR(7),	
	[Дата начала] DATE,
	[Дата окончания] DATE
);

INSERT INTO @ГрафикиРаботыСотрудников
	([ID], [Сотрудник], [График работы], [Дата начала], [Дата окончания])
VALUES 
        (0, 'Иванов', 'ДДВ', '01.01.2010', '08.01.2020'),
	(1, 'Иванов', 'НВ', '09.01.2020', '16.01.2020'),
	(2, 'Иванов', 'СВ', '17.01.2020', '31.12.9999'),
	(3, 'Петров', 'ДВ', '01.01.2020', '08.01.2020'),
	(4, 'Петров', 'НВ', '09.01.2020', '16.01.2020'),
	(5, 'Петров', 'ДВВ', '17.01.2020', '31.12.9999'),
	(6, 'Сидоров', 'ДДВ', '01.01.2020', '08.01.2020'),
	(7, 'Сидоров', 'ДДВ', '09.01.2020', '16.01.2020'),
	(8, 'Сидоров', 'ДДВ', '17.01.2020', '31.12.9999');

'31.12.9999' - это означает расписание продолжает действовать. Предполагаем, что периоды не пересекаются для каждого сотрудника. Следовательно, проверку при INSERT не ставим.

ID Сотрудник График работы Дата начала Дата окончания0 Иванов ДДВ 01.01.2010 08.01.20201 Иванов НВ 09.01.2020 16.01.20202 Иванов СВ 17.01.2020 31.12.99993 Петров ДВ 01.01.2020 08.01.20204 Петров НВ 09.01.2020 16.01.20205 Петров ДВВ 17.01.2020 31.12.99996 Сидоров ДДВ 01.01.2020 08.01.20207 Сидоров ДДВ 09.01.2020 16.01.20208 Сидоров ДДВ 17.01.2020 31.12.9999
SQL
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
DECLARE @ВыходНаРаботу TABLE
(
	[ID] INT,
	[Сотрудник] NVARCHAR(10),
	[Текущий график] VARCHAR(1),
	[Дата и время начала] DATETIME,
	[Дата и время окончания] DATETIME	
);

Это результирующая таблица.

Результат должен быть следующим:

Сотрудник График работы Текущий график Текущая дата Дата и время начала Дата и время окончанияИванов ДДВ Д 01.01.2020 01.01.2020 09:00 01.01.2020 18:00Иванов ДДВ Д 02.01.2020 02.01.2020 09:00 02.01.2020 18:00Иванов ДДВ Д 04.01.2020 04.01.2020 09:00 04.01.2020 18:00Иванов ДДВ Д 05.01.2020 05.01.2020 09:00 05.01.2020 18:00Иванов ДДВ Д 07.01.2020 07.01.2020 09:00 07.01.2020 18:00Иванов ДДВ Д 08.01.2020 08.01.2020 09:00 08.01.2020 18:00Иванов НВ Н 09.01.2020 09.01.2020 18:00 10.01.2020 09:00Иванов НВ Н 11.01.2020 11.01.2020 18:00 12.01.2020 09:00Иванов НВ Н 13.01.2020 13.01.2020 18:00 14.01.2020 09:00Иванов НВ Н 15.01.2020 15.01.2020 18:00 16.01.2020 09:00Иванов СВ С 17.01.2020 17.01.2020 09:00 18.01.2020 09:00Иванов СВ С 19.01.2020 19.01.2020 09:00 20.01.2020 09:00..................

Д - это дневная смена, Н - ночная смена, С - суточная смена и В - выходной. Выходные дни отсекаются. Расписание, то есть график работы, цикличное: если закончилось, то начинается с начала.Написал SQL-запрос с использованием рекурсии.
SQL
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
DECLARE @DATE_END DATE = '01.02.2020'; -- Для лимита, чтобы не пробегаться до 31.12.9999

WITH CTE AS (
    SELECT 
                [ID],
		[Сотрудник],
		[График работы],
		1 AS [Position],
		LEN([График работы]) AS [MAX_POSITION],		
                CAST(SUBSTRING([График работы], 1, 1) AS VARCHAR(1)) [Текущий график],
		[Дата начала],
		[Дата окончания],
		[Дата начала] AS [Текущая дата]
	FROM
		@ГрафикиРаботыСотрудников
    UNION ALL 
    SELECT 
                [ID],
		[Сотрудник],
		[График работы],		
		(CASE WHEN (Position + 1 <= [MAX_POSITION]) THEN Position + 1 ELSE 1 END),		
		[MAX_POSITION],
                CAST(SUBSTRING([График работы], (CASE WHEN (Position + 1 <= [MAX_POSITION]) THEN Position + 1 ELSE 1 END), 1) AS VARCHAR(1)),
		[Дата начала],
		[Дата окончания], 
		DATEADD(Day, 1, [Текущая дата])
    FROM
		CTE
    WHERE		
		DATEADD(Day, 1, [Текущая дата]) <= (CASE
                                                       WHEN [Дата окончания] <= @DATE_END 
                                                       THEN [Дата окончания]
                                                       ELSE @DATE_END
                                                    END)
)

SELECT	
	[Сотрудник],
	[График работы],			
        [Текущий график],	
	FORMAT([Текущая дата],'dd.MM.yyyy') AS [Текущая дата],
	FORMAT(
			CAST([Текущая дата] AS DATETIME) + CAST(CAST([Время начала] AS TIME) as DATETIME),
			'dd.MM.yyyy HH:mm'
			)AS [Дата и время начала],		
	FORMAT(
		DATEADD(ss,
				(CASE
					WHEN ([Время начала] < [Время окончания]) 
						THEN (DATEDIFF (ss, [Время начала], [Время окончания]))
					WHEN ([Время начала] > [Время окончания]) 
						THEN 86400
							- 
							(
                                                             DATEPART(hh, [Время начала]) * 3600
                                                             +
                                                             DATEPART(mi, [Время начала]) * 60
                                                             +
                                                             DATEPART(ss, [Время начала]))
							+
							(    DATEPART(hh, [Время окончания]) * 3600
                                                             +
                                                             DATEPART(mi, [Время окончания]) * 60 
                                                             +
                                                             DATEPART(ss, [Время окончания]))						
					WHEN ([Время начала] = [Время окончания])
						THEN 86400
				END),
				CAST([Текущая дата] AS DATETIME) + CAST(CAST([Время начала] AS TIME) as DATETIME)
		),
		'dd.MM.yyyy HH:mm'
	) AS [Дата и время окончания]
FROM
	CTE
LEFT JOIN
	@ГрафикиРаботы Tab1
ON
	CTE.[Текущий график] = Tab1.[Вариант графика]
WHERE
	[Текущий график] <> 'В' 
ORDER BY	
	[Сотрудник],
	[Дата начала],	
	[Текущая дата]	

OPTION(MAXRECURSION 100);

Запрос работает, но он получился не оптимальным. И отсюда появляются следующие вопросы:


1. Если изменить на более раннюю дату
Код: sql
1.
2.
3.
4.
5.
INSERT INTO @ГрафикиРаботыСотрудников
	([ID], [Сотрудник], [График работы], [Дата начала], [Дата окончания])
VALUES 
    (0, 'Иванов', 'ДДВ', '01.01.2010', '08.01.2020'),
    ... 

то пишет "Выполнение инструкции прервано. Максимальная рекурсия 100 была использована до завершения инструкции." . Можно написать так
Код: sql
1.
OPTION(MAXRECURSION 10000);

то заработает. Но можно указать еще более раннюю дату. И не хотелось каждый раз менять ограничение. Ставить ноль небезопасно.

Исходя такой логики как можно запрос было бы написать более оптимальнее для больших периодов?


2. Учитывая, что продолжающийся действующий график в таблице @ГрафикиРаботыСотрудников отмечается как '31.12.9999'. Если не ставить такое ограничение
Код: sql
1.
2.
3.
4.
DECLARE @DATE_END DATE = '01.02.2020';
...
WHERE
    DATEADD(Day, 1, [Текущая дата]) <= (CASE WHEN [Дата окончания] <= @DATE_END THEN [Дата окончания] ELSE @DATE_END END)


и оставить так
Код: sql
1.
OPTION(MAXRECURSION 0)

то запрос будет долгим.

Скажите, как Вы оцениваете данное решение с помощью такого ограничения? Есть ли более оптимальный вариант?


3. В разных колонках присутствуют одинаковые выражения.
Код: sql
1.
2.
3.
(CASE WHEN (Position + 1 <= [MAX_POSITION]) THEN Position + 1 ELSE 1 END),
[MAX_POSITION],
CAST(SUBSTRING([График работы], (CASE WHEN (Position + 1 <= [MAX_POSITION]) THEN Position + 1 ELSE 1 END), 1) AS VARCHAR(1)),

Возможно ли чтобы не повторяться эти выражения поместить как переменную для выражений, но не делать как функцию? Учитывая, что везде используются одинаковые имена полей.


4. Возможно ли было поле [Дата и время окончания] написать по короче? Именно в этой части
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
86400
- 
(
    DATEPART(hh, [Время начала]) * 3600
    +
    DATEPART(mi, [Время начала]) * 60
    +
    DATEPART(ss, [Время начала]))
+
(
    DATEPART(hh, [Время окончания]) * 3600
    +
    DATEPART(mi, [Время окончания]) * 60 
    +
    DATEPART(ss, [Время окончания])
)

Получилось как то намудрено.


5. Учитывая, что в поле [Дата и время окончания] для определения разницы между время начала и окончания смены условие сравнивает на само начало и окончание. Решение не очень удачное. Так как не учитывается, что в таблице @ГрафикиРаботы время может быть задано такое:

ID Вариант графика Дата начала Дата окончания0 Д 09:00:00 00 :00:001 Н 18:00:00 23 :00:002 С 09:00:00 12 :00:00 или 08 :00:003 В NULL NULL
Или в суточной смене нужно работать двое суток или более. Учитывая, что в перспективе могут быть несколько вариантов для дневной, ночной или суточной смены.

Как правильнее тут написать?


6. Есть тут еще то, что не учитывается? Возможно ли бы эту задачу было бы решить без рекурсии?
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056218
aleks222
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Нахера тут "рекурсия"?
Шобы було?
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056245
Владислав Колосов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ferzmikk,

а как Вы выбираете из ДДВ, когда у сотрудника Д, а когда В?
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056264
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ferzmikk
Код: sql
1.
2.
3.
4.
5.
6.
INSERT INTO @ГрафикиРаботыСотрудников
	([ID], [Сотрудник], [График работы], [Дата начала], [Дата окончания])
VALUES 
        (0, 'Иванов', 'ДДВ', '01.01.2020', '08.01.2020'),
	(1, 'Иванов', 'НВ', '09.01.2020', '16.01.2020'),
	...



ID Сотрудник График работы Дата начала Дата окончания0 Иванов ДДВ 01.01.20 2 0 08.01.20201 Иванов НВ 09.01.2020 16.01.2020...............
Опечатка в дате, исправил.
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056266
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Владислав Колосов
ferzmikk,

а как Вы выбираете из ДДВ, когда у сотрудника Д, а когда В?
В таблице @ГрафикиРаботыСотрудников берем первую строку. У сотрудника Иванов график работы "ДДВ". Дата начала и окончания такого расписания соответственно: 01.01.2020 и 08.01.2020. Начинаем с 01.01.2020.

01.01.2020 - "Д"
02.01.2020 - "Д"
03.01.2020 - "В"
04.01.2020 - "Д"
05.01.2020 - "Д"
06.01.2020 - "В"
07.01.2020 - "Д"
08.01.2020 - "Д"

Потом в таблице @ГрафикиРаботыСотрудников берем вторую строку. График работы "НВ". Дата начала и окончания такого расписания соответственно: 09.01.2020 и 16.01.2020. Продолжаем

09.01.2020 - "Н"
10.01.2020 - "В"
11.01.2020 - "Н"
12.01.2020 - "В"
13.01.2020 - "Н"
14.01.2020 - "В"
15.01.2020 - "Н"
16.01.2020 - "В"

Дальше аналогично
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056293
Владислав Колосов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ferzmikk,

правильно ли я понял, что в первом сообщении таблица результата заполнена неверно?
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056296
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Владислав Колосов
ferzmikk,

правильно ли я понял, что в первом сообщении таблица результата заполнена неверно?
Таблица результата верная. Но без выходных.
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056326
invm
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
select
 a.Сотрудник, d.[Вариант графика], d.Дата, e.[Время начала], e.[Время окончания]
from
  @ГрафикиРаботыСотрудников a cross apply
  (select datediff(day, a.[Дата начала], isnull(a.[Дата окончания], getdate()))) b(days) cross apply
  (
   select top (b.days)
    row_number() over (order by 1/0) - 1
   from
    master.dbo.spt_values sptv1 cross join
    master.dbo.spt_values sptv2
  ) c(n) cross apply
  (select dateadd(day, c.n, a.[Дата начала]), substring(a.[График работы], c.n % len(a.[График работы]) + 1, 1)) d([Дата], [Вариант графика]) join
  @ГрафикиРаботы e on e.[Вариант графика] = d.[Вариант графика];
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056391
Владислав Колосов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ferzmikk,

решение с дополнительной таблицей - справочником графиков.

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
DECLARE @ТипГрафика TABLE
	([График работы] NVARCHAR(10), [Очередность типа графика] INT, [ID Графика] INT);

INSERT @ТипГрафика ([График работы], [Очередность типа графика],  [ID Графика]) VALUES
	('ДДВ', 1, 0),
	('ДДВ', 2, 0),
	('ДДВ', 3, 3),
	('ДBВ', 1, 0),
	('ДBВ', 2, 3),
	('ДBВ', 3, 3),
	('НВ', 1, 1),
	('НВ', 2, 3),
	('СВ', 1, 2),
	('СВ', 2, 3),
	('ДВ', 1, 0),
	('ДВ', 2, 3);

select gst.*,
	numbers.*,
	tg.[Очередность типа графика],
	gr.[Время начала],
	gr.[Время окончания]
from @ГрафикиРаботыСотрудников gst
inner join @ТипГрафика tg on tg.[График работы] = gst.[График работы]
inner join @ГрафикиРаботы gr on gr.id = tg.[ID Графика]
-- таблица чисел
cross apply (select cast(dateadd(day, m1.number, [Дата начала]) as date) [Дата работы], m1.number + 1 number, m2.number number1
			 from master.dbo.spt_values m1
			 cross join master.dbo.spt_values m2
			 where m1.type = 'P'
				and m1.number >= 0
				and m2.type = 'P'
				and m2.number >= 0
				and cast(dateadd(day, m1.number, [Дата начала]) as date) <= cast([Дата окончания] as date)
			) numbers
where gst.ID in  (0, 1) -- ограничение для тестирования
	and numbers.number = tg.[Очередность типа графика] + numbers.number1 * len(gst.[График работы])
order by gst.id, numbers.[Дата работы]



можно сделать свою таблицу чисел или расширить её в запросе с помощью приёма в сообщении выше.
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40056454
Alex_Toms
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ferzmikk
Есть своя система учёта отработанного времени на SQL.
Разработка с "нуля", в эксплуатации более 15 лет.
На выходе: табель рабочего времени, данные для расчёта заработной платы, расчета отпусков и больничных.
Выгрузка стажа в пенсионный фонд и различные отчёты и аналитика .
Судя по всему у вас самое начало.
Пытаюсь понять вашу задачу, проектируете с "нуля" или адаптируете готовую систему?
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40057658
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
invm, Владислав Колосов,

Спасибо, по экспериментирую!
...
Рейтинг: 0 / 0
Вопрос про оптимизацию SQL-запроса
    #40057668
ferzmikk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alex_Toms
Есть своя система учёта отработанного времени на SQL .
Разработка с "нуля", в эксплуатации более 15 лет.
На выходе: табель рабочего времени, данные для расчёта заработной платы, расчета отпусков и больничных.
Выгрузка стажа в пенсионный фонд и различные отчёты и аналитика .
Надо это изучить.
Судя по всему у вас самое начало.
Пытаюсь понять вашу задачу, проектируете с "нуля" или адаптируете готовую систему?Я не хочу изобретать велосипед. Во-первых, хочу SQL овладеть в совершенстве. Во-вторых, научиться проектировать небольшие системы.
...
Рейтинг: 0 / 0
12 сообщений из 12, страница 1 из 1
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Вопрос про оптимизацию SQL-запроса
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]