2009-07-14 1 views
5

Мы используем SQL Server 2005. Весь доступ к данным осуществляется через хранимые процедуры. Наши хранимые процедуры выбора всегда возвращают несколько наборов результатов.Как повторно использовать код в хранимых процедурах SQL?

Например:

CREATE PROCEDURE hd_invoice_select(@id INT) AS 
    SELECT * FROM Invoice WHERE InvoiceID = @id 
    SELECT * FROM InvoiceItem WHERE InvoiceID = @id 
    SELECT * FROM InvoiceComments WHERE InvoiceID = @id 
    RETURN 

уровень доступа к данным нашего приложения строит граф объектов на основе результатов (O/R Mapper стиля).

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

CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS 
    SELECT * FROM Invoice WHERE CustomerID = @customerID 
    SELECT * FROM InvoiceItem WHERE InvoiceID IN 
     (SELECT InvoiceID FROM Invoice WHERE CustomerID = @customerID) 
    SELECT * FROM InvoiceComments WHERE InvoiceID = @id 
     (SELECT InvoiceID FROM Invoice WHERE CustomerID = @customerID) 
    RETURN 

и у меня есть много других, включая:

hd_invoice_selectActive() 
hd_invoice_selectOverdue() 
hd_invoice_selectForMonth(@year INT, @month INT) 

и у меня есть один и тот же шаблон для многих понятий (клиентов, сотрудников и т.д.)

Мы в конечном итоге копируем много кода и обслуживание очень сложно. Когда изменяется «структура» концепции, мы должны идти и исправлять все процессы, и это очень подвержено ошибкам.

Итак, мой вопрос: что является лучшим способом повторного использования кода в сценарии?

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

Благодаря

ответ

1

Опубликовать это как второй ответ, потому что это другой подход. Если вы используете SQL Server 2008:

CREATE TYPE InvoiceListTableType AS TABLE 
(
    InvoiceId INT 
); 
GO 

CREATE PROCEDURE hd_invoice_selectFromTempTable 
(
    @InvoiceList InvoiceListTableType READONLY 
) 
AS 
BEGIN 
    SELECT * FROM Invoice WHERE InvoiceID IN 
     (SELECT InvoiceId FROM @InvoiceList) 

    SELECT * FROM InvoiceItem WHERE InvoiceID IN 
     (SELECT InvoiceId FROM @InvoiceList) 

    SELECT * FROM InvoiceComments WHERE InvoiceID IN 
     (SELECT InvoiceId FROM @InvoiceList) 

    RETURN 
END 
GO 

CREATE PROCEDURE hd_invoice_select(@id INT) AS 
BEGIN 
    DECLARE @InvoiceList AS InvoiceListTableType; 

    SELECT id AS ID 
     INTO @InvoiceList 

    EXEC hd_invoice_selectFromTempTable(@InvoiceList) 
    RETURN 
END 
GO 

CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS 
BEGIN 
    DECLARE @InvoiceList AS InvoiceListTableType; 

    SELECT invoiceID as ID 
     INTO @InvoiceList 
     FROM Invoice WHERE CustomerID = @customerID 

    EXEC hd_invoice_selectFromTempTable(@InvoiceList) 
    RETURN 
END 
GO 

CREATE PROCEDURE hd_invoice_selectAllActive AS 
BEGIN 
    DECLARE @InvoiceList AS InvoiceListTableType; 

    SELECT invoiceID as ID 
     INTO @InvoiceList 
     FROM Invoice WHERE Status = 10002 

    EXEC hd_invoice_selectFromTempTable(@InvoiceList) 
    RETURN 
END 
GO 
+0

Это очень здорово. Вы знаете, как это происходит? Оптимизатор запросов выполняет хорошую работу? Внутренне это использование временных таблиц, или это действительно передача набора как простого значения. – Sylvain

+0

У меня еще не было возможности использовать их, но немного почитал их. Я, по возможности, очень анти-temp-таблицы, поэтому кажется более элегантным решением, если оно работает хорошо. –

+0

Я буду реорганизовать этот подход, как только мы перейдем к SQL 2008. – Sylvain

0

Это одна из главных проблем, с хранимыми процедурами и почему люди не любят их.

Я никогда не встречал и не видел пути вокруг него.

0

Я начал использовать хранимые процедуры, генерируемые генераторами кода для моего основного CRUD. Я использую хранимые procs для отчетов или сложную работу SQL.

У меня есть предложение, не связанное с вашим вопросом, а также - вместо использования предложения IN используйте предложение EXISTS в ваших операторах SQL.

+1

Можете ли вы объяснить, почему, по вашему мнению, EXISTS лучше, чем IN в этом случае? –

+1

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

2

