簡體   English   中英

我應該如何將表名傳遞到存儲過程中?

[英]How should I pass a table name into a stored proc?

我剛剛遇到了一件奇怪的事情......我們網站上有一些代碼采用了巨大的 SQL 語句,通過根據某些用戶值進行一些搜索和替換來在代碼中修改它,然后將它傳遞給 SQL 服務器作為一個問題。

我當時認為這將作為對存儲過程的參數化查詢更清晰,將用戶值作為參數,但是當我更仔細地觀察時,我明白了他們為什么會這樣做......他們從中選擇的表是取決於這些用戶值。

例如,在一種情況下,如果值是 ("FOO", "BAR"),則查詢最終會變成類似於 "SELECT * FROM FOO_BAR"

有沒有簡單明了的方法來做到這一點? 我正在嘗試的一切似乎都不優雅。

編輯:當然,我可以在存儲過程中動態生成 sql,然后執行它 (bleh),但那時我想知道我是否獲得了任何東西。

EDIT2:以某種智能方式重構表名,比如說將它們全部放在一個具有不同名稱的表中作為一個新列將是解決所有這些問題的好方法,幾個人已經直接指出或暗示了這一點。 遺憾的是,在這種情況下這不是一個選擇。

首先,您永遠不應該像這樣在客戶端應用程序上執行 SQL 命令組合,就是 SQL 注入。 (對於沒有自己的權限的管理工具來說是可以的,但對於共享使用的應用程序則不行)。

其次,是的,對存儲過程的參數化調用既干凈又安全。

但是,由於您需要使用動態 SQL 來執行此操作,因此您仍然不希望在執行的查詢的文本中包含傳遞的字符串。 相反,您希望使用傳遞的字符串來查找應允許用戶以這種方式查詢的實際表的名稱。

這是一個簡單的天真示例:

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS
-- Counts the number of rows from any non-system Table, *SAFELY*
BEGIN
    DECLARE @ActualTableName AS NVarchar(255)

    SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
    FROM INFORMATION_SCHEMA.TABLES
    WHERE TABLE_NAME = @PassedTableName

    DECLARE @sql AS NVARCHAR(MAX)
    SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

    EXEC(@SQL)
END

有些人已經相當地問為什么這更安全。 希望小鮑比表可以更清楚地說明這一點:0替代文字


更多問題的答案:

  1. 不能保證單獨的 QUOTENAME 是安全的。 MS 鼓勵我們使用它,但他們並沒有保證它不會被黑客打敗。 僅供參考,真正的安全就是保證。 使用 QUOTENAME 的表查找是另一回事,它牢不可破。

  2. QUOTENAME 在這個例子中不是絕對必要的,僅在 INFORMATION_SCHEMA 上的 Lookup 翻譯通常就足夠了。 QUOTENAME 在這里是因為包含完整和正確的解決方案是安全的好形式。 這里的 QUOTENAME 實際上是在防止一個不同但類似的潛在問題,稱為潛在注入


我應該注意到,您可以對動態列名和INFORMATION_SCHEMA.COLUMNS表做同樣的事情。

