SQL Server: как получить имя базы данных в качестве параметра хранимой процедуры - PullRequest
15 голосов
/ 15 октября 2010

Я пытаюсь создать простую хранимую процедуру, которая запрашивает таблицу 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

Это не похоже на работу, потому что я должен поставить GO после USE @dbname, но это прекращает создание этой процедуры?Как я могу включить эту выборку базы данных в эту процедуру, чтобы пользователь мог дать имя базы данных в качестве параметра для этого процесса?

Ответы [ 4 ]

26 голосов
/ 15 октября 2010

Если вы используете EXEC @Var (без скобок - т.е. не EXEC (@Var)), SQL Server ищет хранимую процедуру, соответствующую имени, переданному в @Var. Для этого вы можете использовать три части именования.

Если sys.sp_executesql вызывается с именем из трех частей, контекст устанавливается для базы данных, в которой он вызывается.

Таким образом, вы можете сделать это с ноль риск внедрения SQL, как показано ниже.

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 

Даже если вышеупомянутое не было возможно, я все еще утверждал бы, что вполне возможно использовать динамический SQL для этого безопасным способом, как здесь.

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
16 голосов
/ 15 октября 2010

Есть как минимум два способа сделать это:

  1. Используйте оператор case / switch (или, в моем примере, наивный if..else блок), чтобы сравнить параметр со списком баз данных и выполнить оператор using на его основе. Это имеет то преимущество, что ограничивает базы данных, к которым у proc есть доступ к известному набору, вместо того, чтобы разрешать доступ ко всему и тому, на что у учетной записи есть права.

    declare @dbname nvarchar(255);    
    set @dbname = 'db1';    
    if @dbname = 'db1'
     use db1;
    else if @dbname = 'db2'
     use db2;
    
  2. Динамический SQL. Я ненавижу динамический SQL. Это огромная дыра в безопасности и почти никогда не нужна. (в перспективе: за 17 лет профессионального развития мне никогда не приходилось развертывать производственную систему, использующую динамический SQL). Если вы решите пойти по этому пути, ограничьте код, который динамически вызывается / создается, оператором using, и вызов другой хранимой процедуры выполняет фактическую работу. Вы не можете просто динамически выполнить оператор using из-за правил области действия.

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

конечно, в вашем примере вы могли бы просто сделать

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

Оператор разрешения .<schema_name>. позволяет запрашивать объекты в другой базе данных без использования оператора use.

Существуют очень и очень редкие обстоятельства, при которых может быть желательно разрешить sproc использовать произвольную базу данных. На мой взгляд, единственное приемлемое использование - это генератор кода или какой-то инструмент анализа базы данных, который не может заранее узнать необходимую информацию.

Обновление Оказывается, вы не можете use в хранимой процедуре, оставляя динамический SQL в качестве единственного очевидного метода. Тем не менее, я бы рассмотрел использование

select top 100 * from db_name.dbo.table_name

, а не use.

1 голос
/ 15 октября 2010

Единственный способ сделать это - использовать Динамический SQL , который является мощным, но опасным.

Сначала прочтите эту статью.

0 голосов
/ 26 сентября 2016

Другим способом является использование системной хранимой процедуры.

См. Хранимая процедура (ы) SQL - выполнение из нескольких баз данных .

Если имя процедуры начинается с «sp_», находится в главной базе данных и помечено sys.sp_MS_MarkSystemObject, то ее можно вызвать так:

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

Или вот так:

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

Параметры также могут быть использованы.

Использование этого метода требует использования префикса sp_ и помещения кода в системную базу данных. Это ваш выбор, если это компенсирует не использование динамического SQL.

...