#sql #sql-server #tsql #while-loop
#sql #sql-server #tsql #цикл while
Вопрос:
У нас есть пара миллионов строк данных, которые нам нужно «разобрать», добавив строку для каждой даты между started_at date и ended_at date . Цикл while — это то, что занимает больше всего времени в нашем запросе.
Есть идеи о том, как его оптимизировать или заменить?
IF (OBJECT_ID('TempDb..#exploded_services') IS NOT NULL)
DROP TABLE #exploded_services;
CREATE TABLE #exploded_services
(
target_date date,
move_id varchar(30),
initiation_id varchar(30),
initiated_at date,
booked_at date,
transferee varchar(60),
account_id varchar(30),
mc_id varchar(30),
po varchar(60),
weight int,
service varchar(150),
started_at date,
ended_at date,
location_id nvarchar(64),
description varchar(max),
provider varchar(max),
mode varchar(60),
origin_location_id nvarchar(64),
destination_location_id nvarchar(64),
transferee_phone varchar(40),
transferee_email varchar(100),
status varchar(10),
ordinal int
);
WHILE (@pointer <= @end_date)
BEGIN
INSERT INTO #exploded_services
SELECT
@pointer,
svcs.*
FROM #Services svcs
WHERE @pointer BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,@end_date)
SET @pointer = DATEADD(dd, 1, @pointer)
END;
Комментарии:
1. Добавьте RowNumber в оператор select и user DATEADD(dd, значение столбца Row_Number, @pointer) в предложении where . Одним оператором select можно вставить все строки.
2. Просто сделайте это с помощью одного оператора insert . В чем смысл цикла здесь?
3. Пожалуйста, ознакомьтесь с различием между декларативными и императивными языковыми структурами. В этом заключается ваш ответ. НИКОГДА не используйте циклы внутри декларативных операторов SQL.
4. Кроме того, пожалуйста, не используйте сокращение like
dd
. Не намного больше усилий для вводаday
, но он, несомненно, более удобочитаемый (не говоря уже о надежности).5. Вы создаете дни для диапазонов дат. На языке программирования это делается с помощью цикла. В SQL для этого обычно используется рекурсивный запрос. Сейчас у меня нет времени публиковать ответ. Надеюсь, это сделает кто-нибудь другой.
Ответ №1:
- Создайте таблицу с одним столбцом даты.
- Заполните его всеми возможными датами, которые применяются к вашим службам.
- Заполните вашу целевую таблицу:
INSERT INTO #exploded_services
SELECT
dates_table.date,
svcs.*
FROM #Services svcs
INNER JOIN dates_table ON dates_table.date BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,_arbitrary_end_date_)
Ответ №2:
Этого можно добиться с помощью таблицы подсчета. Вот пример того, как это сделать, используя созданный на лету с помощью каскадных cte.
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteTally(n) AS(
SELECT TOP(DATEDIFF(DD, @pointer, @end_date) 1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 n
FROM E4
)
INSERT INTO #exploded_services
SELECT
DATEADD( dd, n @pointer),
svcs.*
FROM #Services svcs
JOIN cteTally t ON DATEADD( dd, n @pointer) BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,@end_date);
Ответ №3:
Вы можете попробовать приведенный ниже код с использованием CTE для генерации всех необходимых дат:
-- cte to get all dates needed
;with cte as (
select @pointer ptr
union all
select DATEADD(dd, 1, @pointer) from cte
where @pointer < @end_date
)
-- adjusted insert query
INSERT INTO #exploded_services
select c.*, s.*
from #Services s
join cte c on c.ptr between s.started_at and coalesce(svcs.ended_at,@end_date)