在 T-SQL 中评估
我有一个存储过程,允许 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
没有“更简洁”的方法可以做到这一点。 如果您接受它并查看其他内容,则会节省时间。
编辑:啊哈! 关于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.
尝试 sp_executesql 内置函数。
你基本上可以在你的过程中构建你的SQL字符串,然后调用
try the sp_executesql built in function.
You can basically build up your SQL string in your proc, then call
您无法在 SQL Server 中指定动态表名称。
有几个选项:
您已经说过您不喜欢 1,所以让我们选择 2。
第一个选项是将混乱限制为一个line:
我不确定我喜欢这个,但这可能是你最好的选择。 这样所有的 SELECT 都将是相同的。
您可以将其重构为您喜欢的内容,但是这样做有很多缺点,包括同义词是在事务中创建的,因此您不能有两个
同时运行的查询数
时间(因为双方都会尝试
创建 temptablesyn)。 取决于
根据锁定策略,我们将
阻止对方。
同义词是永久性的,因此这就是您需要在事务中执行此操作的原因。
You can't specify a dynamic table name in SQL Server.
There are a few options:
You've said you don't like 1, so lets go for 2.
First option is to restrict the messyness to one line:
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.
有一些选择,但它们比您已经做的方式更混乱。 我建议您:
(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.
只是一个想法,但如果您有这些数据库的预定义列表,那么您可以在连接到的数据库中创建一个视图来加入它们 - 类似于:
然后,您可以将数据库名称传递到存储的数据库中过程并简单地将其用作 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:
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!
我认为马克·布里廷汉姆的想法是正确的(此处:
http://stackoverflow.com/questions /688425/evaluate-in-t-sql/718223#718223),即发出
use database
命令并写入 sp 以不完全限定表名。 正如他所指出的,这将作用于登录名当前数据库中的表。让我添加一些可能的阐述:
根据OP的评论,我收集数据库每月更改一次,当它变得“太大”时。 (“我们必须每个月将数据加载到新数据库中,否则它会变得太大。– d03boy”)
用户登录有一个默认数据库,使用 sp_defaultdb(已弃用)或 ALTER LOGIN 设置。 如果每个月您都会转移到新数据库,并且不需要在旧副本上运行 sp,只需每月更改登录的默认数据库,并且再次强调,不要完全限定表名。
可以在客户端登录中设置要使用的数据库:
sqlcmd -U login_id -P password -d db_name
,然后从那里执行 sp。您可以使用您选择的客户端(命令行、ODBC、JDBC)建立与数据库的连接,然后发出
use database
命令,执行 sp。使用数据库栏;
exec sp_foo;
使用上述方法之一设置数据库后,您可以选择执行存储过程的三种选择:
您可以将 sp 与数据库一起复制到新数据库中。 只要表名不是完全限定的,您就可以对新数据库的表进行操作。
exec sp_foo;
您可以在其自己的数据库中安装 sp 的单个规范副本,将其称为
procs
,表名不完全限定,然后调用其完全限定名称:exec procs.dbo.sp_foo;
您可以在每个单独的数据库中安装一个存根
sp_foo
来执行真实 sp 的完全限定名称,然后执行sp_foo
而不对其进行限定。 存根将被调用,并且它将调用 procs 中的真实过程。 (不幸的是,use database dbname
无法在 sp 内执行。)但是,这样做后,如果数据库正在更改,则应使用
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")
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.
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.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:
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;
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;
You could, in each individual database, install a stub
sp_foo
that execs the fully qualified name of the real sp, and then execsp_foo
without qualifying it. The stub will be called, and it will call the real procedure inprocs
. (Unfortunately,use database dbname
cannot be executed from within an sp.)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.您可以创建 SQL CLR 表值 UDF 来访问表。 您必须将其与架构联系起来,因为 TV-UDF 不支持动态架构。 (我的示例包括 ID 和标题列 - 根据您的需要进行修改)
完成此操作后,您应该能够执行以下查询:
您也可以在该字符串中包含多部分名称。
返回该表中的 ID、标题列。
您可能需要启用 SQL CLR 并打开 TRUSTWORTHY 选项:
创建一个 C# SQL 项目,添加一个新的 UDF 文件,然后将其粘贴到其中。 将项目属性、数据库、权限级别设置为外部。 构建、部署。 无需 VisualStudio 即可完成。 如果您需要,请告诉我。
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:
You can include a multipart name in that string too.
to return the ID,Title columns from that table.
You will likely need to enable SQL CLR and turn on the TRUSTWORTHY option:
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.
复制到全局临时表中,然后您可以像常规表一样使用它
copies into a global temporary table which you can then use like a regular table
如果您有相当数量的数据库,最好使用预定义的条件语句,例如:
...
如果您要更改可查询的数据库列表,您可以生成此过程作为数据库创建脚本的一部分。
这避免了动态 SQL 的安全问题。 您还可以通过将“select”语句替换为针对每个数据库的存储过程(每个查询 1 个缓存的执行计划)来提高性能。
If you have a reasonably manageable number of databases, it may be best to use a pre-defined conditional statement like:
...
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).
阅读此...动态 SQL 的诅咒和祝福,帮助我了解如何解决这类问题。
Read this... The Curse and Blessings of Dynamic SQL, help me a lot understanding how to solve this type of problems.