Вопрос об импорте данных: следует ли использовать курсор?

В настоящее время я работаю над процедурой импорта SQL для импорта данных из устаревшего приложения в более современную надежную систему. Подпрограмма просто импортирует данные из устаревшей таблицы в виде плоского файла (хранящейся в виде файла .csv) в SQL Server по классическому шаблону порядок/порядок-подробности. Вот как выглядят обе таблицы:

**LEGACY_TABLE**  
Cust_No  
Item_1_No  
Item_1_Qty  
Item_1_Prc  
Item
**INVOICE**  
Invoice_No  
Cust_No

**INVOICE_LINE_ITEM**  
Invoice_No  
Item_No  
Item_Qty  
Item_Prc  
No Item
**INVOICE**  
Invoice_No  
Cust_No

**INVOICE_LINE_ITEM**  
Invoice_No  
Item_No  
Item_Qty  
Item_Prc  
Qty Item
**INVOICE**  
Invoice_No  
Cust_No

**INVOICE_LINE_ITEM**  
Invoice_No  
Item_No  
Item_Qty  
Item_Prc  
Prc ... Item_7_No Item_7_Qty Item_7_Prc

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

Новые таблицы выглядят так:

**INVOICE**  
Invoice_No  
Cust_No

**INVOICE_LINE_ITEM**  
Invoice_No  
Item_No  
Item_Qty  
Item_Prc  

Мой быстрый и грязный подход состоял в том, чтобы создать реплику LEGACY_TABLE (давайте назовем ее LEGACY_TABLE_SQL) в SQL Server. Эта таблица будет заполнена из файла .csv с помощью импорта базы данных, который уже встроен в приложение.
Оттуда я создал хранимую процедуру для фактического копирования каждого значения из таблицы LEGACY_TABLE_SQL в таблицы INVOICE/INVOICE_LINE_ITEM. а также обрабатывать базовые логические ограничения (т. е. выполнять проверки существования, проверять наличие уже открытых счетов и т. д.). Наконец, я создал триггер базы данных, который вызывает хранимую процедуру при вставке новых данных в таблицу LEGACY_TABLE_SQL.

Хранимая процедура выглядит примерно так:

CREATE PROC IMPORT_PROCEDURE 
@CUST_NO 
@ITEM_NO  
@ITEM_QTY  
@ITEM_PRC  

Однако вместо того, чтобы вызывать процедуру один раз, я фактически вызываю хранимую процедуру семь раз (по одному разу для каждого элемента) с помощью триггера базы данных. Я выполняю хранимую процедуру только тогда, когда ITEM_NO НЕ NULL, чтобы учесть пустые элементы в файле .csv. Поэтому мой триггер выглядит так:

CREATE TRIGGER IMPORT_TRIGGER  
if ITEM_NO_1 IS NOT NULL  
begin  
exec IMPORT_PROCEDURE (CUST_NO,ITEM_NO_1, ITEM_QTY_1, ITEM_PRC_1)  
end  

...так далее и так далее.

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


person TelJanini    schedule 09.09.2011    source источник
comment
Курсор неэффективен. Для каждого запроса будет обратный путь.   -  person zs2020    schedule 10.09.2011
comment
Так вы говорите, что подход, который я использую, правильный?   -  person TelJanini    schedule 10.09.2011
comment
Выглядит идеально - определенно не используйте для этого курсор   -  person Derek Kromm    schedule 10.09.2011


Ответы (2)


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

Как часто вы импортируете эти файлы?

У меня был бы процесс импорта, который в основном является автономным. Он может использовать хранимые процедуры или таблицы в базе данных, но я бы не стал использовать триггеры. Простой подход будет примерно таким, как показано ниже. Я добавил столбец в Legacy_Invoices (также переименованный во что-то более описательное), чтобы вы могли отслеживать, когда элементы были импортированы и откуда. Вы можете расширить это, чтобы отслеживать больше информации, если это необходимо.

Кроме того, я не понимаю, как вы отслеживаете номера счетов в своем коде. Я предположил, что столбец IDENTITY в таблице Legacy_Invoices. Этого почти наверняка недостаточно, поскольку я предполагаю, что вы также создаете счета в своей собственной системе (вне устаревшей системы). Однако, не зная вашей схемы нумерации счетов, невозможно дать решение.

BEGIN TRAN

DECLARE
    @now DATETIME = GETDATE()

UPDATE Legacy_Invoices
SET
    import_datetime = @now
WHERE
    import_status = 'Awaiting Import'

INSERT INTO dbo.Invoices (invoice_no, cust_no)
SELECT DISTINCT invoice_no, cust_no
FROM
    Legacy_Invoices
