Sp_executesql в цикле, выполнение которого занимает ВЕЧНОСТЬ. Замедление во время бега. Утечка памяти?

Короче говоря, мой запрос выполняется НАВСЕГДА, и я думаю, что это связано с запросами sp_executesql. Да, есть циклы, и да, я использую Dynamic SQL.

Я использую циклы достаточно, чтобы знать, что что-то происходит, это занимает гораздо больше времени, чем следовало бы. Единственное, о чем я могу думать, это то, что sp_executesql неправильно сформирован или что-то в этом роде.

В таблице [tmp_compare] будет около 8500 записей, и она сравнивает 15 полей, поэтому я знаю, что это 127 000 «процессов», но ее запуск не должен занимать ЧАСЫ, верно?!

Я отслеживал его работу в течение 90 минут, и он начал работать со скоростью 84 записи (записи в data_log) в секунду. Через час скорость снизилась до 14 записей в секунду. Постепенно оно замедляется. Последний раз я видел нечто подобное, когда в написанной мной программе произошла утечка памяти. Я исправил предыдущую проблему с типами данных, и это немного ускорило работу, но что-то все равно кажется неправильным. Мне нужно очистить переменные или что-то в этом роде? Что мне здесь не хватает?

Цель: Я сравниваю конкретные столбцы из двух таблиц и помещаю данные и предлагаемые действия на основе этих данных в другую таблицу, чтобы действовать позже. Таблица [contact_compare_fields] содержит имена полей для двух таблиц, а таблица [tmp_compare] представляет собой просто список идентификаторов.

Создание/подготовка таблицы:

CREATE TABLE [dbo].[contact_compare_fields] 
(
    [id]       int identity(1,1),
    [sf_field] varchar(100),
    [h_field]  varchar(100)
);

INSERT INTO [contact_compare_fields] 
VALUES
    ('Title',               'Job Title'),
    ('FirstName',           'First Name'),
    ('LastName',            'Last Name'),
    ('MailingStreet',       'Person Street'),
    ('MailingCity',         'Person City'),
    ('MailingState',        'Person State'),
    ('MailingPostalCode',   'Person Zip Code'),
    ('MailingCountry',      'Country'),
    ('Department',          'Department'),
    ('Email',               'Email Address'),
    ('Phone',               'Direct Phone Number'),
    ('Fax',                 'Fax'),
    ('MobilePhone',         'Mobile Phone'),
    ('Job_Level__c',        'Management Level'),
    ('Job_Role__c',         'Job Function')
    
CREATE TABLE [dbo].[tmp_compare] 
(
    [id]      int identity(1,1),
    [H_ZI_ID] bigint
)

INSERT INTO [tmp_compare] 
VALUES
    ('1001122877'),('1001125385'),('1002260105'),('100233801'),('1002661679')

CREATE TABLE [dbo].[h_processed] 
(
    [Contact ID] varchar(255),
    [Job Title]  varchar(255)
)

INSERT INTO [h_processed] 
VALUES ('1001122877', 'Chief Financial Officer')

CREATE TABLE [dbo].[sf_contact] 
(
    [Contact ID] varchar(255),
    [Title]  varchar(255)
)

INSERT INTO [sf_contact] 
VALUES ('1001122877', 'CFO')

CREATE TABLE [data_log] (
    [id]        int identity(1,1),
    [dataID]    varchar(500),
    [field]     varchar(128),
    [sf_data]   varchar(500),
    [h_data]    varchar(500),
    [score]     float,
    [action]    varchar(128)
);

КОД:

DECLARE
    @fld_cnt int = 1,
    @fld_max int = 0,
    @cnt int = 1,
    @max int = 0,
    @score float,
    @zi bigint,
    @value int = 0,
    @sf_field nvarchar(500) = '',
    @h_field nvarchar(500) = '',
    @sf_data nvarchar(500) = '',
    @h_data nvarchar(500) = '',
    @ParamDef nvarchar(500) = '',
    @sql nvarchar(max)
    
