SQL Server - 从子-父-子中选择并返回多个结果集

Posted

技术标签:

【中文标题】SQL Server - 从子-父-子中选择并返回多个结果集【英文标题】:SQL Server - Select from child-parent-child and return multiple results-set 【发布时间】:2015-06-28 05:30:17 【问题描述】:

我正在使用 SQL Server 12/Azure 并且有 3 个表(T1、T2、T3),其中 T1 有 1-many T2 和 T3,我想从 T2 中选择并返回 T1 记录的信息及其关联的 T3记录。 举个简单的例子,T1 是“客户”,T1 是“订单”,T3 是“客户地址”,所以一个客户可以有多个订单和多个地址。现在我想查询订单并包含客户信息和地址,让事情变得有点复杂,订单查询可能包括匹配客户地址,例如获取这些地址的订单。

Customer Table                   
----------------------          
Id, Name,...                    
----------------------          

Orders Table                            
------------------------------          
OrderId, CustomerKey, Date,...          
------------------------------          

CustomerAddresses
-----------------------------------------------
AutoNumber, CustomerKey, Street, ZipCode,...
-----------------------------------------------

我无法编写最佳方式(优化)以在一个事务中返回所有结果并动态生成 sql 语句,这就是我认为结果应该返回的方式:

订单 (T2) 和客户信息 (T1) 在一个结果集/表中返回,而客户地址 (T2) 在另一个结果集/表中返回。我正在使用 ADO.NET 生成和执行查询,并使用 System.Data.SqlClient.SqlDataReader 循环返回的结果。

结果如何返回的示例:

Order-Customer Table
-------------------------------
Order.OrderId, Customer.Id, Customer.Name, Order.Date,....
-------------------------------

CustomerAddresses
-------------------------------
AutoNumber, CustomerKey, Street
-------------------------------

这是我当前生成的查询示例:

SELECT [Order].[OrderId], [Order].[Date], [Customer].[Id], [Customer].[Name] 
FROM Order 
INNER JOIN [Customer] on [Order].[CustomerKey] = [Customer].[Id] 
WHERE ([Order].[Date] > '2015-06-28') 

问题: 1. 如何扩展上述查询以允许在单独的结果集/表中返回 CustomerAddresses? 为了在 CustomerAddresses 上启用匹配,我应该能够与 Customer 表进行连接,并在 WHERE 语句中包含我需要匹配的任何列。

    有没有更好、更简单、更优化的方法来实现我想要的?

-------- 更新 --------------- 要详细说明我如何在我的应用中使用返回的数据:

    我正在使用 ADO.NET、SqlConnection、SqlCommand 和 SqlDataReader 来解析结果。 (我不想在这里使用实体框架或任何其他高级数据库框架)

    我的模型对象是 T2(订单)的集合,其中包含 T1(客户信息)和 T3(客户地址)

订单类: OrderId, OrderDate, CustomerId, CustomerName, CustomerAddresses[],...

CustomerAddresses 类: 街道,邮政编码,....

我发现人们通常在一个返回冗余数据的表中的单个选择语句中返回所有结果。我更喜欢按原样返回仅包含相关信息的表(T1、T2 和 T3),然后我可以在我的应用程序中对其进行处理以创建模型。

另一种解决方案是将 select 语句中的 ID 插入到 Temp 表中,然后在多个 select 语句中返回结果:

Select T1.* From T1 where  Id in (
        select Temp.T1Id from Temp )
Select T2.* From T2 where  Id in (
        select Temp.T2Id from Temp )
Select T3.* From T3 where  Id in (
        select Temp.T3Id from Temp )

【问题讨论】:

我不是 100% 确定您为什么希望在单个事务中分离从数据库返回的 2 个相互关联的结果集?我只能想象您的意图是稍后重用所有客户地址?但是,获取已根据您的要求过滤的单个结果集,然后在运行时将集合逻辑分离到您所需的实体模型中可能会更有效。 @Geewers 如果有更好的方法可以向我解释作为一个很好的答案,基本上我将使用所有这些信息并将它们组合为多个结果 - 域对象 - 在我的应用程序。 当然@zaidsafadi,我的第一条评论是关于利用现有概念,即在模型层中使用工厂模式来构建业务实体,以供领域对象在技术堆栈中使用。为了提供更多细节,我需要更多地了解您的具体情况。例如,您使用的是 ADO.NET Entity Framework DBContexts 还是 ADO.NET SQLConnection 和相关对象? @Geewers 请找到我的更新,我希望能更清楚地了解我想要完成的工作。谢谢! 在下面查看我的完整答案。万事如意 【参考方案1】:

这是一个很好的问题,也是一个在其他地方显然没有很好解决的常见问题。正如您所提到的,问题在于,由于每个客户可能有许多订单和地址,因此单个查询中的结果数量可能很大。例如,

select * from customer
left outer join order 
on (order.customer_id = customer.customer_id)
left outer join customer_orders co 
on (co.customer_id = customer.customer_id)

将生成您需要的信息,但会返回许多结果。例如,如果每个客户有 n 个订单,每个客户有 m 个地址,则会有 mxn 个结果。

因此,您还提到的方法是一种好方法。您所说的是从第一个查询中获取 customer_ids 并使用这些 id 来“生成”订单查询和地址查询。

