T-SQL 动态 SQL 和临时表

发布于 2024-09-03 19:28:22 字数 2333 浏览 9 评论 0原文

看起来通过 EXECUTE 字符串方法使用动态 SQL 创建的 #temptables 具有不同的作用域,并且不能由同一存储过程中的“固定”SQL 引用。 但是,我可以在后续动态 SQL 中引用由动态 SQL 语句创建的临时表,但除非 SQL 已修复,否则存储过程似乎不会将查询结果返回给调用客户端。

一个简单的 2 表场景: 我有2张桌子。我们将它们称为订单和项目。订单的主键为 OrderId,项目的主键为 ItemId。 Items.OrderId 是标识父订单的外键。一个订单可以有 1 到 n 个项目。

我希望能够向用户提供一个非常灵活的“查询生成器”类型界面,以允许用户选择他想要查看的项目。过滤条件可以基于项目表和/或父订单表中的字段。如果某个项目满足过滤条件,包括父订单上的条件(如果存在),则查询中应返回该项目以及父订单。

通常,我想大多数人会在 Item 表和父 Order 表之间构建一个联接。我想执行 2 个单独的查询。一个返回所有符合条件的商品,另一个返回所有不同的父订单。原因有两个,你可能同意也可能不同意。

第一个原因是我需要查询父订单表中的所有列,如果我执行单个查询将订单表连接到项目表,我将多次重复订单信息。由于每个订单通常有大量商品,我想避免这种情况,因为这会导致更多数据传输到胖客户端。相反,如上所述,我想在数据集中单独返回两个表,并使用其中的两个表来填充自定义订单和子项目客户端对象。 (我对 LINQ 或实体框架还不够了解。我手动构建我的对象)。我想返回两个表而不是一个表的第二个原因是因为我已经有另一个过程,该过程返回给定 OrderId 的所有项目以及父订单,并且我想使用相同的 2 表方法,以便我可以重用客户端代码来从返回的 2 个数据表中填充我的自定义订单和客户端对象。

我希望做的是:

在客户端上构造一个动态 SQL 字符串,它将订单表连接到项目表,并按照在 Winform 胖客户端应用程序上创建的自定义过滤器指定的方式对每个表进行适当的过滤。客户端上的 SQL 构建看起来像这样:

TempSQL = "

    INSERT INTO #ItemsToQuery
       OrderId, ItemsId
    FROM
       Orders, Items 
    WHERE
       Orders.OrderID = Items.OrderId AND
       /* Some unpredictable Order filters go here */
      AND
       /* Some unpredictable Items filters go here */
    "

然后,我会调用一个存储过程,

CREATE PROCEDURE GetItemsAndOrders(@tempSql as text)
   Execute (@tempSQL) --to create the #ItemsToQuery table

SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)

SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)

这种方法的问题是 #ItemsToQuery 表,因为它是由动态 SQL 创建的,所以无法从以下 2 个静态 SQL 访问,并且如果我将静态 SQL 更改为动态 SQL,则不会将任何结果传回胖客户端。

我想到了 3 个左右,但我正在寻找一个更好的:

1)第一个 SQL 可以通过从客户端执行动态构造的 SQL 来执行。然后,结果可以作为表传递到上述存储过程的修改版本。我熟悉以 XML 形式传递表数据。如果我这样做,存储过程就可以使用静态 SQL 将数据插入到临时表中,因为它是由动态 SQL 创建的,所以可以毫无问题地进行查询。 (我还可以研究传递新的 Table 类型参数而不是 XML。)但是,我想避免将可能较大的列表传递给存储过程。

2)我可以执行客户端的所有查询。

第一个是这样的:

SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)

这仍然为我提供了重用客户端对象填充代码的能力,因为订单和项目继续在两个不同的表中返回。

我有一种感觉,我可能有一些在存储过程中使用表数据类型的选项,但这对我来说也是新的,我会很感激在这个选项上进行一点点喂养。

如果你浏览了我写的这篇文章,我会感到惊讶,但如果是这样,我会很欣赏你关于如何最好地实现这一目标的任何想法。

It looks like #temptables created using dynamic SQL via the EXECUTE string method have a different scope and can't be referenced by "fixed" SQLs in the same stored procedure.
However, I can reference a temp table created by a dynamic SQL statement in a subsequence dynamic SQL but it seems that a stored procedure does not return a query result to a calling client unless the SQL is fixed.

A simple 2 table scenario:
I have 2 tables. Let's call them Orders and Items. Order has a Primary key of OrderId and Items has a Primary Key of ItemId. Items.OrderId is the foreign key to identify the parent Order. An Order can have 1 to n Items.

