Если честно, ответа нет ... Единственный ответ: Гонки на лошадях (Эрик Липперт) .
То, как вы пишете свой запрос, не говорит вам, какдвигатель приведёт его в исполнение.Это зависит от многих, многих влияний ...
Вы говорите двигателю, что вы хотите получить, и двигатель решает , как получить это.
Это может даже отличаться между одинаковыми вызовами в зависимости от статистики, текущих запросов, существующих кэшированных результатов и т. Д.
Как подсказка, попробуйте следующее:
USE master;
GO
CREATE DATABASE testDB;
GO
USE testDB;
GO
- я создаютаблица физических тестов с 1.000.000 строк
CREATE TABLE testTbl(ID INT IDENTITY PRIMARY KEY, SomeValue VARCHAR(100));
WITH MioRows(Nr) AS (SELECT TOP 1000000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values v1 CROSS JOIN master..spt_values v2 CROSS JOIN master..spt_values v3)
INSERT INTO testTbl(SomeValue)
SELECT CONCAT('Test',Nr)
FROM MioRows;
- Теперь мы можем начать тестировать это
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
- Ваш подход с CTE
;with t1 as (select * from testTbl)
,t2 as (select * from t1)
,t3 as (select * from t1)
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target1
from t2
join t3 on t3.ID=t2.ID;
SELECT 'Final CTE',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
- Запись промежуточного результата в физическую таблицу
SELECT * INTO test1 FROM testTbl;
SELECT 'Write into test1',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target2
from test1 t2
join test1 t3 on t3.ID=t2.ID
SELECT 'Final physical table',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
- То же, что и раньше, но с первичным ключом на промежуточной таблице
SELECT * INTO test2 FROM testTbl;
SELECT 'Write into test2',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
ALTER TABLE test2 ADD PRIMARY KEY (ID);
SELECT 'Add PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target3
from test2 t2
join test2 t3 on t3.ID=t2.ID
SELECT 'Final physical tabel with PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
- Очистить (Осторожнос реальными данными !!!)
GO
USE master;
GO
--DROP DATABASE testDB;
GO
В моей системе
- сначала занимает 674 мс,
- второй 1,205 мс (297 для записи в * 1041)*) и
- третьи 1,727 мс (285 для записи в
test2
и ~ 650 мс для создания индекса.
Хотя запросПри выполнении дважды, движок может использовать кешированные результаты.
Conclusio
Движок действительно умный ... Не пытайтесь быть умнее ...
Еслитаблица будет охватывать множество столбцов и гораздо больше данных в строке, весь тест может вернуть что-то еще ...
Если ваши CTE (подзапросы) содержат гораздо более сложные данные с объединениями, представлениями, функциями ии так, у двигателя могут возникнуть проблемы с поиском наилучшего подхода.
Если производительность имеет значение, вы можете мчаться на лошадях , чтобы проверить это. Один совет: я иногда использовал TABLE HINT довольно успешно: FORCE ORDER
.Это выполнит соединения в порядке, указанном в запросе.