Альтернативы варианту использования курсора в SQL Server

У меня есть две таблицы:

  • tb1 (ID, AccountId, updated_at,..., 30+ столбцов)
  • tb2 (ID, AccountId, updated_at,..., 30+ столбцов)

Я пытаюсь обновить значения нескольких столбцов в tb1, если в tb2 есть такая же строка, иначе мне нужно вставить все новые значения из tb2, если строк нет в таблице tb1.

Я использовал курсор, но он работает очень медленно.

Мой код:

DECLARE @accountId INT, @id INT, 
        @title NVARCHAR(1000), @num_questions INT,
        @type NVARCHAR(1000), @starts_at NVARCHAR(1000), 
        @finishes_at NVARCHAR(1000), @published_at NVARCHAR(1000),
        @summary NVARCHAR(2000), @updated_at NVARCHAR(1000)

-- Data from tb2
DECLARE quizRecSet CURSOR FOR
    SELECT 
        AccountId, ID, title, num_questions, type, 
        starts_at, finishes_at, published_at, summary, updated_at
    FROM 
        tb2 
    WHERE
        accountId = 1 
    ORDER BY
        updated_at DESC

OPEN quizRecSet
FETCH NEXT FROM quizRecSet INTO @accountId, @id, @title, @num_questions, @type, 
                                @starts_at, @finishes_at, @published_at, @summary, @updated_at

WHILE (@@FETCH_STATUS = 0)  
BEGIN  
    IF ((SELECT COUNT(*) FROM tb1(nolock) 
         WHERE ID = @ID AND accountId = @accountId) = 0)
    BEGIN
        INSERT INTO tb1
        VALUES (@accountId, @id, @title, @num_questions, @type,
                @starts_at, @finishes_at, @published_at, @summary, @updated_at)
    END
    ELSE
    BEGIN
        UPDATE tb1
        SET title = @title, num_questions = @num_questions,
            type = @type, published_at = @published_at,
            summary = @summary, updated_at = @updated_at
        WHERE ID = @ID AND AccountId = @accountId
    END

    FETCH NEXT FROM quizRecSet INTO @accountId, @id, @title, @num_questions, @type,
                                    @starts_at, @finishes_at, @published_at, @summary, @updated_at
END

CLOSE quizRecSet
DEALLOCATE quizRecSet

Вышеприведенный код работает очень медленно, так как в tb1 и tb2 много строк (около 2M).

Как я могу изменить это, чтобы добиться более высокой производительности?

Используйте оператор mergedocs.microsoft.com/en-us/sql/t-sql/statements/… — если ваша версия SQL Server поддерживает его (то есть 2008+).

Roger Wolf 24.03.2019 10:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
1
36
2

Ответы 2

Возможно, это могло бы работать быстрее:

insert into tb1 (accountId, ID, title, num_questions, type, 
    starts_at, finishes_at, published_at, summary, updated_at)
select AccountId, ID, title, num_questions, type,
    starts_at, finishes_at, published_at, summary, updated_at
  from tb2
 where accountId = 1 -- from the previous cursor sample
   and not exists
    (select 1 from tb1 where ID=tb2.ID and accountId = tb2.accountId)

update tb1 set 
    title = tb2.title, num_questions = tb2.num_questions,
    type=tb2.type, published_at = tb2.published_at, 
    summary = tb2.summary, updated_at = tb2.updated_at
from tb1
join tb2 on (tb1.ID = tb2.ID and tb1.accountId = tb2.accountId)
where tb1.accountId = 1 -- from the previous cursor sample
and not (tb1.title=tb2.title and tb1.num_questions=tb2.num_questions
    and tb1.type=tb2.type and tb1.published_at=tb2.published_at 
    and tb1.summary=tb2.summary and tb1.updated_at=tb2.updated_at)

Операции с множествами выполняются быстрее, чем операции с курсором. Следующий sql может помочь вам улучшить производительность. В этом случае мы сначала обновляем таблицу tb1, а затем вставляем строки, которых еще нет, из таблицы tb2 в таблицу tb1.

UPDATE tb1 SET 
  title = t2.title, 
  num_questions = t2.num_questions,
  type = t2.type,
  published_at = t2.published_at, 
  summary = t2.summary,
  updated_at = t2.updated_at
FROM tb1 AS t1
JOIN tb2 AS t2 ON (t1.ID = t2.ID AND t1.accountId = t2.accountId)
WHERE t1.accountId = 1

INSERT INTO tb1 (
  t2.AccountId, t2.ID, t2.title, t2.num_questions, t2.type, 
  t2.starts_at, t2.finishes_at, t2.published_at, t2.summary, t2.updated_at)
SELECT 
  t2.AccountId, t2.ID, t2.title, t2.num_questions, t2.type, 
  t2.starts_at, t2.finishes_at, t2.published_at, t2.summary, t2.updated_at
FROM (
  SELECT
    AccountId, ID, title, num_questions, type, 
    starts_at, finishes_at, published_at, summary, updated_at
  FROM tb2
  EXCEPT
  SELECT 
    t2.AccountId, t2.ID, t2.title, t2.num_questions, t2.type, 
    t2.starts_at, t2.finishes_at, t2.published_at, t2.summary, t2.updated_at
  FROM tb2 AS t2
  JOIN tb1 AS t1 ON (t1.ID = t2.ID AND t1.accountId = t2.accountId)
  WHERE t2.accountId = 1
) AS TempTable

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