【问题标题】:Evaluate in T-SQL在 T-SQL 中评估
【发布时间】:2009-03-27 03:27:04
【问题描述】:

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

eval(@dbname + 'MyTable')

目前我一直在创建一个字符串,然后使用exec() 将该字符串作为查询运行。这很混乱,我宁愿不必创建一个字符串。有没有办法可以评估变量或字符串,以便我可以执行以下操作?

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

我希望它进行评估,所以它最终看起来像这样:

SELECT *
FROM myserver.mydatabase.dbo.MyTable

【问题讨论】:

  • d03boy - 我删除了我的建议,因为我忘记了“使用”不能在存储过程中使用。我也应该知道得更多,因为几个月前我遇到了这个问题。对不起,如果我把你带错了路。
  • 我不知道它不能用所以我至少学到了这么多:)
  • d03boy - 我删除了我的第三个建议,即使用 OPENROWSET,因为您不能在连接字符串或查询中使用变量。无赖。

标签: sql-server-2005 tsql evaluation


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    没有“更简洁”的方法可以做到这一点。如果你接受它并看看别的东西,你会节省时间。

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

    SQL Server 提供了处理“太大”的表(特别是分区)的本机机制,这将允许您将表作为单个实体来处理,同时在后台将表划分为单独的文件,因此彻底解决您当前的问题。

    换句话说,这是您的数据库管理员的问题,而不是数据库使用者的问题。如果您也遇到这种情况,我建议您查看partitioning 这张表。

    【讨论】:

    • 阿门。听起来他的数据库架构是这里万恶之源。
    • 这是为什么呢?我们必须每个月将数据加载到一个新数据库中,否则它会变得太大。
    • 这实际上不是我们的软件。我们每个月都会在软件中创建新的“项目”。我们可以对数据库做一些工作,但受限于我们对系统的了解不足。
    • 另外,这跨越了许多项目,无论如何这些项目都将位于不同的数据库中。所以代码仍然必须是动态的。
    • 在这种情况下,我坚持我的第一个答案。
    【解决方案3】:

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

    exec sp_executesql @SQLString.
    
    DECLARE @SQLString nvarchar(max)
    SELECT @SQLString = '
    SELECT *
    FROM  ' +  @TableName 
    
    EXEC sp_executesql @SQLString
    

    【讨论】:

    • 这是他目前正在做的,不喜欢的。
    • 这就是我想避免的:)
    【解决方案4】:

    您不能在 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)。取决于 根据锁定策略,将 挡住对方。

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

    【讨论】:

      【解决方案5】:

      有几个选项,但它们比您已经在做的方式更混乱。我建议你:
      (1) 坚持目前的做法
      (2) 继续将 SQL 嵌入代码中,因为无论如何您都在这样做。
      (3) 要格外小心地验证您的输入以避免 SQL 注入。

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

      【讨论】:

        【解决方案6】:

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

        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 列(或任何您称之为的)和表的主键(我从问题中假设表的模式是相同的? )。

        显然,如果您的数据库列表经常更改,那么这将变得更加成问题 - 但如果您无论如何都必须创建这些数据库,那么同时维护此视图应该不会造成太大的开销!

        【讨论】:

        • 这些数据库的添加频率很高,很难跟踪和维护正确的列表。
        • 然后制作一个数据库维护脚本,通过检查服务器上的所有数据库并检查它们是否属于适当的类型来创建视图(每晚?)。然后,您还可以在您的选择中自动插入一个文本列,该列是数据库的名称(因此您可以将其传入)
        【解决方案7】:

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

        让我添加一些可能的详细说明:

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

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

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

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

          使用数据库栏; 执行 sp_foo;

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

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

          执行 sp_foo;

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

          exec procs.dbo.sp_foo;

        3. 您可以在每个单独的数据库中安装一个存根sp_foo 来执行真正的 sp 的完全限定名称,然后在不限定它的情况下执行 sp_foo。将调用存根,它会调用procs 中的真实过程。 (很遗憾,use database dbname 不能在 sp 中执行。)

          --sp_foo 存根:
          创建过程 bar.dbo.sp_foo
           @parm 整数
          作为
          开始
            执行 procs.dbo.sp_foo @parm;
          结尾
          去

        然而这样做,如果数据库正在被改变,真正的 sp 应该使用WITH RECOMPILE 选项创建,否则它将为错误的表缓存一个执行计划。存根当然不需要这个。

        【讨论】:

          【解决方案8】:

          您可以创建一个 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();
                  }
              }
          }
          

          【讨论】:

            【解决方案9】:
            declare @sql varchar(256);
            set @sql = 'select * into ##myGlobalTemporaryTable from '+@dbname
            exec sp_executesql @sql
            
            select * from ##myGlobalTemporaryTable
            

            复制到一个全局临时表中,然后您可以像普通表一样使用它

            【讨论】:

            • 在我的具体情况下,这很可能会使用太多资源。巨大的桌子。
            【解决方案10】:

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

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

            ...

            如果您要更改可查询的数据库列表,您可以生成此过程作为数据库创建脚本的一部分。

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

            【讨论】:

              【解决方案11】:
              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 
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-06-10
                • 1970-01-01
                • 2016-08-11
                • 1970-01-01
                相关资源
                最近更新 更多