2010-10-15 8 views
14

Estoy intentando crear un procedimiento almacenado simple que consulta una tabla sys.tables.SQL Server: cómo obtener un nombre de base de datos como un parámetro en un procedimiento almacenado

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 

no parece que esto funcione porque yo debería poner GO después @dbname USO pero esto termina la creación de este procedimiento? ¿Cómo puedo poner esta selección de base de datos en este procedimiento para que un usuario pueda dar un nombre de base de datos como parámetro para este proceso?

Respuesta

15

Hay al menos dos maneras de hacer esto:

  1. utilizar una declaración de caso/interruptor (o, en mi ejemplo, una ingenua if..else bloque) para comparar el parámetro contra una lista de bases de datos y ejecute una instrucción de uso basada en eso. Esto tiene la ventaja de limitar las bases de datos que el proc puede tener acceso a un conjunto conocido, en lugar de permitir el acceso cualquier cosa y todo lo que la cuenta de usuario tiene derechos para.

    declare @dbname nvarchar(255);  
    set @dbname = 'db1';  
    if @dbname = 'db1' 
    use db1; 
    else if @dbname = 'db2' 
    use db2; 
    
  2. SQL dinámico. ODIO SQL dinámico. Es un gran agujero de seguridad y casi nunca es necesario. (para poner esto en perspectiva: en 17 años de desarrollo profesional, nunca tuve que implementar un sistema de producción que utilizara SQL dinámico). Si decide seguir esta ruta, limite el código que se llama/crea dinámicamente a una instrucción de uso, y una llamada a otro proceso almacenado hace el trabajo real. No se puede simplemente ejecutar dinámicamente la instrucción using por sí mismo debido a reglas de ámbito.

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

por supuesto, en su ejemplo, usted podría hacer

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

el operador de resolución .<schema_name>. le permite consultar los objetos de una base de datos diferente sin necesidad de utilizar una declaración use.

Existen algunas circunstancias muy, muy raras en las que puede ser deseable permitir que un sproc use una base de datos arbitraria. En mi opinión, el único uso aceptable es un generador de código, o algún tipo de herramienta de análisis de base de datos que no puede conocer la información requerida con anticipación.

Actualización Resulta que no se puede use en un procedimiento almacenado, dejando SQL dinámico como el único método obvio. Aún así, consideraría usar

select top 100 * from db_name.dbo.table_name 

en lugar de use.

+6

Excepto cómo se usa el comando 'USE ' dentro de un proceso almacenado cuando SQL Server específicamente lo deshabilita? – JNK

+0

¡Gracias por esta información! – jjoras

+0

Whoops - sí, SQL Server no permite 'use' en un proceso almacenado. Respuesta actualizada –

25

Si utiliza EXEC @Var (sin paréntesis - es decir, no EXEC (@Var)) de SQL Server busca un procedimiento almacenado coincide con el nombre aprobado en @Var. Puede usar nombres de tres partes para esto.

Si se llama a sys.sp_executesql con un nombre de tres partes, el contexto se establece en la base de datos a la que se llama.

Así que puede hacer esto con cero Riesgo de inyección SQL como a continuación.

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 

Incluso si el anterior no era posible todavía diría que es perfectamente posible utilizar SQL dinámico para esto de una manera segura como en este caso.

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

Muy agradable. No había pensado en usar 'db_id()' para validar el parámetro '@ dbname'. –

+0

¿Cómo debería tratarse el manejo de combinaciones entre tablas a través de múltiples db? – akashchandrakar

0

Una manera diferente de usar el mismo procedimiento es utilizar un procedimiento almacenado en el sistema.

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

Si el nombre del procedimiento comienza con "sp_", está en la base de datos maestra y marcado con sys.sp_MS_MarkSystemObject, entonces se puede invocar como esto:

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

O así:

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

Los parámetros se pueden usar también.

El uso de esta técnica requiere el uso del prefijo 'sp_' y la colocación de código en una base de datos del sistema. Es su elección si las compensaciones no usan SQL dinámico.

Cuestiones relacionadas