web-dev-qa-db-de.com

SQL Server: So erhalten Sie einen Datenbanknamen als Parameter in einer gespeicherten Prozedur

Ich versuche, eine einfache gespeicherte Prozedur zu erstellen, die eine Tabelle sys.tables abfragt. 

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

Dies scheint nicht zu funktionieren, weil ich GO nach USE @dbname setzen sollte, aber dies beendet die Erstellung dieser Prozedur? Wie kann ich diese Datenbankauswahl in dieses Verfahren einschließen, damit ein Benutzer einen Datenbanknamen als Parameter für diese Prozedur angeben kann?

15
jjoras

Es gibt mindestens zwei Möglichkeiten, dies zu tun:

  1. Verwenden Sie eine case/switch -Anweisung (oder in meinem Beispiel einen naiven if..else-Block), um den Parameter mit einer Liste von Datenbanken zu vergleichen, und führen Sie auf dieser Grundlage eine using-Anweisung aus. Dies hat den Vorteil, dass die Datenbanken, auf die die proc zugreifen kann, auf eine bekannte Gruppe beschränkt werden, anstatt den Zugriff auf alles und alles zu erlauben, auf das das Benutzerkonto Rechte hat.

    declare @dbname nvarchar(255);    
    set @dbname = 'db1';    
    if @dbname = 'db1'
     use db1;
    else if @dbname = 'db2'
     use db2;
    
  2. Dynamisches SQL. Ich hasse dynamisches SQL. Es ist eine riesige Sicherheitslücke und fast nie nötig. (Um es in die richtige Perspektive zu bringen: In 17 Jahren beruflicher Entwicklung musste ich nie ein Produktionssystem einsetzen, das dynamisches SQL verwendete). Wenn Sie sich für diese Route entscheiden, beschränken Sie den dynamisch aufgerufenen/erstellten Code auf eine using-Anweisung, und ein Aufruf an eine andere gespeicherte Prozedur erledigt die eigentliche Arbeit. Sie können die Anweisung usingaufgrund von Bereichsregeln nicht einfach selbst dynamisch ausführen.

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

natürlich können Sie dies in Ihrem Beispiel tun

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

mit dem .<schema_name>.-Auflösungsoperator können Sie Objekte in einer anderen Datenbank abfragen, ohne eine usename__-Anweisung zu verwenden.

Es gibt einige sehr, sehr seltene Umstände, in denen es wünschenswert sein kann, einem Sproc die Verwendung einer beliebigen Datenbank zu erlauben. Meines Erachtens ist die einzige akzeptable Verwendung ein Codegenerator oder ein Datenbankanalysetool, das die erforderlichen Informationen nicht vorab kennen kann.

Update Es stellt sich heraus, dass Sie in einer gespeicherten Prozedur nicht usekönnen, wobei dynamisches SQL die einzige offensichtliche Methode bleibt. Trotzdem würde ich es in Betracht ziehen 

select top 100 * from db_name.dbo.table_name

anstatt eines usename__.

16
3Dave

Wenn Sie EXEC @Var (ohne Klammern - d. H. nicht EXEC (@Var)) verwenden, sucht SQL Server nach einer gespeicherten Prozedur, die dem in @Var übergebenen Namen entspricht. Sie können hierfür eine dreiteilige Benennung verwenden.

Wenn sys.sp_executesql mit einem dreiteiligen Namen aufgerufen wird, wird der Kontext auf die Datenbank gesetzt, in der er aufgerufen wird.

So können Sie dies mit zero SQL-Injektionsrisiko wie unten beschrieben tun.

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 

Selbst wenn dies nicht möglich wäre, würde ich behaupten, dass es durchaus möglich ist, dynamisches SQL auf sichere Weise wie hier zu verwenden.

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
26
Martin Smith

Die einzige Möglichkeit, dies zu tun, ist die Verwendung von Dynamic SQL , das zwar mächtig, aber gefährlich ist.

Lesen Sie zuerst diesen Artikel.

1
JNK

Ein anderer Weg zum selben Zweck ist die Verwendung einer gespeicherten Systemprozedur.

Siehe Gespeicherte SQL-Prozedur (en) - Ausführung aus mehreren Datenbanken .

Wenn der Prozedurname mit "sp_" beginnt, sich in der Master-Datenbank befindet und mit sys.sp_MS_MarkSystemObject gekennzeichnet ist, kann er folgendermaßen aufgerufen werden:

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

Oder so:

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

Parameter können auch verwendet werden.

Die Verwendung dieser Technik erfordert die Verwendung des Präfix 'sp_' und das Einfügen von Code in eine Systemdatenbank. Sie haben die Wahl, wenn diese Offsets nicht mit dynamischem SQL arbeiten.

0
Roy Latham