4

“最佳实践”之一是通过存储过程访问数据。我明白为什么这种情况很好。我的动机是拆分数据库和应用程序逻辑(表可以更改,如果存储过程的行为相同),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

“更新”的存储过程与“删除”类似,但我不确定这是正确的方法,如何去做。我认为更新所有列效率不高。

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 参数传递 where 条件:

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”个字符的限制是丑陋的。

下一个建议是为每一列使用过滤表。将过滤器值插入过滤器表,然后调用具有过滤器 ID 的存储过程:

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

但我认为这个建议有点复杂。

是否有一些“最佳实践”来执行这种类型的存储过程?

4

6 回答 6

6

对于读取数据,您不需要存储过程来确保安全或分离出逻辑,您可以使用视图。

只授予视图上的选择。

您可以限制显示的记录、更改字段名称、将许多表连接成一个逻辑“表”等。

于 2008-10-29T15:39:33.797 回答
5

首先:对于您的删除例程,您的 where 子句应该只包含主键。

第二:对于你的更新程序,在你有工作代码之前不要尝试优化。事实上,在您可以分析您的应用程序并查看瓶颈所在之前,不要尝试进行优化。我可以肯定地告诉你,更新一行的一列和更新一行的所有列的速度几乎相同。在 DBMS 中需要时间的是 (1) 找到您将写入数据的磁盘块和 (2) 锁定其他写入器,以便您的写入保持一致。最后,编写只更新需要更改的列所需的代码通常更难做,也更难维护。如果您真的想变得挑剔,则必须将找出哪些列更改的速度与仅更新每一列的速度进行比较。如果您全部更新它们,则不必阅读其中任何一个。

第三:我倾向于为每个检索路径编写一个存储过程。在您的示例中,我将按主键创建一个,按每个外键创建一个,然后根据应用程序中的需要为每个新访问路径添加一个。敏捷;不要编写你不需要的代码。我也同意使用视图而不是存储过程,但是,您可以使用存储过程来返回多个结果集(在某些版本的 MSSQL 中)或将行更改为列,这很有用。

例如,如果您需要按主键获取 7 行,您有一些选择。您可以调用七次按主键获取一行的存储过程。如果您在所有呼叫之间保持连接打开,这可能会足够快。如果您知道一次不需要超过一定数量(例如 10 个)的 ID,则可以编写一个包含 where 子句的存储过程,例如“and ID in (arg1, arg2, arg3...)” 并制作确保未使用的参数设置为 NULL。如果您决定需要生成动态 SQL,我不会为存储过程而烦恼,因为 TSQL 与任何其他语言一样容易出错。此外,使用数据库进行字符串操作不会给您带来任何好处——它几乎总是您的瓶颈,因此没有必要为数据库提供任何不必要的工作。

于 2008-10-29T15:40:53.293 回答
3

我不同意创建插入/更新/选择存储过程是“最佳实践”。除非您的整个应用程序是用 SP 编写的,否则请在应用程序中使用数据库层来处理这些 CRUD 活动。更好的是,使用 ORM 技术为您处理它们。

于 2008-10-29T17:47:54.427 回答
2

我的建议是您不要尝试创建一个存储过程来完成您现在或曾经需要做的所有事情。如果您需要根据表的主键检索一行,请编写一个存储过程来执行此操作。如果您需要搜索满足一组条件的所有行,则找出该条件可能是什么并编写一个存储过程来执行此操作。

如果您尝试编写软件来解决所有可能的问题,而不是一组特定的问题,那么您通常无法提供任何有用的东西。

于 2008-10-29T16:04:52.310 回答
2

您的选择存储过程可以按如下方式完成,只需要一个存储过程,但 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))
于 2008-10-29T16:18:06.277 回答
0

在 SQL 2005 中,它支持 nvarchar(max),它有 2G 的限制,但实际上接受了普通 nvarchar 上的所有字符串操作。您可能想测试这是否适合您在第一种方法中的需要。

于 2008-10-29T15:54:35.607 回答