Короче говоря, мой запрос выполняется НАВСЕГДА, и я думаю, что это связано с запросами 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
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 ...
может обновить целевую таблицу данными из исходной таблицы
Вам действительно придется начинать с самого начала и объяснять реальную проблему (бизнес-кейс, таблицы со значимыми именами, логику, примеры данных), потому что текущий код очень трудно читать. Перепроектирование реальных таблиц и столбцов из этого займет слишком много времени. Единственная причина, по которой я могу это понять, заключается в том, что я уже работаю над проверкой данных, но не использую динамические поля и не пытаюсь запускать правила построчно.
@PanagiotisKanavos Я добавил «цель». Я уже включил таблицы и некоторые примеры данных для всего, поэтому никакого обратного проектирования не должно быть. Я не думаю, что то, что я пытаюсь сделать, сложно, и я ДУМАЛ, что было бы лучше/проще сделать это с помощью динамического sql, чем писать запросы для каждого поля индивидуально.
Это не. Что касается того, что вы пытаетесь сделать, это называется Ряд за мучительным рядом
Я не могу обновлять таблицы таким образом. Мне нужно иметь их отдельно для рассмотрения. Возможно, data_log можно создать лучше без оценки или действия, а затем запускать запросы для их обновления.
Сейчас я работаю с несколькими таблицами строк по 30 миллионов и сложными правилами. Да, вы можете обновить таблицы. Однако сначала вам нужно объяснить, что вы хотите сделать. Это purpose
предложение непонятно всем остальным. Здесь нет таблиц, столбцов, бизнес-правил или логики. The [contact_compare_fields] table holds the field names for the two tables
это критическая ошибка. Вместо того, чтобы делать вашу систему «динамической», что очень затрудняет ее обслуживание и оптимизацию.
Прежде всего, 120 тысяч строк — это достаточно мало, вы можете просто загрузить данные, например, в фрейм данных Pandas, и применить там правила — без зацикливания. Если вы знаете Python, это может быть жизнеспособным решением. Существуют библиотеки, такие как Pydantic или Pandera, которые позволяют вам определять проверки и правила для целых фреймов данных и находить нарушения.
Вам нужно провести некоторый анализ и определить, соответствуют ли конкретные таблицы, выполнение которых занимает больше времени, порядку, в котором они выполняются. И проверить планы выполнения запросов, чтобы определить, что и где происходит замедление. Или, может быть, вы израсходовали всю оперативную память сервера, и ее необходимо заменить.
Другой вариант — создать представление с вычисляемым полем для каждого правила и отфильтровать его по полям с ошибками. Вы можете использовать это представление для обновления строк с ошибками или вставки ошибок в другую таблицу.
@DaleK это вложенный цикл построчно с одной итерацией для каждого поля, а не только для строки. Даже если бы поля были проиндексированы, это все равно было бы самым медленным решением. Этот код выполняет 15*127 тыс. запросов.
Пытаюсь добавить индексы в ваши временные таблицы. Цикл, конечно, плох, но если у вас не так много строк, то все не так уж и плохо. Если только у вас нет индексов на h_processed и т. д. И, во имя бога, SDU_Tools.NULLifBlank
, не используйте скалярные функции, если вам нужна достойная производительность, особенно для тривиальных задач.
@PanagiotisKanavos Спасибо, вы заставили меня задуматься об этом по-другому, и я думаю, что у меня есть план.
В моем случае, чтобы сравнить финансовые транзакции из двух разных систем, я объединил таблицы транзакций по общему ключу, а затем вычислил по одному флагу для каждого правила. Они были сохранены в сравнительной таблице. Таким образом, было очень легко и быстро рассчитать конкретные случаи сбоев и составить отчеты. Вы можете сделать сравнение быстрым, если будете рассчитывать правила только для измененных строк. Вместо того, чтобы работать со всеми 127 тысячами строк каждый день, вы можете работать только с 1000 новыми строками каждый час. Использование индексов Columnstore очень хорошо работает в таких случаях.
@PanagiotisKanavos Буду признателен за вашу информацию: stackoverflow.com/questions/78635017/…
Для этого вообще не нужны циклы. Вам просто нужно динамически создать объединенный запрос, который разворачивает столбцы и сравнивает их, вставляя в журнал при необходимости.
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
Примечание:
QUOTENAME
.sysname
.sql_variant
.tc.[H_ZI_ID]
и [Contact ID]
имеют разные типы данных. Они должны быть одного типа.[Contact ID]
и [H_ZI_ID]
соответственно.Это здорово, я собираюсь скопировать это и посмотреть, смогу ли я понять это достаточно, чтобы использовать его повторно. То, что я придумал, ОЧЕНЬ похоже. В нем все еще есть цикл, но он проходит только по 15 наборам полей, а затем я создаю динамический запрос для прямой загрузки data_log. Таким образом было вырезано МНОГО записей, которые мне не нужны :)
Буду признателен за вашу информацию: stackoverflow.com/questions/78635017/…
По-прежнему не похоже, что вам нужен цикл, но неважно.
In a loop
вот твоя проблема. Это самый медленный способ сделать что-либо в SQL. SQL — это язык, основанный на множествах. 127 тыс. строк — это небольшой объем данных. Объясните, что вы хотите сделать, чтобы люди могли помочь вам сделать это правильно.