I want to be able to provide a very flexible "query builder" type interface to the user to allow the user to select what Items he want to see. The filter criteria can be based on fields from the Items table and/or from the parent Order table. If an Item meets the filter condition including and condition on the parent Order if one exists, the Item should be return in the query as well as the parent Order.

Usually, I suppose, most people would construct a join between the Item table and the parent Order tables. I would like to perform 2 separate queries instead. One to return all of the qualifying Items and the other to return all of the distinct parent Orders. The reason is two fold and you may or may not agree.

The first reason is that I need to query all of the columns in the parent Order table and if I did a single query to join the Orders table to the Items table, I would be repoeating the Order information multiple times. Since there are typically a large number of items per Order, I'd like to avoid this because it would result in much more data being transfered to a fat client. Instead, as mentioned, I would like to return the two tables individually in a dataset and use the two tables within to populate a custom Order and child Items client objects. (I don't know enough about LINQ or Entity Framework yet. I build my objects by hand). The second reason I would like to return two tables instead of one is because I already have another procedure that returns all of the Items for a given OrderId along with the parent Order and I would like to use the same 2-table approach so that I could reuse the client code to populate my custom Order and Client objects from the 2 datatables returned.

What I was hoping to do was this:

Construct a dynamic SQL string on the Client which joins the orders table to the Items table and filters appropriate on each table as specified by the custom filter created on the Winform fat-client app. The SQL build on the client would have looked something like this:

TempSQL = "

    INSERT INTO #ItemsToQuery
       OrderId, ItemsId
    FROM
       Orders, Items 
    WHERE
       Orders.OrderID = Items.OrderId AND
       /* Some unpredictable Order filters go here */
      AND
       /* Some unpredictable Items filters go here */
    "

Then, I would call a stored procedure,

CREATE PROCEDURE GetItemsAndOrders(@tempSql as text)
   Execute (@tempSQL) --to create the #ItemsToQuery table

SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)

SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)

The problem with this approach is that #ItemsToQuery table, since it was created by dynamic SQL, is inaccessible from the following 2 static SQLs and if I change the static SQLs to dynamic, no results are passed back to the fat client.

3 around come to mind but I'm look for a better one:

1) The first SQL could be performed by executing the dynamically constructed SQL from the client. The results could then be passed as a table to a modified version of the above stored procedure. I am familiar with passing table data as XML. If I did this, the stored proc could then insert the data into a temporary table using a static SQL that, because it was created by dynamic SQL, could then be queried without issue. (I could also investigate into passing the new Table type param instead of XML.) However, I would like to avoid passing up potentially large lists to a stored procedure.

2) I could perform all the queries from the client.

The first would be something like this:

SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)

This still provides me with the ability to reuse my client sided object-population code because the Orders and Items continue to be returned in two different tables.

I have a feeling to, that I might have some options using a Table data type within my stored proc, but that is also new to me and I would appreciate a little bit of spoon feeding on that one.

If you even scanned this far in what I wrote, I am surprised, but if so, I woul dappreciate any of your thoughts on how to accomplish this best.

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

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

发布评论

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

