在 T-SQL 中评估

发布于 2024-07-16 02:34:51 字数 441 浏览 6 评论 0原文

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

eval(@dbname + 'MyTable')

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

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

我希望它进行评估,所以它最终会出现这样的情况:

SELECT *
FROM myserver.mydatabase.dbo.MyTable

I've got a stored procedure that allows an IN parameter specify what database to use. I then use a pre-decided table in that database for a query. The problem I'm having is concatenating the table name to that database name within my queries. If T-SQL had an evaluate function I could do something like

eval(@dbname + 'MyTable')

Currently I'm stuck creating a string and then using exec() to run that string as a query. This is messy and I would rather not have to create a string. Is there a way I can evaluate a variable or string so I can do something like the following?

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

I would like it to evaluate so it ends up appearing like this:

SELECT *
FROM myserver.mydatabase.dbo.MyTable

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

街角迷惘 2024-07-23 02:34:52

没有“更简洁”的方法可以做到这一点。 如果您接受它并查看其他内容,则会节省时间。

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

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

换句话说,这是数据库管理员的问题,而不是数据库消费者的问题。 如果您也遇到这种情况,我建议您研究分区 这个表。

There's no "neater" way to do this. You'll save time if you accept it and look at something else.

EDIT: Aha! Regarding the OP's comment that "We have to load data into a new database each month or else it gets too large.". Surprising in retrospect that no one remarked on the faint smell of this problem.

SQL Server offers native mechanisms for dealing with tables that get "too large" (in particular, partitioning), which will allow you to address the table as a single entity, while dividing the table into separate files in the background, thus eliminating your current problem altogether.

To put it another way, this is a problem for your DB administrator, not the DB consumer. If that happens to be you as well, I suggest you look into partitioning this table.

别靠近我心 2024-07-23 02:34:52

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

exec sp_executesql @SQLString.

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

EXEC sp_executesql @SQLString

try the sp_executesql built in function.
You can basically build up your SQL string in your proc, then call

exec sp_executesql @SQLString.

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

EXEC sp_executesql @SQLString
野の 2024-07-23 02:34:52

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

有几个选项:

  1. 使用动态 SQL
  2. 使用同义词(这意味着动态 SQL 较少,但仍然有一些)

您已经说过您不喜欢 1,所以让我们选择 2。

第一个选项是将混乱限制为一个line:

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)。 取决于
根据锁定策略,我们将
阻止对方。

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

You can't specify a dynamic table name in SQL Server.

There are a few options:

  1. Use dynamic SQL
  2. Play around with synonyms (which means less dynamic SQL, but still some)

You've said you don't like 1, so lets go for 2.

First option is to restrict the messyness to one line:

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;

I'm not sure I like this, but it may be your best option. This way all of the SELECTs will be the same.

You can refactor this to your hearts content, but there are a number of disadvantages to this, including the synonym is created in a transaction, so you can't have two
of the queries running at the same
time (because both will be trying to
create temptablesyn). Depending
upon the locking strategy, one will
block the other.

Synonyms are permanent, so this is why you need to do this in a transaction.

沫尐诺 2024-07-23 02:34:52

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

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

There are a few options, but they are messier than the way you are already doing. I suggest you either:
(1) Stick with the current approach
(2) Go ahead and embed the SQL in the code since you are doing it anyway.
(3) Be extra careful to validate your input to avoid SQL Injection.

Also, messiness isn't the only problem with dynamic SQL. Remember the following:
(1) Dynamic SQL thwarts the server's ability to create a reusable execution plan.
(2) The ExecuteSQL command breaks the ownership chain. That means the code will run in the context of the user who calls the stored procedure NOT the owner of the procedure. This might force you to open security on whatever table the statement is running against and create other security issues.

何处潇湘 2024-07-23 02:34:52

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

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列(或您所说的任何名称)和表的主键上建立索引(我从问题中假设表的架构是相同的? )。

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

Just a thought, but if you had a pre-defined list of these databases, then you could create a single view in the database that you connect to to join them - something like:

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...

Then, you could pass your database name in to your stored procedure and simply use it as a paramater in a WHERE clause. If the tables are large, you might consider using an indexed view, indexed on the new database_name column (or whatever you call it) and the tables' primary key (I'm assuming from the question that the tables' schemas are the same?).