基本上你需要做的是发出如下查询:

select * from customer
where ....

检索客户信息。那么

select * from order 
where customer_id in [The customer_ids found in the above query]

select * from customer_address 
where customer_id in [The customer_ids found in the above query]

您可以按照建议使用临时表,但表值参数会更有效。由于您使用的是 SQL Server 12,因此您可以使用表值参数。有关更多信息,请参阅以下链接:http://www.sommarskog.se/arrays-in-sql-2008.html

所有这些查询都应在一个事务中完成,您需要注意事务隔离级别,这会使问题进一步复杂化。

【讨论】:

我不是数据库专家,所以我认为我的问题一开始很常见并且有一个简单的解决方案,显然从多个表查询时存在真正的挑战。感谢您提及表值参数,我一定会考虑到这一点。【参考方案2】:

好的,首先...这对您来说会更容易,建议您使用 SQL 逻辑创建存储过程,例如 '[dbo].[GetOrderDetails]'.

原因是为了防止成为 SQL 注入的受害者,并防止在更新查询时重新编译和重新分发程序集。

这里有一些解释伪代码来指导你:

CREATE PROCEDURE [dbo].[OrderDetails] 
(
    @OrderDate DateTime,
    @AddressFilter VARCHAR(128)
)

AS

BEGIN

    IF(OBJECT_ID('tempdb..#OrderDetails') IS NOT NULL) DROP TABLE #OrderDetails

    SELECT orders.[*ListYourOrdersColumnsHere*], customer.[*ListYourCustomerColumnsHere*], addresses.[*ListYourAddressColumnsHere*]
    INTO #OrderDetails
    FROM [Order] orders
        INNER JOIN [Customer] customer on orders.[CustomerKey] = customer.[Id] 
        LEFT JOIN [CustomerAddresses] addresses ON addresses.[CustomerKey] = customer.[CustomerKey]
        -- Or inner join if you need orders just with CustomerAddresses
        --INNER JOIN [CustomerAddresses] addresses ON addresses.[CustomerKey] = customer.[CustomerKey]
    WHERE orders.[Date] > @OrderDate
    AND addresses.StreetName LIKE '%' + @AddressFilter + '%'

    -- Orders
    SELECT [*ListYourOrdersColumnsHere*]
    FROM #OrderDetails
    GROUP BY [*ListYourOrdersColumnsHere*]

    -- Customers
    SELECT [*ListYourCustomerColumnsHere*]
    FROM #OrderDetails
    GROUP BY [*ListYourCustomerColumnsHere*]

    -- Addresses
    SELECT [*ListYourAddressColumnsHere*]
    FROM #OrderDetails
    GROUP BY [*ListYourAddressColumnsHere*]

    DROP TABLE #OrderDetails
END

然后从解释环境转移到您的托管(编译)代码,您需要查看:

SqlDataReader:NextResult()

此方法将数据读取器推进到下一个结果集,如下面的下一个伪代码块所示。

SqlConnection myConnection = new SqlConnection("*YourAzureDbConnectionString*");
SqlCommand myCommand = new SqlCommand();
SqlDataReader myReader;

myCommand.CommandType = CommandType.StoredProcedure;
myCommand.Connection = myConnection;
myCommand.CommandText = "[dbo].[OrderDetails]";
myCommand.Parameters.Add(new SqlParameter("@OrderDate", DateTime.Now);
myCommand.Parameters.Add(new SqlParameter("@AddressFilter", "Some or other address filter"));

int resultSetCount = 0;

try

    myConnection.Open();
    myReader = myCommand.ExecuteReader();

    do
    
        resultSetCount++;
        while (myReader.Read())
        
            switch (resultSetCount)
            
                case 1:
                    // Populate Order information from 1st resultset
                    break;
                case 2:
                    // Populate Customer customer information from 2nd resultset
                    break;
                case 3:
                    // Populate CustomerAddress information from 3rd resultset
                    break;
                                    
        
    
    while (myReader.NextResult());

catch (Exception ex)

    // handle exception logic

finally

    myConnection.Close();

最后,这也可以使用 SqlDataAdapter 来完成,它将以 3 个单独的 DataTables 形式返回您的存储过程。 Here 是文档的链接。

希望这会有所帮助!

【讨论】:

感谢@Geewers 提供此解决方案。实际上 .NET 部分与我现在拥有的几乎完全相同 :)。至于 SQL 解决方案,我试过了,它肯定会按照我想要的方式返回数据,太棒了!您知道这种方法是否比仅将主键插入临时表/表值参数然后从原始表中选择记录更有效? (我认为您的解决方案更好,因为临时表中的行数远少于单个选择语句的原始表)。当然也使用存储过程;) @zaidsafadi,对索引列进行过滤总是最好的方法

以上是关于SQL Server - 从子-父-子中选择并返回多个结果集的主要内容,如果未能解决你的问题,请参考以下文章

mui 从子页面返回至父页面,同时刷新父页面

SQL Server:从子查询中调出列

在不关闭子窗口的情况下从子窗口返回焦点到父窗口(在mfc visual studio中)

将 vue.js 组件子中的对象或值发送到父通信

将 indexpath 行传回父视图

将父 POM 折叠到子中