WHERE
    import_datetime = @now

UPDATE Legacy_Invoices
SET
    import_status = 'Invoice Imported'
WHERE
    import_datetime = @now

INSERT INTO dbo.Invoice_Lines (invoice_no, item_no, item_qty, item_prc)
SELECT
    invoice_no,
    item_no_1,
    item_qty_1,
    item_prc_1
FROM
    Legacy_Invoices LI
WHERE
    import_datetime = @now AND
    import_status = 'Invoice Imported' AND
    item_no_1 IS NOT NULL

UPDATE Legacy_Invoices
SET
    import_status = 'Item 1 Imported'
WHERE
    import_datetime = @now AND
    import_status = 'Invoice Imported'

<Repeat for item_no_2 through 7>

COMMIT TRAN

Но есть большое предостережение. Хотя курсоры обычно нежелательны в SQL, и вы хотите использовать обработку на основе наборов вместо обработки RBAR (строка за строкой), импорт данных часто является исключением.

Проблема с вышеизложенным заключается в том, что если одна строка терпит неудачу, весь этот шаг импорта завершается неудачей. Кроме того, очень сложно запустить один объект (счет-фактуру и отдельные позиции) с помощью бизнес-логики, когда вы импортируете их массово. Это то место, где SSIS действительно сияет. Это очень быстро (при условии, что вы правильно его настроили), даже при импорте одного объекта за раз. Затем вы можете поместить в него все виды обработки ошибок, чтобы убедиться, что импорт проходит гладко. В одной строке импорта указан неверный номер счета-фактуры? Нет проблем, отметьте это как ошибку и двигайтесь дальше. В строке есть элемент № 2, но нет элемента № 1 или цена указана без количества? Нет проблем, отметьте ошибку и двигайтесь дальше.

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

Если у вас есть какие-либо проблемы с запуском SSIS (учебники есть по всему Интернету и в MSDN в Microsoft), опубликуйте здесь любые проблемы, и вы получите быстрые ответы.

person Tom H    schedule 12.09.2011

Я не уверен, почему вы добавили триггер. Будете ли вы продолжать использовать LEGACY_TABLE_SQL? Если нет, то как насчет этой одноразовой процедуры? Он использует синтаксис Oracle, но может быть адаптирован к большинству баз данных.

ПРОЦЕДУРА МИГРАЦИИ

CURSOR all_data is
SELECT invoice_no, cust_no,Item_1_no,Item_1_qty........
FROM LEGACY_TABLE_SQL;

НАЧИНАТЬ

ДЛЯ данных в all_data LOOP INSERT INTO INVOICE (invoice_no, cust_no) VALUES (data.invoice_no, data.cust_no); ЕСЛИ Item_1_no НЕ НУЛЬ, ТОГДА ВСТАВИТЬ В INVOICE_LINE_ITEM(invoice_no,Item_1_no,Item_1_qty....) VALUES(data.invoice_no,data.Item_1_no,data.Item_1_qty....) END IF; --дальнейшие вставки для каждого элемента

КОНЕЦ ЦИКЛ;
СОВЕРШИТЬ;
КОНЕЦ;

Это можно дополнительно оптимизировать в Oracle с помощью BULK_COLLECT. Я бы создал таблицу INVOICE_LINE_ITEM со значениями по умолчанию 0 для всех элементов.

Я бы также рассмотрел следующие возможности: действительно ли номер счета-фактуры уникален сейчас и в будущем? может быть хорошей идеей добавить псевдоключ на основе последовательности
имеет ли какое-либо значение пустые записи item_no? Может ли это указывать на отложенный заказ, недопоставку или просто неправильный ввод данных?

РЕДАКТИРОВАТЬ: поскольку вы советуете, что будете продолжать использовать устаревшую таблицу, вам нужно расставить приоритеты в том, что вы хотите. Является ли эффективность и производительность вашим приоритетом номер один, ремонтопригодность, синхронная транзакция. Например:
- если производительность не очень важна, тогда реализуйте это, как вы описали - если это необходимо будет поддерживать, вы можете больше инвестировать в кодирование - если вам не требуется синхронная транзакция, вы можете добавить в свой LEGACY_TABLE_SQL столбец под названием «обработано» со значением по умолчанию 0. Затем один раз в день или час запланируйте задание, чтобы получить все заказы, которые не были обработаны.

person kevinskio    schedule 10.09.2011
comment
Да, к сожалению, я буду продолжать использовать LEGACY_TABLE_SQL для импорта новых записей в таблицу. - person TelJanini; 11.09.2011