Как создать один GUID для всех строк в пакетной вставке в запросе? - PullRequest
3 голосов
/ 21 июня 2019

Я пишу быстрое и грязное приложение для загрузки данных плана продаж в SQL Server (FWIW 2008, хотя я не думаю, что конкретная версия имеет значение).

Набор данных представляет собой корпоративный план продаж: несколько тысяч строк единиц, долларов и цены для каждой комбинации клиента, номера детали и месяца. Эти данные обновляются каждые несколько недель, и важно отслеживать, кто их изменил и какие изменения были.

--  Metadata columns are suffixed with ' ##', to enable an automated 
--  tool I wrote to handle repetitive tasks such as de-duplication of 
--  records whose values didn't change in successive versions of the 
--  forecast.  

CREATE TABLE [SlsPlan].[PlanDetail]
(
    [CustID] [char](15) NOT NULL,
    [InvtID] [char](30) NOT NULL,
    [FiscalYear] [int] NOT NULL,
    [FiscalMonth] [int] NOT NULL,
    [Version Number ##] [int] IDENTITY(1,1) NOT NULL,

    [Units] [decimal](18, 6) NULL,
    [Unit Price] [decimal](18, 6) NULL,
    [Dollars] [decimal](18, 6) NULL,

    [Batch GUID ##] [uniqueidentifier] NOT NULL,
    [Record GUID ##] [uniqueidentifier] NOT NULL DEFAULT (NEWSEQUENTIALID()),
    [Time Created ##] [datetime] NOT NULL,
    [User ID ##] [varchar](64) NULL DEFAULT (ORIGINAL_LOGIN()),

    CONSTRAINT [PlanByProduct_PK] PRIMARY KEY CLUSTERED 
        ([CustID], [InvtID], [FiscalYear], [FiscalMonth], [Version Number ##])
)

Чтобы отслеживать изменения, я использую столбец IDENTITY как часть первичного ключа, чтобы включить несколько версий с одним и тем же первичным ключом. Чтобы отследить, кто внес изменение, а также чтобы разрешить откат всего плохого обновления, если кто-то делает что-то совершенно глупое, я вставляю в Active Directory вход в систему создателя этой версии записи, отметку времени и два идентификатора GUID.

Столбец «Пакетный GUID» должен быть одинаковым для всех записей в пакете; столбец «GUID записи», очевидно, уникален для этой конкретной записи и используется только для дедупликации, а не для какого-либо запроса.

Я бы предпочел сгенерировать пакетный GUID внутри запроса, а не писать хранимую процедуру, которая делает очевидное:

DECLARE @BatchGUID UNIQUEIDENTIFIER = NEWID()

INSERT INTO MyTable 
SELECT I.*, @BatchGUID
FROM InputTable I

Я решил, что простой способ сделать это - создать однострочный результат с отметкой времени, идентификатором пользователя и вызовом NEWID () для создания пакетного GUID. Затем выполните CROSS JOIN, чтобы добавить эту строку к каждой из вставляемых строк. Я попытался сделать это несколькими разными способами, и оказалось, что механизм выполнения запросов по существу выполняет GETDATE () один раз, потому что одна отметка времени появляется во всех строках (даже для теста с 5 миллионами строк). Тем не менее, я получаю разные GUID для каждой строки в наборе результатов.

Приведенные ниже примеры просто фокусируются на запросе и опускают логику вставки вокруг них.

WITH MySingleRow AS 
(
    Select              NewID() as [Batch GUID ##], 
                        ORIGINAL_LOGIN() as [User ID ##],
                        getdate() as [Time Created ##]
)
SELECT                  N.*, R1.*
FROM                    util.zzIntegers N
    CROSS JOIN          MySingleRow R1
WHERE                   N.Sequence < 10000000

В приведенном выше запросе "util.zzIntegers" - это просто таблица целых чисел от 0 до 10 миллионов. Для выполнения запроса на моем сервере с холодным кешем требуется около 10 секунд, поэтому, если бы SQL Server выполнял функцию GETDATE () для каждой строки основной таблицы, он, безусловно, имел бы другое значение, по крайней мере, в столбце миллисекунд, но все 10 миллионов строк имеют одинаковую метку времени. Но я получаю разные GUID для каждой строки. Как я уже говорил, цель состоит в том, чтобы иметь одинаковый GUID в каждой строке.

Я также решил попробовать версию с явным конструктором табличных значений в надежде, что смогу обмануть оптимизатора, чтобы он поступил правильно. Я также сравнил его с реальной таблицей, а не с относительно «синтетическим» тестом, таким как список целых чисел из одного столбца. Следующее дало тот же результат.

WITH AnotherSingleRow AS
(
    SELECT              SingleRow.* 
    FROM                (
                            VALUES (NewID(), Original_Login(), getdate())
                        ) 
                        AS SingleRow(GUID, UserID, TimeStamp)
)

SELECT                  R1.*, S.*
FROM                    SalesOrderLineItems S
    CROSS JOIN          AnotherSingleRow R1

SalesOrderLineItems - это таблица с 6 миллионами строк и 135 столбцами, чтобы вдвойне убедиться, что время выполнения было достаточно продолжительным, чтобы GETDATE () увеличивался, если бы SQL Server полностью оптимизировал конструктор табличных значений и просто вызывал функцию каждый раз запрос выполняется.

Я прятался здесь некоторое время, и это мой первый вопрос, поэтому я определенно хотел провести хорошее исследование и избежать критики за то, что просто выбросил вопрос. Следующие вопросы на этом сайте касаются GUID, но не имеют прямого отношения. Я также потратил полчаса на поиск в Google с различными комбинациями фраз, которые, кажется, ничего не нашли.

  • Azure фактически делает то, что я хочу, о чем свидетельствует следующий вопрос I оказался в моем исследовании: Guid.NewGuid () всегда возвращает один и тот же Guid для всех строк . Тем не менее, я не на Azure и не собираюсь идти туда в ближайшее время.

  • Кто-то пытался сделать то же самое в SSIS ( Как вставить тот же guid в импорт SSIS ) но ответ на этот запрос вернулся, что вы генерируете GUID в SSIS в качестве переменной и вставьте ее в каждую строку. Я мог бы, конечно, сделать эквивалент в хранимой процедуре, но ради элегантности иСопровождаемость (мои коллеги имеют меньше опыта с запросами к SQL Server, чем я), я бы предпочел сохранить создание GUID пакета в запросе и максимально упростить любые хранимые процедуры.

Кстати, мой уровень опыта составляет 1-2 года с SQL Server в качестве аналитика данных / разработчика SQL в рамках более 10 лет, потраченных на написание кода, но в течение последних 20 лет яЯ был в основном специалистом по цифрам, а не специалистом по ИТ.В начале своей карьеры я работал на одного из ведущих поставщиков баз данных в качестве одного из разработчиков оптимизатора запросов, поэтому у меня есть довольно хорошее представление о том, что делает оптимизатор запросов, но у меня не было времени по-настоящему разобраться в том, как это делает SQL Server.,Так что я мог бы упустить что-то очевидное для других.

Заранее благодарю за помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...