Obviously if your list of database changes frequently, then this becomes more problematic - but if you're having to create these databases anyway, then maintaining this view at the same time shouldn't be too much of an overhead!

筱果果 2024-07-23 02:34:52

我认为马克·布里廷汉姆的想法是正确的(此处:
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。

    使用数据库栏;
    exec sp_foo;

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

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

    exec 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 int 
      作为 
      开始 
        执行 procs.dbo.sp_foo @parm; 
      结尾 
      go

但是,这样做后,如果数据库正在更改,则应使用 WITH RECOMPILE 选项,否则它将缓存错误表的执行计划。 存根当然不需要这个。

I think Mark Brittingham has the right idea (here:
http://stackoverflow.com/questions/688425/evaluate-in-t-sql/718223#718223), which is to issue a use database command and write the sp to NOT fully qualify the table name. As he notes, this will act on tables in the login's current database.

Let me add a few possible elaborations:

From a comment by the OP, I gather the database is changed once a month, when it gets "too large". ("We have to load data into a new database each month or else it gets too large. – d03boy")

  1. User logins have a default database, set with sp_defaultdb (deprecated) or ALTER LOGIN. If each month you move on to the new database, and don't need to run the sp on the older copies, just change the login's default db monthly, and again, don't fully qualify the table name.

  2. The database to use can be set in the client login:
    sqlcmd -U login_id -P password -d db_name, then exec the sp from there.

  3. You can establish a connection to the database using the client of your choice (command line, ODBC, JDBC), then issue a use database command, the exec the sp.

    use database bar;
    exec sp_foo;

Once the database has been set using one of the above, you have three choices for executing the stored procedure:

  1. You could just copy the sp along with the database, in to the new database. As long as the table name is NOT fully qualified, you'll operate on the new database's table.

    exec sp_foo;

  2. You could install the single canonical copy of the sp in its own database, call it procs, with the tablename not fully qualified, and then call its fuly qualified name:

    exec procs.dbo.sp_foo;

  3. You could, in each individual database, install a stub sp_foo that execs the fully qualified name of the real sp, and then exec sp_foo without qualifying it. The stub will be called, and it will call the real procedure in procs. (Unfortunately, use database dbname cannot be executed from within an sp.)

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

However this is done, if the database is being changed, the real sp should be created with the WITH RECOMPILE option, otherwise it'll cache an execution plan for the wrong table. The stub of course doesn't need this.

執念 2024-07-23 02:34:52

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

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

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();
        }
    }
}

You could create a SQL CLR Table-Valued UDF to access the tables. You have to tie it to the schema because TV-UDFs don't support dynamic schema. (My sample includes an ID and a Title column - modify for your needs)

Once you've done this, you should be able to do the follow query:

SELECT * FROM dbo.FromMyTable('table1')

You can include a multipart name in that string too.

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

to return the ID,Title columns from that table.

You will likely need to enable SQL CLR and turn on the TRUSTWORTHY option:

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

Create a C# SQL Project, add a new UDF file, paste this in there. Set Project Property, Database, Permission Level to external. Build, deploy. Can be done without VisualStudio. Let me know if you need that.

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();
        }
    }
}
云胡 2024-07-23 02:34:52
declare @sql varchar(256);
set @sql = 'select * into ##myGlobalTemporaryTable from '+@dbname
exec sp_executesql @sql

select * from ##myGlobalTemporaryTable

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

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

select * from ##myGlobalTemporaryTable

copies into a global temporary table which you can then use like a regular table

掩于岁月 2024-07-23 02:34:52

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

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

...

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

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

If you have a reasonably manageable number of databases, it may be best to use a pre-defined conditional statement like:

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

...

you can generate this proc as part of your database creation scripts if you are changing the list of databases available to query.

This avoids security concerns with dynamic sql. You can also improve performance by replacing the 'select' statements with stored procedures targeting each database (1 cached execution plan per query).

若能看破又如何 2024-07-23 02:34:52
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 
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 
陈年往事 2024-07-23 02:34:51

阅读此...动态 SQL 的诅咒和祝福,帮助我了解如何解决这类问题。

Read this... The Curse and Blessings of Dynamic SQL, help me a lot understanding how to solve this type of problems.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文