DROP TABLE IF EXISTS [tmp_compare];
SELECT
    identity(int,1,1) AS [id],
    h.[Contact ID] AS [H_ZI_ID]
 INTO [tmp_compare]
 FROM [h_processed] h
  LEFT JOIN [sf_contact] s ON
    h.[Contact ID] =  s.[DOZISF__ZnID__c]
  WHERE s.[DOZISF__ZI_ID__c] IS NOT NULL 
    AND s.[DOZISF__ZI_ID__c] <> ''

SELECT @fld_max = COUNT(*) FROM [contact_compare_fields]
SELECT @max = COUNT(*) FROM [tmp_compare]


-- Cycle Through Every ID in [tmp_compare] table
WHILE (@cnt <= @max)
BEGIN
    SELECT @zi = [H_ZI_ID] FROM [tmp_compare] WHERE [id] = @cnt

    -- Cycle Through Every Field in the Table
    WHILE (@fld_cnt <= @fld_max)
    BEGIN
        SELECT @sf_field = [sf_field], @h_field = [h_field] FROM [contact_compare_fields] WHERE [id] = @fld_cnt
        
        SET @sql = N'SELECT @h_dataOUT = SDU_Tools.NULLifBlank(' + QUOTENAME(@h_field) + ') FROM [h_processed] WHERE [Contact ID] = @ziIN;';
        SET @ParamDef = N'@ziIN bigint, @h_dataOUT varchar(500) OUTPUT';
        EXEC sp_executesql @sql, @ParamDef, @ziIN = @zi, @h_dataOUT = @h_data OUTPUT;

        SET @sql = N'SELECT @sf_dataOUT  = SDU_Tools.NULLifBlank(' + QUOTENAME(@sf_field) + ') FROM [sf_contact] WHERE [DOZISF__ZI_ID__C] = @ziIN;';
        SET @ParamDef = N'@ziIN bigint, @sf_dataOUT varchar(500) OUTPUT';
        EXEC sp_executesql @sql, @ParamDef, @ziIN = @zi, @sf_dataOUT = @sf_data OUTPUT;

        INSERT INTO [data_log] VALUES
            (@zi, @sf_field, @sf_data, @h_data, NULL, '')

        IF (@sf_data = @h_data)
            UPDATE [data_log] SET [action] = 'None' WHERE [DataID] = @zi AND [field] = @sf_field;
        ELSE IF (@sf_data IS NULL) AND (@h_data IS NULL)
            UPDATE [data_log] SET [action] = 'None' WHERE [DataID] = @zi AND [field] = @sf_field;
        ELSE IF (@sf_data IS NOT NULL) AND (@h_data IS NULL)
            UPDATE [data_log] SET [action] = 'None' WHERE [DataID] = @zi AND [field] = @sf_field;
        ELSE IF (@sf_data IS NULL) AND (@h_data IS NOT NULL)
            UPDATE [data_log] SET [action] = 'UPDATE' WHERE [DataID] = @zi AND [field] = @sf_field;
        ELSE
            UPDATE [data_log] SET [action] = 'Needs Review' WHERE [DataID] = @zi AND [field] = @sf_field;

        SET @fld_cnt = @fld_cnt + 1
    END

    SET @fld_cnt = 1
    SET @cnt = @cnt + 1
END
In a loopвот твоя проблема. Это самый медленный способ сделать что-либо в SQL. SQL — это язык, основанный на множествах. 127 тыс. строк — это небольшой объем данных. Объясните, что вы хотите сделать, чтобы люди могли помочь вам сделать это правильно.
Panagiotis Kanavos 17.06.2024 09:33
Yes, there are loops, and yes I am using Dynamic SQL. No, I do not really understand CTEs and can never get them to work, вам не нужны CTE, чтобы использовать результаты одного запроса в другом. Вы не сможете ничего делать в SQL, пока не поймете, как использовать подзапросы и JOIN. Возврат к циклам никогда не работает. UPDATE target SET ... FROM source INNER JOIN target ON target.SomeColumn=source.SomeColumn WHERE ... может обновить целевую таблицу данными из исходной таблицы
Panagiotis Kanavos 17.06.2024 09:38

