2008-10-29 137 views
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 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 

但這個建議被littlebit複雜,我想。

有沒有一些「最佳做法」來做這種類型的存儲過程?

回答

5

第一:你刪除程序,您的where子句應該只包括主鍵。第二:對於你的更新例程,不要在你有工作代碼之前嘗試優化。事實上,不要嘗試優化,直到您可以分析應用程序並查看瓶頸的位置。我可以肯定地告訴你,更新一行的一列和更新一行的所有列的速度幾乎相同。在DBMS中需要花費時間的是(1)找到將要寫入數據的磁盤塊,(2)鎖定其他寫入器,以便寫入將保持一致。最後,編寫只需更新需要更改的列的代碼通常很難做到,而且難以維護。如果你真的想挑剔,那麼你必須比較找出哪些列發生了變化,而不是隻更新每一列。如果你全部更新它們,你不必閱讀它們中的任何一個。

第三:我傾向於爲每個檢索路徑編寫一個存儲過程。在你的例子中,我通過主鍵創建一個,每個外鍵創建一個,然後在應用程序中爲每個新的訪問路徑添加一個。敏捷;不要編寫你不需要的代碼。我同意使用視圖而不是存儲過程,但是,您可以使用存儲過程返回多個結果集(在某些版本的MSSQL中)或將行更改爲列,這可能很有用。

如果您需要通過主鍵獲取7行,您可以選擇一些選項。您可以調用通過主鍵獲取一行的存儲過程七次。如果在所有呼叫之間保持連接打開,這可能會足夠快。如果你知道你一次不需要超過一定數量(比如10)的ID,你可以編寫一個存儲過程,包含一個where子句,如「(arg1,arg2,arg3 ...)中的ID和ID」確保未使用的參數設置爲NULL。如果您決定需要生成動態SQL,那麼我不會爲存儲過程而煩惱,因爲TSQL與其他語言一樣容易犯錯。此外,使用數據庫進行字符串操作並不會帶來任何好處 - 它幾乎總是您的瓶頸,因此給數據庫提供超出必要的工作是沒有意義的。

6

對於讀取數據,您不需要用於安全性的存儲過程或分離邏輯,您可以使用視圖。

只需授予視圖上的選擇。

您可以限制顯示的記錄,改變字段名稱,加入許多表成一個邏輯「表」等

+0

我應該使用視圖還是函數? 我認爲這些功能更加靈活。是爲什麼不使用函數而不是視圖或者不重要? – TcKs 2008-10-29 15:43:11

+0

通常DBMS優化器會比函數更好地理解視圖。 – 2008-10-29 15:45:50

+0

然後,它可能不會 - 在一個大型的生產數據庫中,當我們將一些較大的複雜查詢從視圖切換到函數時,我們的性能得到了顯着改善...... – 2008-10-29 16:11:41

0

在SQL 2005中,它支持nvarchar(max),它具有2G的限制,但實際上接受正常nvarchar上的所有字符串操作。您可能想要測試這是否適合您在第一種方法中需要的內容。

2

我的建議是,您不要嘗試創建一個存儲過程來完成您現在或需要執行的任何操作。如果您需要根據表的主鍵檢索行,請編寫一個存儲過程來執行此操作。如果您需要搜索滿足一系列條件的所有行,請查明可能的條件並編寫存儲過程來完成此操作。

如果您嘗試編寫解決所有可能問題而不是特定問題的軟件,則通常無法提供任何有用的功能。

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)) 
3

我不同意創建插入/更新/選擇存儲過程是「最佳實踐」。除非您的整個應用程序使用SP編寫,否則請在您的應用程序中使用數據庫層來處理這些CRUD活動。更好的是,使用ORM技術爲您處理它們。