web-dev-qa-db-fra.com

SQL Server: comment obtenir un nom de base de données en tant que paramètre dans une procédure stockée

J'essaie de créer une procédure stockée simple qui interroge une table sys.tables. 

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

Cela ne semble pas fonctionner car je devrais mettre GO après USE @dbname mais cela met fin à la création de cette procédure? Comment est-ce que je peux mettre cette sélection de base de données dans cette procédure afin qu'un utilisateur puisse donner un nom de base de données en tant que paramètre pour ce proc?

15
jjoras

Il y a au moins deux façons de le faire:

  1. Utilisez une instruction case/switch (ou, dans mon exemple, un bloc naïf if..else) pour comparer le paramètre à une liste de bases de données et exécutez une instruction using basée sur celle-ci. Cela présente l'avantage de limiter les bases de données auxquelles le proc peut accéder à un ensemble connu, plutôt que de permettre l'accès à tout ce dont le compte d'utilisateur dispose de droits.

    declare @dbname nvarchar(255);    
    set @dbname = 'db1';    
    if @dbname = 'db1'
     use db1;
    else if @dbname = 'db2'
     use db2;
    
  2. SQL dynamique. Je déteste SQL dynamique. C'est un énorme trou de sécurité et presque jamais nécessaire. (pour mettre cela en perspective: en 17 ans de développement professionnel, je n’ai jamais eu à déployer de système de production utilisant du SQL dynamique). Si vous décidez de suivre cette voie, limitez le code appelé/créé dynamiquement à une instruction using et appelez un autre processus stocké pour effectuer le travail réel. Vous ne pouvez pas simplement exécuter dynamiquement l'instruction using par elle-même en raison de règles d'étendue.

    declare @sql nvarchar(255);
    set @sql = 'using '+@dbname+'; exec mydatabase..do_work_proc;';
    

bien sûr, dans votre exemple, vous pouvez simplement faire

    set @sql='select * from '+@dbname+'.sys.tables';

l'opérateur de résolution .<schema_name>. vous permet d'interroger des objets d'une autre base de données sans utiliser d'instruction use.

Dans de très rares circonstances, il peut être souhaitable d'autoriser un sproc à utiliser une base de données arbitraire. À mon avis, la seule utilisation acceptable est un générateur de code, ou une sorte d’outil d’analyse de base de données qui ne peut pas connaître les informations requises à l’avance.

Update Il s'avère que vous ne pouvez pas use dans une procédure stockée, le SQL dynamique restant la seule méthode évidente. Pourtant, je envisagerais d'utiliser 

select top 100 * from db_name.dbo.table_name

plutôt que use.

16
3Dave

Si vous utilisez EXEC @Var (sans crochets - c'est-à-dire not EXEC (@Var)), SQL Server recherche une procédure stockée correspondant au nom transmis dans @Var. Vous pouvez utiliser le nommage en trois parties pour cela.

Si sys.sp_executesql est appelé avec un nom en trois parties, le contexte est défini sur la base de données dans laquelle il est appelé.

Ainsi, vous pouvez le faire avec zéro risque d'injection SQL comme ci-dessous.

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 

Même si ce qui précède n’était pas possible, je dirais toujours qu’il est parfaitement possible d’utiliser le code SQL dynamique de manière sûre, comme ici.

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

La seule façon de faire est d'utiliser SQL dynamique , qui est puissant mais dangereux.

Lisez cet article en premier.

1
JNK

Une méthode différente pour atteindre le même objectif consiste à utiliser une procédure stockée système.

Voir Procédure (s) stockée (s) SQL - Exécution à partir de plusieurs bases de données .

Si le nom de la procédure commence par "sp_", se trouve dans la base de données principale et est marqué avec sys.sp_MS_MarkSystemObject, elle peut être appelée comme ceci:

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

Ou comme ceci:

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

Les paramètres peuvent être utilisés aussi.

L'utilisation de cette technique nécessite d'utiliser le préfixe 'sp_' et de placer du code dans une base de données système. C'est votre choix si les compensations n'utilisent pas le SQL dynamique.

0
Roy Latham