Вам действительно придется начинать с самого начала и объяснять реальную проблему (бизнес-кейс, таблицы со значимыми именами, логику, примеры данных), потому что текущий код очень трудно читать. Перепроектирование реальных таблиц и столбцов из этого займет слишком много времени. Единственная причина, по которой я могу это понять, заключается в том, что я уже работаю над проверкой данных, но не использую динамические поля и не пытаюсь запускать правила построчно.

Panagiotis Kanavos 17.06.2024 09:41

@PanagiotisKanavos Я добавил «цель». Я уже включил таблицы и некоторые примеры данных для всего, поэтому никакого обратного проектирования не должно быть. Я не думаю, что то, что я пытаюсь сделать, сложно, и я ДУМАЛ, что было бы лучше/проще сделать это с помощью динамического sql, чем писать запросы для каждого поля индивидуально.

Dizzy49 17.06.2024 09:46

Это не. Что касается того, что вы пытаетесь сделать, это называется Ряд за мучительным рядом

Panagiotis Kanavos 17.06.2024 09:49

Я не могу обновлять таблицы таким образом. Мне нужно иметь их отдельно для рассмотрения. Возможно, data_log можно создать лучше без оценки или действия, а затем запускать запросы для их обновления.

Dizzy49 17.06.2024 09:50

Сейчас я работаю с несколькими таблицами строк по 30 миллионов и сложными правилами. Да, вы можете обновить таблицы. Однако сначала вам нужно объяснить, что вы хотите сделать. Это purpose предложение непонятно всем остальным. Здесь нет таблиц, столбцов, бизнес-правил или логики. The [contact_compare_fields] table holds the field names for the two tables это критическая ошибка. Вместо того, чтобы делать вашу систему «динамической», что очень затрудняет ее обслуживание и оптимизацию.

Panagiotis Kanavos 17.06.2024 09:52

Прежде всего, 120 тысяч строк — это достаточно мало, вы можете просто загрузить данные, например, в фрейм данных Pandas, и применить там правила — без зацикливания. Если вы знаете Python, это может быть жизнеспособным решением. Существуют библиотеки, такие как Pydantic или Pandera, которые позволяют вам определять проверки и правила для целых фреймов данных и находить нарушения.

Panagiotis Kanavos 17.06.2024 09:54

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

Dale K 17.06.2024 09:56

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

Panagiotis Kanavos 17.06.2024 09:58

@DaleK это вложенный цикл построчно с одной итерацией для каждого поля, а не только для строки. Даже если бы поля были проиндексированы, это все равно было бы самым медленным решением. Этот код выполняет 15*127 тыс. запросов.

Panagiotis Kanavos 17.06.2024 09:59

Пытаюсь добавить индексы в ваши временные таблицы. Цикл, конечно, плох, но если у вас не так много строк, то все не так уж и плохо. Если только у вас нет индексов на h_processed и т. д. И, во имя бога, SDU_Tools.NULLifBlank, не используйте скалярные функции, если вам нужна достойная производительность, особенно для тривиальных задач.

siggemannen 17.06.2024 10:07

@PanagiotisKanavos Спасибо, вы заставили меня задуматься об этом по-другому, и я думаю, что у меня есть план.

Dizzy49 17.06.2024 10:31

В моем случае, чтобы сравнить финансовые транзакции из двух разных систем, я объединил таблицы транзакций по общему ключу, а затем вычислил по одному флагу для каждого правила. Они были сохранены в сравнительной таблице. Таким образом, было очень легко и быстро рассчитать конкретные случаи сбоев и составить отчеты. Вы можете сделать сравнение быстрым, если будете рассчитывать правила только для измененных строк. Вместо того, чтобы работать со всеми 127 тысячами строк каждый день, вы можете работать только с 1000 новыми строками каждый час. Использование индексов Columnstore очень хорошо работает в таких случаях.

