Вопрос импорта данных: должен ли я использовать курсор? - PullRequest
1 голос
/ 10 сентября 2011

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

**LEGACY_TABLE**  
Cust_No  
Item_1_No  
Item_1_Qty  
Item_1_Prc  
Item_2_No  
Item_2_Qty  
Item_2_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 имеет значение NOT 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  

... и так далее, и тому подобное.

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

Ответы [ 2 ]

0 голосов
/ 12 сентября 2011

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

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

У меня будет процесс импорта, который будет в основном автономным. Он может использовать хранимые процедуры или таблицы в базе данных, но я бы не использовал триггеры. Простой подход будет что-то вроде ниже. Я добавил столбец в 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), опубликуйте здесь любые проблемы, и вы должны получить быстрые ответы.

0 голосов
/ 10 сентября 2011

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

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

КУРСОР all_data равен
ВЫБЕРИТЕ 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 не равен NULL, то INSERT INTO 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; - дополнительные вкладыши для каждого предмета

END LOOP;
COMMIT;
END;

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

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

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

...