Я видел пару подобных потоков, но все они, кажется, о массивных базах данных. Я создал фиктивную базу данных, чтобы продемонстрировать эту проблему, увидев этот урожай в небольшой живой базе данных этим утром.Почему мой CTE присоединяется к обновлению настолько медленнее, чем моя переменная в таблице?
Основой для этих данных является следующее: компания отслеживает фондовые портфели для своих 100 клиентов. Каждый из 1000 акций имеет ежедневную запись, в которой перечислены четыре инвестора, которые владеют им, вместе с их процентом. К сожалению, у этого есть сбой, который позволяет владельцу появляться несколько раз. Процедура анализирует данные и отделяет записи таким образом, чтобы каждый день составлял 4 записи для каждой акции, а затем складывал общую сумму портфеля для каждого владельца. Однако, поскольку существует несколько записей, это может завышать значение для этого владельца. Таким образом, флаг вводится для идентификации любого из этих дубликатов. Позже в коде значение каждой строки умножается на этот флаг, который равен 0 для дубликата и 1, если нет.
У меня есть пять способов обновления этого флага. Я начинаю с 0, это просто использование CTE с оператором SELECT в качестве базовой линии; он занимает около 0,07 секунды. 1 использует CTE с JOIN для обновления таблицы и занимает около 48 секунд. 2 использует вложенный оператор select вместо CTE и занимает около 48 секунд. 3 сбрасывает CTE на переменную таблицы и присоединяется к ней и занимает около 0,13 секунды. 4 Я думал, что это будет наименее эффективным, потому что он использует контурный цикл и обновляет по одной строке за раз, но это заняло всего 0,17 секунды. 5 использует оператор CASE для обновления всех строк, присоединенных к CTE, и занимает около 48 секунд.
DECLARE @OwnRec TABLE (
StockID INT
, TradeDate DATE
, Shares DECIMAL(4,0)
, Price DECIMAL(4,2)
, Owner1 INT
, Owner1Pct DECIMAL(3,2)
, Owner2 INT
, Owner2Pct DECIMAL(3,2)
, Owner3 INT
, Owner3Pct DECIMAL(3,2)
, Owner4 INT
, Owner4Pct DECIMAL(3,2)
)
DECLARE @OwnRec2 TABLE (
RecID INT IDENTITY
, StockID INT
, TradeDate DATE
, Shares DECIMAL(4,0)
, Price DECIMAL(4,2)
, Owner0 INT
, Owner0Pct DECIMAL(3,2)
, OwnerNum INT
, DupeOwner TINYINT
)
DECLARE @CullDupe TABLE (
ID INT IDENTITY
, RecID INT
)
DECLARE @Method INT
, @Counter1 INT = 0
, @StartTime DATETIME
--Populate tables with dummy data
WHILE @Counter1 < 1000
BEGIN
SET @Counter1 += 1
INSERT INTO @OwnRec (
StockID
, TradeDate
, Shares
, Price
, Owner1
, Owner1Pct
, Owner2
, Owner2Pct
, Owner3
, Owner3Pct
, Owner4
, Owner4Pct
)
SELECT @Counter1
, '2016-09-26'
, ROUND((RAND() * 1000 + 500)/25,0)*25
, ROUND((RAND() * 30 + 20),2)
, ROUND((RAND() * 100 + .5),0)
, CAST(ROUND((RAND() * 5 + .5),0)*.05 AS DECIMAL(3,2))
, ROUND((RAND() * 100 + .5),0)
, CAST(ROUND((RAND() * 5 + .5),0)*.05 AS DECIMAL(3,2))
, ROUND((RAND() * 100 + .5),0)
, CAST(ROUND((RAND() * 5 + .5),0)*.05 AS DECIMAL(3,2))
, ROUND((RAND() * 100 + .5),0)
, CAST(ROUND((RAND() * 5 + .5),0)*.05 AS DECIMAL(3,2))
END
SET @Counter1 = 0
WHILE @Counter1 < 1000
BEGIN
SET @Counter1 += 1
INSERT INTO @OwnRec (
StockID
, TradeDate
, Shares
, Price
, Owner1
, Owner1Pct
, Owner2
, Owner2Pct
, Owner3
, Owner3Pct
, Owner4
, Owner4Pct
)
SELECT @Counter1 + 1000
, '2016-09-27'
, Shares
, ROUND(Price * ROUND(RAND()*10 + .5,0)*.01+.95,2)
, Owner1
, Owner1Pct
, Owner2
, Owner2Pct
, Owner3
, Owner3Pct
, Owner4
, Owner4Pct
FROM @OwnRec WHERE StockID = @Counter1
END
UPDATE orx
SET Owner2Pct = Owner1Pct
FROM @OwnRec orx
WHERE Owner1 = Owner2
UPDATE orx
SET Owner3Pct = Owner1Pct
FROM @OwnRec orx
WHERE Owner1 = Owner3
UPDATE orx
SET Owner4Pct = Owner1Pct
FROM @OwnRec orx
WHERE Owner1 = Owner4
UPDATE orx
SET Owner3Pct = Owner2Pct
FROM @OwnRec orx
WHERE Owner2 = Owner3
UPDATE orx
SET Owner4Pct = Owner2Pct
FROM @OwnRec orx
WHERE Owner2 = Owner4
UPDATE orx
SET Owner4Pct = Owner3Pct
FROM @OwnRec orx
WHERE Owner3 = Owner4
INSERT INTO @OwnRec2
SELECT StockID, TradeDate, Shares, Price, Owner1 AS Owner0, Owner1Pct, 1, 1 AS Owner0Pct
FROM @OwnRec
UNION
SELECT StockID, TradeDate, Shares, Price, Owner2 AS Owner0, Owner2Pct, 2, 1 AS Owner0Pct
FROM @OwnRec
UNION
SELECT StockID, TradeDate, Shares, Price, Owner3 AS Owner0, Owner3Pct, 3, 1 AS Owner0Pct
FROM @OwnRec
UNION
SELECT StockID, TradeDate, Shares, Price, Owner4 AS Owner0, Owner4Pct, 4, 1 AS Owner0Pct
FROM @OwnRec
--END Populate tables with dummy data
SET @StartTime = GETDATE()
SET @Method = 5 -- Choose which method to test
--CASE 0: Just identify duplicates
IF @Method = 0
BEGIN
; WITH CullDupe
AS (
SELECT RecID, ROW_NUMBER() OVER (PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2
)
SELECT * FROM CullDupe WHERE rn > 1
END
--CASE 1: Update on JOIN to CTE
IF @Method = 1
BEGIN
; WITH CullDupe
AS (
SELECT RecID, ROW_NUMBER() OVER (PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2
)
UPDATE OR2
SET DupeOwner = 0
FROM @OwnRec2 OR2
JOIN CullDupe cd
ON OR2.RecID = cd.RecID
WHERE rn > 1
END
--CASE 2: Update on JOIN to nested SELECT
IF @Method = 2
BEGIN
UPDATE OR2
SET DupeOwner = 0
FROM @OwnRec2 OR2
JOIN (SELECT RecID, ROW_NUMBER() OVER
(PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2) cd
ON OR2.RecID = cd.RecID
WHERE rn > 1
END
--CASE 3: Update on JOIN to temp table
IF @Method = 3
BEGIN
; WITH CullDupe
AS (
SELECT RecID, ROW_NUMBER() OVER (PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2
)
INSERT INTO @CullDupe SELECT RecID FROM CullDupe WHERE rn > 1
UPDATE OR2
SET DupeOwner = 0
FROM @OwnRec2 OR2
JOIN @CullDupe cd
ON OR2.RecID = cd.RecID
END
--CASE 4: Update using counted loop
IF @Method = 4
BEGIN
; WITH CullDupe
AS (
SELECT RecID, ROW_NUMBER() OVER (PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2
)
INSERT INTO @CullDupe SELECT RecID FROM CullDupe WHERE rn > 1
SET @Counter1 = 0
WHILE @Counter1 < (SELECT MAX(ID) FROM @CullDupe)
BEGIN
SET @Counter1 += 1
UPDATE OR2
SET DupeOwner = 0
FROM @OwnRec2 OR2
WHERE RecID = (SELECT RecID FROM @CullDupe WHERE ID = @Counter1)
END
END
--CASE 5: Update using JOIN to CTE, but updating all rows (CASE to identify)
IF @Method = 5
BEGIN
; WITH CullDupe
AS (
SELECT RecID, ROW_NUMBER() OVER (PARTITION BY StockID, TradeDate, Owner0 ORDER BY OwnerNum) AS rn
FROM @OwnRec2
)
UPDATE OR2
SET DupeOwner = CASE WHEN rn > 1 THEN 0 ELSE 1 END
FROM @OwnRec2 OR2
JOIN CullDupe cd
ON OR2.RecID = cd.RecID
END
SELECT 'Method ' + CAST(@Method AS NVARCHAR(1)) + ': ' + CAST(DATEDIFF(ms,@StartTime,GETDATE()) AS NVARCHAR(10)) + ' milliseconds'
Это абсолютно невероятно. OPTION (RECOMPILE) взял меня с 48,136 мс до 13 мс.Спасибо, что нашли время, чтобы объяснить это. – DaveX
Последующие меры. У меня был еще один шанс воспользоваться этим сегодня. 18 000 строк, без проблем, со всякими объединениями и всякими вещами. Но мне нужно было добавить еще одну пьесу, и это соединение заняло ее более двух минут. Сводит меня с ума. Я попробовал CTE, затем переменную таблицы, затем таблицу temp. Наконец я вернулся к этому сообщению и посмотрел на код, который я создал в результате. Я закончил создание моей исходной переменной таблицы, а затем выполнил обновление, используя другую переменную таблицы, используя OPTION (RECOMPILE). 6 секунд. 6 секунд! Я получил его до 55, и это было потрясающе, сравнительно. Но теперь 6! Вау. – DaveX