2010-10-15 30 views
14

Tôi đang cố gắng tạo một thủ tục được lưu trữ đơn giản truy vấn bảng sys.tables.Máy chủ SQL: cách lấy tên cơ sở dữ liệu làm tham số trong thủ tục được lưu trữ

CREATE PROCEDURE dbo.test 
    @dbname NVARCHAR(255), 
    @col NVARCHAR(255) 
AS 
    SET NOCOUNT ON 
    SET XACT_ABORT ON 

    USE @dbname 

    SELECT TOP 100 * 
    FROM sys.tables 
    WHERE name = @col 
GO 

Điều này dường như không hoạt động vì tôi nên đặt GO sau USE @dbname nhưng điều này chấm dứt việc tạo thủ tục này? Làm thế nào tôi có thể đặt cơ sở dữ liệu này selction vào thủ tục này để người dùng có thể cung cấp cho một tên cơ sở dữ liệu như là một tham số cho proc này?

Trả lời

15

Có ít nhất hai cách để làm điều này:

  1. Sử dụng một tuyên bố trường hợp/switch (hoặc, trong ví dụ của tôi, một ngây thơ if..else khối) để so sánh các thông số chống lại một danh sách các cơ sở dữ liệu và thực hiện một câu lệnh sử dụng dựa trên đó. Điều này có lợi thế là hạn chế các cơ sở dữ liệu mà proc có thể truy cập vào một tập đã biết, thay vì cho phép truy cập mọi thứ và mọi thứ mà tài khoản người dùng có quyền.

    declare @dbname nvarchar(255);  
    set @dbname = 'db1';  
    if @dbname = 'db1' 
    use db1; 
    else if @dbname = 'db2' 
    use db2; 
    
  2. SQL động. Tôi HATE SQL động. Đó là một lỗ hổng bảo mật khổng lồ và hầu như không cần thiết. (để đặt điều này theo quan điểm: Trong 17 năm phát triển chuyên nghiệp, tôi chưa bao giờ phải triển khai một hệ thống sản xuất sử dụng SQL động). Nếu bạn quyết định đi theo lộ trình này, hãy giới hạn mã được gọi/tạo động đến một câu lệnh sử dụng và một cuộc gọi đến một proc được lưu trữ khác sẽ thực hiện công việc thực tế. Bạn không thể chỉ tự động thực hiện tuyên bố using do chính nó do các quy tắc phạm vi.

    declare @sql nvarchar(255); 
    set @sql = 'using '[email protected]+'; exec mydatabase..do_work_proc;'; 
    

tất nhiên, trong ví dụ của bạn, bạn chỉ có thể làm

set @sql='select * from '[email protected]+'.sys.tables'; 

các nhà điều hành giải quyết .<schema_name>. cho phép bạn truy vấn các đối tượng trong cơ sở dữ liệu khác nhau mà không sử dụng một tuyên bố use.

Có một số trường hợp rất, rất hiếm trong đó có thể mong muốn cho phép một sproc sử dụng cơ sở dữ liệu tùy ý. Theo ý kiến ​​của tôi, việc sử dụng duy nhất chấp nhận được là một bộ tạo mã, hoặc một số loại công cụ phân tích cơ sở dữ liệu mà không thể biết được thông tin cần thiết trước thời hạn.

Cập nhật Biến ra bạn không thể use trong quy trình được lưu trữ, để SQL động làm phương pháp hiển nhiên duy nhất. Tuy nhiên, tôi sẽ xem xét việc sử dụng

select top 100 * from db_name.dbo.table_name 

chứ không phải là use.

+6

Ngoại trừ cách bạn sử dụng lệnh 'USE ' bên trong một proc được lưu trữ khi SQL Server đặc biệt không cho phép nó? – JNK

+0

Cảm ơn thông tin này! – jjoras

+0

Rất tiếc - SQL Server không cho phép 'sử dụng' trong một proc được lưu trữ. Đã cập nhật câu trả lời. –

25

Nếu bạn sử dụng EXEC @Var (không có dấu ngoặc - ví dụ: khôngEXEC (@Var)) SQL Server tìm kiếm một thủ tục lưu trữ phù hợp với tên thông qua vào @Var. Bạn có thể sử dụng ba phần đặt tên cho việc này.

Nếu sys.sp_executesql được gọi với tên có ba phần, bối cảnh được đặt thành cơ sở dữ liệu mà nó được gọi.

Vì vậy, bạn có thể làm điều này với số không Nguy cơ tiêm SQL như sau.

CREATE PROCEDURE dbo.test @dbname SYSNAME, 
          @col SYSNAME 
AS 
    SET NOCOUNT, XACT_ABORT ON; 

    DECLARE @db_sp_executesql NVARCHAR(300) = QUOTENAME(@dbname) + '.sys.sp_executesql' 

    EXEC @db_sp_executesql N' 
          SELECT TOP 100 * 
          FROM sys.columns 
          WHERE name = @col', 
          N'@col sysname', 
          @col = @col 

Ngay cả khi những điều trên không thể, tôi vẫn cho rằng hoàn toàn có thể sử dụng SQL động một cách an toàn như ở đây.

CREATE PROCEDURE dbo.test 
    @dbname SYSNAME, /*Use Correct Datatypes for identifiers*/ 
    @col SYSNAME 
AS 
    SET NOCOUNT ON 
    SET XACT_ABORT ON 

    IF DB_ID(@dbname) IS NULL /*Validate the database name exists*/ 
     BEGIN 
     RAISERROR('Invalid Database Name passed',16,1) 
     RETURN 
     END 

DECLARE @dynsql nvarchar(max) 

/*Use QUOTENAME to correctly escape any special characters*/ 
SET @dynsql = N'USE '+ QUOTENAME(@dbname) + N' 

         SELECT TOP 100 * 
         FROM sys.tables 
         WHERE name = @col' 

/*Use sp_executesql to leave the WHERE clause parameterised*/ 
EXEC sp_executesql @dynsql, N'@col sysname', @col = @col 
+3

Rất đẹp. Tôi đã không nghĩ đến việc sử dụng 'db_id()' để xác thực tham số '@ dbname'. –

+0

Làm thế nào một đối phó với việc xử lý các kết nối giữa các bảng trên nhiều db. – akashchandrakar

0

Cách khác để kết thúc tương tự là sử dụng quy trình được lưu trữ trên hệ thống.

Xem SQL Stored Procedure(s) - Execution From Multiple Databases.

Nếu tên thủ tục bắt đầu với "sp_", là trong db tổng thể và được đánh dấu bằng sys.sp_MS_MarkSystemObject, sau đó nó có thể được gọi như thế này:

Exec somedb.dbo.Test; 
Exec anotherdb.dbo.Test; 

Hoặc như thế này:

Declare @Proc_Name sysname; 
Set @Proc_Name = 'somedb.dbo.Test'; 
Exec @Proc_Name; 

Các tham số cũng có thể được sử dụng.

Sử dụng kỹ thuật này yêu cầu sử dụng tiền tố 'sp_' và đặt mã vào cơ sở dữ liệu hệ thống. Đó là sự lựa chọn của bạn nếu bù đắp không sử dụng SQL động.

Các vấn đề liên quan