Panagiotis Kanavos 17.06.2024 10:34

@PanagiotisKanavos Буду признателен за вашу информацию: stackoverflow.com/questions/78635017/…

Dizzy49 18.06.2024 01:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
15
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для этого вообще не нужны циклы. Вам просто нужно динамически создать объединенный запрос, который разворачивает столбцы и сравнивает их, вставляя в журнал при необходимости.

DECLARE @cols nvarchar(max), @sql nvarchar(max);

SELECT @cols = STRING_AGG(N'
      (' + QUOTENAME(ccf.sf_field, '''') + ', sf.' + QUOTENAME(ccf.sf_field) + ', hp.' + QUOTENAME(ccf.h_field) + ')',
    N','
  )
FROM contact_compare_fields ccf;

SET @sql = '
INSERT data_log (dataID, field, sf_data, h_data, action)
SELECT
  sf.[Contact ID],
  v.column_name,
  v.sf_data,
  v.h_data,
  CASE WHEN v.sf_data = v.h_data OR v.h_data IS NULL THEN ''None''
       WHEN v.sf_data IS NULL AND v.h_data IS NOT NULL THEN ''UPDATE''
       ELSE ''Needs Review''
  END
FROM tmp_compare tc
JOIN sf_contact sf ON sf.[Contact ID] = CAST(tc.[H_ZI_ID] AS varchar(255))
JOIN h_processed hp ON hp.[Contact ID] = sf.[Contact ID]
CROSS APPLY (VALUES' + @cols + '
) v(column_name, sf_data, h_data);
';

PRINT @sql;   -- your friend

EXEC sp_executesql @sql;

SELECT * FROM data_log

db<>рабочий пример

Примечание:

  • Обязательно заключайте имена столбцов в кавычки с помощью QUOTENAME.
  • Имена столбцов и объектов должны храниться в типизированных столбцах и переменных sysname.
  • Если типы столбцов различаются, вам следует привести их все к одному типу: строковому типу или sql_variant.
  • В вашем примере tc.[H_ZI_ID] и [Contact ID] имеют разные типы данных. Они должны быть одного типа.
  • Все таблицы должны иметь кластеризованные индексы по [Contact ID] и [H_ZI_ID] соответственно.

Это здорово, я собираюсь скопировать это и посмотреть, смогу ли я понять это достаточно, чтобы использовать его повторно. То, что я придумал, ОЧЕНЬ похоже. В нем все еще есть цикл, но он проходит только по 15 наборам полей, а затем я создаю динамический запрос для прямой загрузки data_log. Таким образом было вырезано МНОГО записей, которые мне не нужны :)

Dizzy49 18.06.2024 00:29

Буду признателен за вашу информацию: stackoverflow.com/questions/78635017/…

Dizzy49 18.06.2024 01:45

По-прежнему не похоже, что вам нужен цикл, но неважно.

Charlieface 18.06.2024 02:52

Другие вопросы по теме

Похожие вопросы

Как с помощью T-SQL получить последнюю среднюю чистую цену за последний проданный год?
Оптимизация производительности SQL Server: объединение таблиц бюджета и фактических данных после одинаковых преобразований CTE и объединений измерений
Как преобразовать «Типы» свойства класса List<string> foo в таблицу SQL Server [Types](FooID int, name nvarchar) с помощью Entity Framework Core
Я постоянно получаю сообщение «Неверный синтаксис рядом с ключевым словом «КОГДА».» Ошибка в моем SQL-запросе
Создайте столбец rowID/identity в подзапросе перекрестного соединения
Использована статистика из кэша планов XML для запроса с заданным QueryPlanHash
Два перекрестных соединения дают повторяющийся результат с двумя отдельными значениями
Условно-рекурсивный запрос в SQL Server
Каков максимальный поддерживаемый уровень совместимости для EF6?
Контроль исходного кода SQL Server (с DACPAC и возможностью видеть изменения с помощью git)