评论(5

初相遇 2024-09-10 19:28:22

您首先需要创建表,然后它将在动态 SQL 中可用。

这有效:

CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')

SELECT *
FROM #temp3

这不起作用:

EXEC (
        'create table #temp2 (id int)
         insert #temp2 values(1)'
        )

SELECT *
FROM #temp2

换句话说:

  1. 创建临时表
  2. 执行 proc
  3. 从临时表中选择

这是完整的示例:

CREATE PROC prTest2 @var VARCHAR(100)
AS
EXEC (@var)
GO

CREATE TABLE #temp (id INT)

EXEC prTest2 'insert #temp values(1)'

SELECT *
FROM #temp

You first need to create your table first then it will be available in the dynamic SQL.

This works:

CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')

SELECT *
FROM #temp3

This will not work:

EXEC (
        'create table #temp2 (id int)
         insert #temp2 values(1)'
        )

SELECT *
FROM #temp2

In other words:

  1. Create temp table
  2. Execute proc
  3. Select from temp table

Here is complete example:

CREATE PROC prTest2 @var VARCHAR(100)
AS
EXEC (@var)
GO

CREATE TABLE #temp (id INT)

EXEC prTest2 'insert #temp values(1)'

SELECT *
FROM #temp
深巷少女 2024-09-10 19:28:22

第一种方法 - 将多个语句包含在同一个动态 SQL 调用中:

DECLARE @DynamicQuery NVARCHAR(MAX)

SET @DynamicQuery = 'Select * into #temp from (select * from tablename) alias 
select * from #temp
drop table #temp'

EXEC sp_executesql @DynamicQuery

第二种方法 - 使用全局临时表:
(小心,您需要额外注意全局变量。)

IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
    EXEC (
            'create table ##temp2 (id int)
             insert ##temp2 values(1)'
            )

    SELECT *
    FROM ##temp2
END

使用完 ##temp2 对象后,不要忘记手动删除它:

IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
     DROP Table ##temp2
END

注意:不要使用此方法 2如果您不知道数据库的完整结构。

1st Method - Enclose multiple statements in the same Dynamic SQL Call:

DECLARE @DynamicQuery NVARCHAR(MAX)

SET @DynamicQuery = 'Select * into #temp from (select * from tablename) alias 
select * from #temp
drop table #temp'

EXEC sp_executesql @DynamicQuery

2nd Method - Use Global Temp Table:
(Careful, you need to take extra care of global variable.)

IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
    EXEC (
            'create table ##temp2 (id int)
             insert ##temp2 values(1)'
            )

    SELECT *
    FROM ##temp2
END

Don't forget to delete ##temp2 object manually once your done with it:

IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
     DROP Table ##temp2
END

Note: Don't use this method 2 if you don't know the full structure on database.

萌吟 2024-09-10 19:28:22

我遇到了 @Muflix 提到的同样的问题。当您不知道要返回的列,或者它们是动态生成的时,我所做的是创建一个具有唯一 id 的全局表,然后在完成后将其删除,这看起来类似于所示的内容如下:

DECLARE @DynamicSQL NVARCHAR(MAX)
DECLARE @DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE @DynamicColumns NVARCHAR(MAX)

--Get "@DynamicColumns", example: SET @DynamicColumns = '[Column1], [Column2]'

SET @DynamicSQL = 'SELECT ' + @DynamicColumns + ' INTO [##' + @DynamicTable + ']' + 
     ' FROM [dbo].[TableXYZ]'

EXEC sp_executesql @DynamicSQL

SET @DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + @DynamicTable + ''' , ''U'') IS NOT NULL ' + 
    ' BEGIN DROP TABLE [##' + @DynamicTable + '] END'

EXEC sp_executesql @DynamicSQL

当然不是最好的解决方案,但这似乎对我有用。

I had the same issue that @Muflix mentioned. When you don't know the columns being returned, or they are being generated dynamically, what I've done is create a global table with a unique id, then delete it when I'm done with it, this looks something like what's shown below:

DECLARE @DynamicSQL NVARCHAR(MAX)
DECLARE @DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE @DynamicColumns NVARCHAR(MAX)

--Get "@DynamicColumns", example: SET @DynamicColumns = '[Column1], [Column2]'

SET @DynamicSQL = 'SELECT ' + @DynamicColumns + ' INTO [##' + @DynamicTable + ']' + 
     ' FROM [dbo].[TableXYZ]'

EXEC sp_executesql @DynamicSQL

SET @DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + @DynamicTable + ''' , ''U'') IS NOT NULL ' + 
    ' BEGIN DROP TABLE [##' + @DynamicTable + '] END'

EXEC sp_executesql @DynamicSQL

Certainly not the best solution, but this seems to work for me.

甜心 2024-09-10 19:28:22

我强烈建议您阅读 http://www.sommarskog.se /arrays-in-sql-2005.html

就我个人而言,我喜欢传递逗号分隔的文本列表,然后将其与文本解析到表函数并连接到它的方法。如果您首先在连接中创建临时表方法,则它可以起作用。但感觉有点乱。

I would strongly suggest you have a read through http://www.sommarskog.se/arrays-in-sql-2005.html

Personally I like the approach of passing a comma delimited text list, then parsing it with text to table function and joining to it. The temp table approach can work if you create it first in the connection. But it feel a bit messier.

ι不睡觉的鱼゛ 2024-09-10 19:28:22

动态 SQL 的结果集返回给客户端。我已经做了很多这样的事了。

关于通过临时表和变量以及 SQL 和它生成的动态 SQL 之间的共享数据之类的问题,您的看法是正确的。

我认为在尝试让临时表工作时,您可能会感到困惑,因为您绝对可以从执行动态 SQL 的 SP 获取数据:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + ''''
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO

另外:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + '''; SELECT * FROM #temp;'
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO

Result sets from dynamic SQL are returned to the client. I have done this quite a lot.

You're right about issues with sharing data through temp tables and variables and things like that between the SQL and the dynamic SQL it generates.

I think in trying to get your temp table working, you have probably got some things confused, because you can definitely get data from a SP which executes dynamic SQL:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + ''''
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO

Also:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + '''; SELECT * FROM #temp;'
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

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