«Лучшим» способом для этого конкретного сценария было бы использование своего рода генерации кода. Придумайте какое-то соглашение и подключите его к генератору кода.

0

я иногда делаю это в два этапа:

подхожу со списком InvoiceID. Затем я вызываю свою хранимую процедуру с этим списком в качестве параметра.

В 2005 году мы не имеем таблицу значения параметров, поэтому я упаковываю мой список идентификаторов в блоб и представить его в SQL Server, как описано здесь: Arrays and Lists in SQL Server 2005

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

+0

Используя эту технику, как бы вы сделали так, чтобы proc hd_invoice_selectForMonth (@year INT, @month INT) мог найти совпадающие идентификаторы, а затем упаковать их и отправить их в hd_invoice_selectFromIDs (@ids blob)? – Sylvain

0

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

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

Возможно, это может сработать и в вашей ситуации.

1

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

CREATE PROCEDURE hd_invoice_select 
(
    @id INT = NULL 
    , @customerId INT = NULL 
) AS 
BEGIN 
    SELECT * 
     FROM Invoice 
     WHERE 
      (
       @id IS NULL 
       OR InvoiceID = @id 
      ) 
      AND (
       @customerId IS NULL 
       OR CustomerID = @customerId 
      ) 
    RETURN 
END 

Эта процедура может быть вызвана настежь, посылая @Id и @CustomerID в качестве значения NULL, для конкретного InvoiceID на основе @Id с @CustomerID как NULL (или просто оставить его все вместе), или для конкретный клиент, основанный на @customerId, оставляя @id как NULL или исключая его из запроса.

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

+2

Chris, Это в основном то же самое решение, которое мы используем, но с одним небольшим изменением. В предложении WHERE мы будем использовать: CustomerID = ISNULL (@customerId, CustomerID) Причина, по которой это дает нам побочный эффект возврата ВСЕХ элементов, если значение равно null. Таким образом, если мы будем называть EXEC hd_invoice_select() без параметров, мы можем получить полный список. Возможно, это не то, что вы хотите, но мы считаем это очень полезным. Приветствия! -Erick – Erick

+0

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

+0

Чтобы включить это в последний комментарий: я определенно ценю краткость вашего подхода. –

0

В некоторых случаях я использую VIEWS для повторного использования «кода». В случаях, когда фильтры, активные элементы, устаревшие вещи и т. Д. ...

0

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

+0

@HLGEM: Как вы вернете несколько результатов с помощью объединений? Мне нужен счет-фактура, все элементы счета-фактуры и все комментарии для счета-фактуры, возвращенного как 3 набора результатов. – Sylvain

1

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

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

Для каждого понятия в моем приложении, у меня есть один storec прок, как это:

CREATE PROCEDURE hd_invoice_selectFromTempTable AS 

    /* Get the IDs from an existing #TempInvoiceIDs temporary table */ 

    SELECT * FROM Invoice WHERE InvoiceID IN 
     (SELECT ID FROM #TempInvoiceIDs) 

    SELECT * FROM InvoiceItem WHERE InvoiceID IN 
     (SELECT ID FROM #TempInvoiceIDs) 

    SELECT * FROM InvoiceComments WHERE InvoiceID IN 
     (SELECT ID FROM #TempInvoiceIDs) 

    RETURN 

Затем я создаю столько выбора хранимой процедуры, как мне нужно:

CREATE PROCEDURE hd_invoice_select(@id INT) AS 

    /* Fill #TempInvoiceIDs with matching IDs */ 
    SELECT id AS ID INTO #TempInvoiceIDs 

    EXEC hd_invoice_selectFromTempTable 
    RETURN 

CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS 

    /* Fill #TempInvoiceIDs with matching IDs */ 
    SELECT invoiceID as ID 
    INTO #TempInvoiceIDs 
    FROM Invoice WHERE CustomerID = @customerID 

    EXEC hd_invoice_selectFromTempTable 
    RETURN 

CREATE PROCEDURE hd_invoice_selectAllActive AS 

    /* Fill #TempInvoiceIDs with matching IDs */ 
    SELECT invoiceID as ID 
    INTO #TempInvoiceIDs 
    FROM Invoice WHERE Status = 10002 

    EXEC hd_invoice_selectFromTempTable 
    RETURN 

Что вы думаете этот подход? Это несколько похоже на ответ Алексея Кузнецова, но я использую временные таблицы вместо параметра BLOB.

+1

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

+0

Очень полезный комментарий, спасибо. Является ли время перекомпилировать proc существенное и общее время выполнения? Если proc занимает 2 мс для повторной компиляции и 120 мс для выполнения, то время перекомпиляции не очень важно. Правильно? – Sylvain

+0

Мне нравится этот подход, потому что он прямо использует объекты, которые являются естественными для SQL Server. –