Оценивать в T-SQL - PullRequest
       64

Оценивать в T-SQL

8 голосов
/ 27 марта 2009

У меня есть хранимая процедура, которая позволяет параметру IN указывать, какую базу данных использовать. Затем я использую предварительно определенную таблицу в этой базе данных для запроса. Проблема, с которой я сталкиваюсь, заключается в объединении имени таблицы с этим именем базы данных в моих запросах. Если бы у T-SQL была функция оценки, я мог бы сделать что-то вроде

eval(@dbname + 'MyTable')

В настоящее время я застрял в создании строки, а затем использую exec() для запуска этой строки в качестве запроса. Это грязно, и я бы не стал создавать строку. Есть ли способ, которым я могу оценить переменную или строку, чтобы я мог сделать что-то вроде следующего?

SELECT *
FROM eval(@dbname + 'MyTable')

Я бы хотел, чтобы он оценил, чтобы он выглядел так:

SELECT *
FROM myserver.mydatabase.dbo.MyTable

Ответы [ 11 ]

16 голосов
/ 02 апреля 2009

Прочитайте это ... Проклятие и благословения динамического SQL , очень помогите мне понять, как решить этот тип проблем.

9 голосов
/ 27 марта 2009

Нет "аккуратного" способа сделать это. Вы сэкономите время, если примете это и посмотрите на что-то еще.

РЕДАКТИРОВАТЬ: Ага! Относительно комментария ОП: «Мы должны загружать данные в новую базу данных каждый месяц, иначе она станет слишком большой». Оглядываясь назад, никто не заметил слабый запах этой проблемы.

SQL Server предлагает встроенные механизмы для работы с таблицами, которые становятся «слишком большими» (в частности, разбиение), что позволит вам обращаться к таблице как к единому объекту, в то же время разделяя таблицу на отдельные файлы в фоновом режиме, таким образом полностью устраняя вашу текущую проблему.

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

5 голосов
/ 27 марта 2009

попробуйте встроенную функцию sp_executesql. Вы можете создать свою строку SQL в вашем proc, а затем вызвать

exec sp_executesql @SQLString.

DECLARE @SQLString nvarchar(max)
SELECT @SQLString = '
SELECT *
FROM  ' +  @TableName 

EXEC sp_executesql @SQLString
2 голосов
/ 01 апреля 2009

Нельзя указывать динамическое имя таблицы в SQL Server.

Есть несколько вариантов:

  1. Использовать динамический SQL
  2. Игра с синонимами (что означает менее динамический SQL, но все же некоторые)

Вы сказали, что вам не нравится 1, так что давайте перейдем к 2.

Первый вариант - ограничить беспорядок одной строкой:

begin transaction t1;
declare @statement nvarchar(100);

set @statement = 'create synonym temptablesyn for db1.dbo.test;'
exec sp_executesql @statement

select * from db_syn

drop synonym db_syn;

rollback transaction t1;

Я не уверен, что мне это нравится, но это может быть вашим лучшим вариантом. Таким образом, все SELECT будут одинаковыми.

Вы можете изменить это для своего сердца, но у этого есть ряд недостатков, включая то, что в транзакции создается синоним, поэтому у вас не может быть двух из запросов, запущенных в то же время (потому что оба будут пытаться создать temptablesyn). в зависимости на стратегии блокировки, один будет заблокировать другой.

Синонимы являются постоянными, поэтому вам необходимо сделать это в транзакции.

1 голос
/ 06 апреля 2009

Вы можете создать UDF с табличным значением SQL CLR для доступа к таблицам. Вы должны связать его со схемой, потому что TV-UDF не поддерживают динамическую схему. (Мой образец включает в себя идентификатор и столбец заголовка - измените для ваших нужд)

Как только вы это сделаете, вы сможете выполнить следующий запрос:

SELECT * FROM dbo.FromMyTable('table1')

Вы также можете включить составное имя в эту строку.

SELECT * FROM dbo.FromMyTable('otherdb..table1')

чтобы вернуть ID, столбцы заголовков из этой таблицы.

Скорее всего, вам потребуется включить SQL CLR и включить параметр TRUSTWORTHY:

