Доступ к данным с помощью хранимых процедур - PullRequest
4 голосов
/ 29 октября 2008

Одной из «лучших практик» является доступ к данным с помощью хранимых процедур. Я понимаю, почему этот сценарий хорош. Моя мотивация - разделение базы данных и логики приложения (таблицы могут быть изменены, если поведение хранимых процедур одинаково), защита для внедрения SQL-кода (пользователи не могут выполнить «select * from some_tables», они могут только вызывать хранимые процедуры) и безопасность (в хранимой процедуре может быть «все», что обеспечивает безопасность, что пользователь не может выбрать / вставить / обновить / удалить данные, что не для них).

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

Я использую MSSQL 2005.

Если у меня есть стол:

CREATE TABLE tblProduct (
   ProductID uniqueidentifier -- PK
   , IDProductType uniqueidentifier -- FK to another table
   , ProductName nvarchar(255) -- name of product
   , ProductCode nvarchar(50) -- code of product for quick search
   , Weight decimal(18,4)
   , Volume decimal(18,4)
)

тогда я должен создать 4 хранимые процедуры (создать / прочитать / обновить / удалить).

Хранимая процедура «создания» очень проста.

CREATE PROC Insert_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   INSERT INTO tblProduct ( ProductID, IDProductType, ... etc .. ) VALUES ( @ProductID, @IDProductType, ... etc ... )
END

Хранимая процедура удаления также очень проста.

CREATE PROC Delete_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
    DELETE tblProduct WHERE ProductID = @ProductID AND IDProductType = @IDProductType AND ... etc ...
END

Хранимая процедура для «update» аналогична «delete», но я не уверен, что это правильный путь, как это сделать. Я думаю, что обновление всех столбцов неэффективно.

CREATE PROC Update_Product( @ProductID uniqueidentifier, @Original_ProductID uniqueidentifier, @IDProductType uniqueidentifier, @Original_IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   UPDATE tblProduct SET ProductID = @ProductID, IDProductType = @IDProductType, ... etc ...
      WHERE ProductID = @Original_ProductID AND IDProductType = @Original_IDProductType AND ... etc ...
END

И последняя запомненная процедура для «чтения» для меня - маленькая загадка. Как передать значения фильтра для сложных условий? У меня есть несколько предложений:

Использование параметра XML для передачи, где условие:

CREATE PROC Read_Product ( @WhereCondition XML ) AS BEGIN
    DECLARE @SELECT nvarchar(4000)
    SET @SELECT = 'SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'

    DECLARE @WHERE nvarchar(4000)
    SET @WHERE = dbo.CreateSqlWherecondition( @WhereCondition ) --dbo.CreateSqlWherecondition is some function which returns text with WHERE condition from passed XML

    DECLARE @LEN_SELECT int
    SET @LEN_SELECT = LEN( @SELECT )
    DECLARE @LEN_WHERE int
    SET @LEN_WHERE = LEN( @WHERE )
    DECLARE @LEN_TOTAL int
    SET @LEN_TOTAL = @LEN_SELECT + @LEN_WHERE
    IF @LEN_TOTAL > 4000 BEGIN
        -- RAISE SOME CONCRETE ERROR, BECAUSE DYNAMIC SQL ACCEPTS MAX 4000 chars
    END

    DECLARE @SQL nvarchar(4000)
    SET @SQL = @SELECT + @WHERE

    EXEC sp_execsql @SQL
END

Но я думаю, что ограничение "4000" символов для одного запроса - это ужасно.

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

CREATE TABLE tblFilter (
   PKID uniqueidentifier -- PK
   , IDFilter uniqueidentifier -- identification of filter
   , FilterType tinyint -- 0 = ignore, 1 = equals, 2 = not equals, 3 = greater than, etc ...
   , BitValue bit , TinyIntValue tinyint , SmallIntValue smallint, IntValue int
   , BigIntValue bigint, DecimalValue decimal(19,4), NVarCharValue nvarchar(4000)
   , GuidValue uniqueidentifier, etc ... )

CREATE TABLE Read_Product ( @Filter_ProductID uniqueidentifier, @Filter_IDProductType uniqueidentifier, @Filter_ProductName uniqueidentifier, ... etc ... ) AS BEGIN
   SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume
   FROM tblProduct
   WHERE ( @Filter_ProductID IS NULL
            OR ( ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 1 ) AND NOT ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 2 ) )
      AND ( @Filter_IDProductType IS NULL
            OR ( ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 1 ) AND NOT ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 2 ) )
      AND ( @Filter_ProductName IS NULL OR ( ... etc ... ) ) 
