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

“创建”的存储过程很容易。

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

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

但我认为这个建议很复杂。

是否有一些“最佳做法”?做这种类型的存储过程?

有帮助吗?

解决方案

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

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

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

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

其他提示

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

只授予视图选择权。

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

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

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

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

您的选择存储过程可以按如下方式完成,只需要一个存储过程,但在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))

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top