sp_configure 'clr enabled',1
go
reconfigure
go
alter database mydatabase set trustworthy on

Создайте проект C # SQL, добавьте новый файл UDF и вставьте его туда. Установите Свойство проекта, База данных, Уровень разрешения на внешнее. Сборка, развертывание. Может быть сделано без VisualStudio. Дайте мне знать, если вам это нужно.

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Data.SqlClient;

[assembly: CLSCompliant(true)]
namespace FromMyTable
{
    public static partial class UserDefinedFunctions
    {
        [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true, SystemDataAccess = SystemDataAccessKind.Read, IsPrecise = true, FillRowMethodName = "FillRow", 
            TableDefinition = "id int, title nvarchar(1024)")]
        public static IEnumerable FromMyTable(SqlString tableName)
        {
            return new FromMyTable(tableName.Value);
        }

        public static void FillRow(object row, out SqlInt32 id, out SqlString title)
        {
            MyTableSchema v = (MyTableSchema)row;
            id = new SqlInt32(v.id);
            title = new SqlString(v.title);
        }
    }

    public class MyTableSchema
    {
        public int id;
        public string title;
        public MyTableSchema(int id, string title) { this.id = id; this.title = title; }
    }

    internal class FromMyTable : IEnumerable
    {
        string tableName;

        public FromMyTable(string tableName)
        {
            this.tableName = tableName;
        }

        public IEnumerator GetEnumerator()
        {
            return new FromMyTableEnum(tableName);
        }
    }

    internal class FromMyTableEnum : IEnumerator
    {
        SqlConnection cn;
        SqlCommand cmd;
        SqlDataReader rdr;
        string tableName;

        public FromMyTableEnum(string tableName)
        {
            this.tableName = tableName;
            Reset();
        }

        public MyTableSchema Current
        {
            get { return new MyTableSchema((int)rdr["id"], (string)rdr["title"]); }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            bool b = rdr.Read();
            if (!b) { rdr.Dispose(); cmd.Dispose(); cn.Dispose(); rdr = null; cmd = null; cn = null; }
            return b;
        }

        public void Reset()
        {
            // note: cannot use a context connection here because it will be closed
            // in between calls to the enumerator.
            if (cn == null) { cn = new SqlConnection("server=localhost;database=mydatabase;Integrated Security=true;"); cn.Open(); }
            if (cmd == null) cmd = new SqlCommand("select id, title FROM " + tableName, cn);
            if (rdr != null) rdr.Dispose();
            rdr = cmd.ExecuteReader();
        }
    }
}
1 голос
/ 05 апреля 2009

Я думаю, что у Марка Бриттингема правильная идея (здесь: h ttp: //stackoverflow.com/questions/688425/evaluate-in-t-sql/718223#718223), который должен выполнить команду use database и записать sp, чтобы НЕ полностью квалифицировать имя таблицы Как он отмечает, это будет действовать для таблиц в текущей базе данных логина.

Позвольте мне добавить несколько возможных уточнений:

Из комментария ОП я понимаю, что база данных меняется один раз в месяц, когда она становится "слишком большой". («Мы должны загружать данные в новую базу данных каждый месяц, иначе она станет слишком большой. - d03boy»)

  1. Пользовательские логины имеют базу данных по умолчанию, заданную с помощью sp_defaultdb (устарела) или ALTER LOGIN. Если каждый месяц вы переходите к новой базе данных и вам не нужно запускать sp для более старых копий, просто меняйте ежемесячную базу данных по умолчанию для имени пользователя и снова не полностью определяйте имя таблицы.

  2. База данных для использования может быть установлена ​​в логине клиента: sqlcmd -U login_id -P password -d db_name, затем выполните sp оттуда.

  3. Вы можете установить соединение с базой данных, используя клиент по вашему выбору (командная строка, ODBC, JDBC), затем выполнить команду use database, выполнив команду sp.

    использовать панель базы данных; exec sp_foo;

Как только база данных была настроена с помощью одного из вышеперечисленных, у вас есть три варианта выполнения хранимой процедуры:

  1. Вы можете просто скопировать sp вместе с базой данных в новую базу данных. Пока имя таблицы НЕ полностью определено, вы будете работать с таблицей новой базы данных.

    exec sp_foo;

  2. Вы можете установить единственную каноническую копию sp в своей собственной базе данных, назвать ее procs, имя таблицы не полностью определено, а затем вызвать ее неверно определенное имя:

    exec procs.dbo.sp_foo;

  3. В каждой отдельной базе данных вы можете установить заглушку sp_foo, которая включает в себя полное имя реального sp, а затем выполнить exec sp_foo, не уточняя его. Будет вызвана заглушка, и она вызовет настоящую процедуру в procs. (К сожалению, use database dbname не может быть выполнено изнутри sp.)

    --sp_foo stub:
    create proc bar.dbo.sp_foo 
     @parm int
    as
    begin
      exec procs.dbo.sp_foo @parm;
    end
    go

Однако это делается, если база данных изменяется, реальный sp должен быть создан с опцией WITH RECOMPILE, в противном случае он будет кэшировать план выполнения для неверной таблицы. Заглушка конечно не нуждается в этом.

1 голос
/ 04 апреля 2009

Просто мысль, но если бы у вас был заранее определенный список этих баз данных, то вы могли бы создать единственное представление в базе данных, к которой вы подключаетесь, чтобы присоединиться к ним - что-то вроде:

CREATE VIEW dbo.all_tables
AS

SELECT  your_columns,
        'db_name1' AS database_name
FROM    db_name1.dbo.your_table

UNION ALL

SELECT  your_columns,
        'db_name2'
FROM    db_name2.dbo.your_table

etc...

Затем вы можете передать имя вашей базы данных в вашу хранимую процедуру и просто использовать его в качестве параметра в предложении WHERE. Если таблицы большие, вы можете рассмотреть возможность использования индексированного представления, индексированного по новому столбцу database_name (или как вы его называете) и первичного ключа таблиц (я предполагаю из вопроса, что схемы таблиц одинаковы? ).

Очевидно, что если ваш список баз данных часто меняется, то это становится более проблематичным - но если вам все равно придется создавать эти базы данных, то поддержание этого представления в то же время не должно быть слишком большой нагрузкой!

1 голос
/ 02 апреля 2009

Есть несколько вариантов, но они более грязные, чем вы уже делаете. Я предлагаю вам либо:
(1) Придерживайтесь текущего подхода
(2) Продолжайте встраивать SQL-код в код, поскольку вы все равно это делаете.
(3) Будьте особенно осторожны при проверке ввода, чтобы избежать SQL-инъекций.

Кроме того, беспорядок не единственная проблема с динамическим SQL. Помните следующее:
(1) Динамический SQL препятствует способности сервера создавать повторно используемый план выполнения.
(2) Команда ExecuteSQL разрывает цепочку владения. Это означает, что код будет выполняться в контексте пользователя, который вызывает хранимую процедуру, а НЕ владельца процедуры. Это может заставить вас открыть защиту для любой таблицы, с которой работает оператор, и создать другие проблемы безопасности.

0 голосов
/ 09 апреля 2009
if exists (select * from master..sysservers where srvname = 'fromdb')
    exec sp_dropserver 'fromdb'
go

declare @mydb nvarchar(99);
set @mydb='mydatabase'; -- variable to select database

exec sp_addlinkedserver @server = N'fromdb',
    @srvproduct = N'',
    @provider = N'SQLOLEDB', 
    @datasrc = @@servername,
    @catalog = @mydb
go

select * from OPENQUERY(fromdb, 'select * from table1') 
go 
0 голосов
/ 05 апреля 2009

Если у вас достаточно управляемое количество баз данных, лучше всего использовать предопределенный условный оператор, например:

if (@dbname = 'db1')
  select * from db1..MyTable
if (@dbname = 'db2')
  select * from db2..MyTable
if (@dbname = 'db3')
  select * from db3..MyTable

...

вы можете создать этот процесс как часть ваших сценариев создания базы данных, если вы изменяете список баз данных, доступных для запроса.

Это позволяет избежать проблем с безопасностью при динамическом sql. Вы также можете повысить производительность, заменив операторы «select» хранимыми процедурами, нацеленными на каждую базу данных (1 кешированный план выполнения на запрос).

...