在T-SQL中进行评估

时间:2009-03-27 03:27:04

标签: sql-server-2005 tsql evaluation

我有一个存储过程,允许IN参数指定要使用的数据库。然后,我在该数据库中使用预先确定的表进行查询。我遇到的问题是在查询中将表名连接到该数据库名称。如果T-SQL有一个评估函数,我可以做类似

的事情
eval(@dbname + 'MyTable')

目前,我无法创建字符串,然后使用exec()将该字符串作为查询运行。这很麻烦,我宁愿不必创建一个字符串。有没有办法我可以评估一个变量或字符串,所以我可以做类似下面的事情?

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

我希望它能够进行评估,所以最终会出现这样的结果:

SELECT *
FROM myserver.mydatabase.dbo.MyTable

11 个答案:

答案 0 :(得分:16)

阅读本文... The Curse and Blessings of Dynamic SQL,帮助我了解如何解决此类问题。

答案 1 :(得分:9)

没有“整洁”的方法来做到这一点。如果您接受它并查看其他内容,您将节省时间。

编辑:啊哈!关于OP的评论“我们必须每月将数据加载到新数据库中,否则它会变得太大。”令人惊讶的是,没有人评论这个问题的微弱气味。

SQL Server提供了处理“太大”(特别是分区)的表的本机机制,这将允许您将表作为单个实体进行处理,同时将表分成背景中的单独文件,从而完全消除你当前的问题。

换句话说,这对您的数据库管理员而不是数据库使用者来说是一个问题。如果那也是你,我建议你看看partitioning这个表。

答案 2 :(得分:5)

尝试sp_executesql内置函数。 您基本上可以在proc中构建SQL字符串,然后调用

exec sp_executesql @SQLString.

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

EXEC sp_executesql @SQLString

答案 3 :(得分:2)

您无法在SQL Server中指定动态表名。

有几个选择:

  1. 使用动态SQL
  2. 使用同义词(这意味着动态SQL较少,但仍有一些)
  3. 你说过你不喜欢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)。根据     根据锁定策略,人们会     阻止另一个。

    同义词是永久性的,因此这就是你需要在事务中执行此操作的原因。

答案 4 :(得分:1)

有一些选择,但它们比你已经做的更加混乱。我建议你:
(1)坚持目前的做法
(2)继续将SQL嵌入到代码中,因为无论如何你都要这样做 (3)要特别小心验证输入以避免SQL注入。

此外,混乱不是动态SQL的唯一问题。请记住以下内容:
(1)动态SQL阻碍了服务器创建可重用执行计划的能力 (2)ExecuteSQL命令打破了所有权链。这意味着代码将在调用存储过程的用户的上下文中运行而不是过程的所有者。这可能会强制您在运行该语句的任何表上打开安全性并创建其他安全问题。

答案 5 :(得分:1)

只是一个想法,但如果您有这些数据库的预定义列表,那么您可以在连接到的数据库中创建一个视图来加入它们 - 例如:

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列(或者您称之为的任何内容)上建立索引,并使用表的主键(我假设问题是表的模式是相同的? )。

显然,如果您的数据库列表经常更改,那么这就会变得更成问题 - 但如果您不得不创建这些数据库,那么同时维护此视图不应该是一个过多的开销!

答案 6 :(得分:1)

我认为Mark Brittingham有正确的想法(这里: h ttp://stackoverflow.com/questions/688425/evaluate-in-t-sql/718223#718223),用于发出use database命令并将sp写入NOT以完全限定表名。正如他所指出的,这将作用于登录当前数据库中的表。

让我补充一些可能的阐述:

从OP的评论中,我收集的数据库每月更改一次,当它变得“太大”时。 (“我们必须每个月将数据加载到一个新的数据库中,否则它会变得太大。 - d03boy”)

  1. 用户登录有一个默认数据库,使用sp_defaultdb(不建议使用)或ALTER LOGIN设置。如果您每个月都转到新数据库,并且不需要在较旧的副本上运行sp,则只需每月更改登录的默认数据库,再次,不要完全限定表名。

    < / LI>
  2. 可以在客户端登录中设置要使用的数据库: sqlcmd -U login_id -P password -d db_name,然后从那里执行sp。

  3. 您可以使用您选择的客户端(命令行,ODBC,JDBC)建立与数据库的连接,然后发出use database命令,exec为sp。

    使用数据库栏; exec sp_foo;

  4. 使用上述方法之一设置数据库后,您有三种选择来执行存储过程:

    1. 您可以将sp与数据库一起复制到新数据库中。只要表名不是完全限定的,您就可以在新数据库的表上操作。

      exec sp_foo;

    2. 您可以在自己的数据库中安装sp的单个规范副本,将其命名为procs,表名不是完全限定的,然后调用其fuly限定名称:

      exec procs.dbo.sp_foo;

    3. 您可以在每个单独的数据库中安装存根sp_foo,该存根执行真实sp的完全限定名称,然后执行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
    4. 但是这样做了,如果要更改数据库,则应使用WITH RECOMPILE选项创建实际sp,否则它将缓存错误表的执行计划。存根当然不需要这个。

答案 7 :(得分:1)

您可以创建SQL CLR表值UDF来访问表。您必须将其绑定到架构,因为TV-UDF不支持动态架构。 (我的示例包括ID和标题列 - 根据您的需要进行修改)

完成此操作后,您应该可以执行以下查询:

SELECT * FROM dbo.FromMyTable('table1')

您也可以在该字符串中包含多部分名称。

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

从该表中返回ID,Title列。

您可能需要启用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();
        }
    }
}

答案 8 :(得分:0)

declare @sql varchar(256);
set @sql = 'select * into ##myGlobalTemporaryTable from '+@dbname
exec sp_executesql @sql

select * from ##myGlobalTemporaryTable

复制到全局临时表中,然后可以像常规表一样使用

答案 9 :(得分:0)

如果您有合理可管理的数据库数量,最好使用预定义的条件语句,如:

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

...

如果要更改可用于查询的数据库列表,则可以将此proc作为数据库创建脚本的一部分生成。

这避免了动态sql的安全问题。您还可以通过将“select”语句替换为针对每个数据库的存储过程(每个查询1个缓存的执行计划)来​​提高性能。

答案 10 :(得分:0)

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