您還可以通過使用參數化 SQL 查詢來繞過對存儲過程的需求(請參閱此處: https : //docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=網絡框架-4.8 )。 但我認為存儲過程為此類情況提供了更易於管理且不易出錯的安全設施。

(不)幸運的是,沒有辦法做到這一點 - 除了動態 sql 生成之外,您不能使用作為參數傳遞給存儲代碼的表名。 在決定在哪里生成 sql 代碼時,我更喜歡應用程序代碼而不是存儲的代碼。 應用程序代碼通常更快且更易於維護。

如果您不喜歡正在使用的解決方案,我建議進行更深入的重新設計(即更改架構/應用程序邏輯,以便您不再需要將表名作為參數傳遞到任何地方)。

我反對在存儲過程中動態生成 SQL; 這會給你帶來麻煩,並可能導致注入漏洞。

相反,我會分析可能受查詢影響的所有表,並創建某種枚舉來確定用於查詢的表。

聽起來您最好使用 ORM 解決方案。

當我在存儲過程中看到動態 sql 時,我感到害怕。

您可以考慮的一件事是創建一個 case 語句,其中包含您想要的相同 SQL 命令,對每個有效表一次,然后將表名作為字符串傳遞到此過程中,並讓 case 選擇要運行的命令。

順便說一句,作為安全人員,上面的建議告訴您從系統表中進行選擇以確保您擁有有效的表,這對我來說似乎是一種浪費的操作。 如果有人可以通過 QUOTENAME() 注入,那么注入將在系統表和基礎表上工作。 這有助於確保它是一個有效的表名,我認為上面的建議是更好的方法,因為您根本沒有使用 QUOTENAME() 。

根據這些表中的列集是相同還是不同,從長遠來看,我會以兩種方式處理它:

1) 如果它們相同,為什么不創建一個新的列來用作選擇器,其值來自用戶提供的參數? (這是性能優化嗎?)

2) 如果它們不同,則處理它們的方式也可能不同。 因此,似乎將選擇/處理代碼拆分為單獨的塊,然后分別調用它們對我來說是一種最模塊化的方法。 您將重復“select * from”部分,但在這種情況下,表集希望是有限的。

允許調用代碼提供表名的任意兩個部分來進行 select from 感覺非常危險。

我不知道您將數據分布在多個表中的原因,但聽起來您正在打破其中一個基本原理。 數據應該在表中,而不是作為表名。

如果表格的布局或多或少相同,請考慮是否最好將數據放在單個表格中。 這將解決您的動態查詢問題,並使數據庫布局更加靈活。

您可以選擇過程,而不是根據用戶輸入值查詢表。 也就是說
1. 創建一個過程 FOO_BAR_prc 並在其中放置查詢 'select * from foo_bar' ,這樣查詢將被數據庫預編譯。
2. 然后根據用戶輸入從您的應用程序代碼中執行正確的程序。

由於您有大約 50 個表,因此這可能不是一個可行的解決方案,因為它需要您做大量的工作。

其實我想知道如何在存儲過程中通過表名來創建表。 通過閱讀一些答案並在最后嘗試進行一些修改,我終於能夠創建一個名稱作為參數傳遞的表。 這是其他人檢查其中任何錯誤的存儲過程。

USE [數據庫名稱] GO /****** 對象:StoredProcedure [dbo].[sp_CreateDynamicTable] 腳本日期:06/20/2015 16:56:25 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[sp_CreateDynamicTable] @tName varchar(255) AS BEGIN SET NOCOUNT ON; 聲明 @SQL nvarchar(max)

SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);'

    EXECUTE sp_executesql @SQL

結尾

@RBarry Young 您不需要將括號添加到查詢字符串中的 @ActualTableName 中,因為它已經包含在 INFORMATION_SCHEMA.TABLES 中的查詢結果中。 否則,執行時會出現錯誤。

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS -- 計算任何非系統表中的行數,安全開始聲明 @ActualTableName AS NVarchar(255)

SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = @PassedTableName

DECLARE @sql AS NVARCHAR(MAX)
--SELECT @sql = 'SELECT COUNT(*) FROM [' + @ActualTableName + '];'

-- changed to this
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

EXEC(@SQL)

結尾

我會不惜一切代價避免動態 SQL。

這不是最優雅的解決方案,但可以完美地完成工作。

PROCEDURE TABLE_AS_PARAMTER (
        p_table_name IN VARCHAR2
    ) AS
    BEGIN
        CASE p_table_name
            WHEN 'TABLE1' THEN
                UPDATE TABLE1
                SET
                    COLUMN1 =1
                WHERE
                    ID =1;
            WHEN 'TABLE2' THEN
                UPDATE TABLE1
                SET
                    COLUMN1 =1
                WHERE
                    ID =2;
        END CASE;

        COMMIT;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK
END TABLE_AS_PARAMTER;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM