在T-SQL存储过程中,当提供两个表,每个表具有相同的行数时,如何根据行顺序而不是连接条件成对匹配行?
基本上,相当于.NET的IEnumerable.Zip()方法?
我正在使用SQL Server 2016。
背景
存储过程的目的是充当两个其他应用程序之间的集成适配器。我不控制任何一个应用程序的源代码。
"客户"应用程序包含可以配置的可扩展性对象,以调用SQL Server数据库中的存储过程。可扩展点的配置选项允许我命名将被调用的存储过程,并提供静态配置的命名参数列表及其关联值,这些值将传递给存储过程。仅支持标量参数,而不支持表值参数。
存储过程需要从"服务器"收集数据。应用程序(通过OLE-DB提供程序公开)并将其转换为适合客户端应用程序使用的结果集。
出于维护原因,我希望避免在适配器数据库中存储任何配置。我想在存储过程中编写通用的,灵活的逻辑,并将所有必要的配置信息作为参数传递给该存储过程。
存储过程所需的配置信息基本上等同于下表变量模式:
DECLARE @TableOfServerQueryParameterValues AS TABLE (
tag NVARCHAR(50),
filterexpr NVARCHAR(500)
)
然后,此表可用作存储过程中JOIN
和CROSS APPLY
查询的左侧,这些查询针对"服务器"应用程序接口。
我遇到的问题是我不知道从客户端应用程序传递参数信息表的任何方法,因为它的可扩展点仅包括标量参数支持。
所以,我以为我会传递两个标量参数。一个是逗号分隔的tag
值列表。另一个是逗号分隔的filterexpr
值列表。
在存储过程中,使用STRING_SPLIT
可以很容易地将每个参数转换为单列表。但后来我需要将两列匹配到一个两列表中,然后我可以将其用作INNER JOIN
或CROSS APPLY
查询服务器应用程序的基础。
我到目前为止提出的最佳解决方案是将每个表选择为表变量并使用ROW_NUMBER()函数分配行号,然后通过匹配额外的ROW_NUMBER将两个表连接在一起柱。有没有比这更简单的方法呢?不必声明表变量中的所有列会很好。
答案 0 :(得分:1)
您使用row_number
的建议似乎很合理。
您可以使用子查询或CTE代替表变量;尽管避免使用表变量会减少你需要制作的传球次数,但整体上应该没什么差别。避免额外的代码维护。
select a.*, b.* --specify whatever columns you want to return
from (
select *
, row_number() over (order by someArbitraryColumnPreferablyYourClusteredIndex) r
from TableA
) a
full outer join --use a full outer if your have different numbers of rows in the tables & want
--results from the larger table with nulls from the smaller for the bonus rows
--otherwise use an inner join to only get matches for both tables
(
select *
, row_number() over (order by someArbitraryColumnPreferablyYourClusteredIndex) r
from TableA
) b
on b.r = a.r
<强>更新强>
关于@ PanagiotisKanavos关于传递结构化数据的评论,这里有一个简单的例子,说明如何将作为xml类型传递的值转换为表数据:
declare @tableA xml = '<TableA>
<row><col1>x</col1><col2>Anne</col2><col3>Droid</col3></row>
<row><col1>y</col1><col2>Si</col2><col3>Borg</col3></row>
<row><col1>z</col1><col2>Roe</col2><col3>Bott</col3></row>
</TableA>'
select row_number() over (order by aRow) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from @tableA.nodes('/*/*') x(aRow)
使用以下内容可以提升性能。这会创建一个虚拟列,允许我们执行order by
,我们不关心顺序。这应该比上面更快,因为按1排序比基于xml类型的排序更简单。
select row_number() over (order by ignoreMe) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from @tableA.nodes('/*/*') x(aRow)
cross join (select 1) a(ignoreMe)
如果您关心订单,可以按数据字段订购,如下:
select row_number() over (order by x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') ) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from @tableA.nodes('/*/*') x(aRow)