END

Но это предложение немного сложнее, я думаю.

Есть ли какая-нибудь "лучшая практика" для выполнения хранимых процедур такого типа?

Ответы [ 6 ]

6 голосов
/ 29 октября 2008

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

Просто предоставьте только выбор на просмотр.

Вы можете ограничить отображаемые записи, изменить имена полей, объединить множество таблиц в одну логическую "таблицу" и т. Д.

5 голосов
/ 29 октября 2008

Во-первых: для вашей процедуры удаления в предложении where должен быть указан только первичный ключ.

Второе: для вашей процедуры обновления не пытайтесь оптимизировать, пока у вас нет рабочего кода. На самом деле, не пытайтесь оптимизировать, пока не сможете профилировать свое приложение и увидеть узкие места. Я могу с уверенностью сказать, что обновление одного столбца одной строки и обновление всех столбцов одной строки почти одинаковы по скорости. В СУБД требуется время: (1) найти дисковый блок, на который вы будете записывать данные, и (2) заблокировать других пишущих, чтобы ваша запись была последовательной. Наконец, написание кода, необходимого для обновления только тех столбцов, которые необходимо изменить, как правило, будет сложнее сделать и труднее поддерживать. Если вы действительно хотите быть разборчивым, вам нужно сравнить скорость выяснения того, какие столбцы изменились, по сравнению с простым обновлением каждого столбца. Если вы обновите их все, вам не нужно будет читать ни одного из них.

В-третьих: я стараюсь написать одну хранимую процедуру для каждого пути поиска. В вашем примере я сделал бы один по первичному ключу, один за каждым внешним ключом, а затем добавил бы один для каждого нового пути доступа, когда они мне понадобились в приложении. Быть проворным; не пишите код, который вам не нужен. Я также согласен с использованием представлений вместо хранимых процедур, однако вы можете использовать хранимую процедуру для возврата нескольких наборов результатов (в некоторых версиях MSSQL) или для преобразования строк в столбцы, что может быть полезно.

Если вам нужно получить, например, 7 строк по первичному ключу, у вас есть несколько вариантов. Вы можете вызвать хранимую процедуру, которая получает одну строку по первичному ключу семь раз. Это может быть достаточно быстро, если вы сохраняете соединение открытым между всеми вызовами. Если вы знаете, что вам никогда не нужно больше, чем определенное количество (скажем, 10) идентификаторов за раз, вы можете написать хранимую процедуру, которая включает в себя предложение where, например «и ID in (arg1, arg2, arg3 ...)» и сделать убедитесь, что неиспользованные аргументы установлены в NULL. Если вы решите, что вам нужно генерировать динамический SQL, я не стал бы беспокоиться о хранимой процедуре, потому что TSQL также легко ошибиться, как и любой другой язык. Кроме того, вы не получаете никакой выгоды от использования базы данных для манипулирования строками - это почти всегда является вашим узким местом, поэтому нет смысла давать БД больше работы, чем необходимо.

3 голосов
/ 29 октября 2008

Я не согласен с тем, что создание хранимых процедур «Вставка / Обновление / Выбор» является «лучшей практикой». Если все ваше приложение не написано в SP, используйте слой базы данных в вашем приложении для обработки этих действий CRUD. Еще лучше, используйте технологию ORM, чтобы справиться с ними за вас.

2 голосов
/ 29 октября 2008

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

Create sp_ProductSelect
(
 @ProductID int = null,
 @IDProductType int = null,
 @ProductName varchar(50) = null,
 @ProductCode varchar(10) = null,
 ...
 @Volume int = null
)
AS
SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'  
Where
  ((@ProductID is null) or (ProductID = @ProductID)) AND
  ((@ProductName is null) or (ProductName = @ProductName)) AND
  ...
  ((@Volume is null) or (Volume= @Volume))
2 голосов
/ 29 октября 2008

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

Если вы пытаетесь написать программное обеспечение, которое решает все возможные проблемы, а не конкретный набор проблем, вы, как правило, не сможете предоставить ничего полезного.

0 голосов
/ 29 октября 2008

В SQL 2005 он поддерживает nvarchar (max), который имеет ограничение 2G, но фактически принимает все строковые операции на обычном nvarchar. Вы можете проверить, может ли это соответствовать тому, что вам нужно при первом подходе.

...