我正在尝试将带有分隔字符串的两列拆分为行。每个字符串中值的位置是相关的,因此我试图将其拆分,以便相关值在一行中。我无法使用函数,因为我无法在数据库中创建对象
这是示例表和数据
CREATE TABLE #temp
(id INT,
keys VARCHAR(50),
vals VARCHAR(50)
);
INSERT INTO #temp
VALUES
(1, '1,2,3', 'one,two,three'),
(2, '4,5,6', 'four,five,six'),
(3, '7,8,9', 'seven,eight,nine');
我想要的输出是
ID key val
1 1 one
1 2 two
1 3 three
2 4 four
2 5 five
2 6 six
3 7 seven
3 8 eight
3 9 nine
如果我只拆分一列,我的查询就可以工作,所以我用 row_number 定义了两个 CTE,并在 ID 和 row_number 上加入。这确实提供了所需的输出,但我的实时表非常大,我希望有一种方法可以只通过表一次,而不是两次。
with keys as(
SELECT id,keys,vals,
keys.keyid.value('.', 'VARCHAR(8000)') AS keyid,
row_number() over(order by (select null)) as rn
FROM
(SELECT id,keys,vals,
CAST('<Keys><key>'+REPLACE(keys, ',', '</key><key>')+'</key></Keys>' AS XML) AS tempkeys
FROM #temp
) AS temp
CROSS APPLY tempkeys.nodes('/Keys/key') AS keys(keyid)),
vals as(
SELECT id,keys,vals,
vals.val.value('.', 'VARCHAR(8000)') AS valid,
row_number() over(order by (select null)) as rn
FROM
(SELECT id,keys,vals,
CAST('<vals><val>'+REPLACE(vals, ',', '</val><val>')+'</val></vals>' AS XML) AS tempvals
FROM #temp
) AS temp
CROSS APPLY tempvals.nodes('/vals/val') AS vals(val))
SELECT k.id, k.keyid, v.valid
FROM keys AS k
INNER JOIN vals AS v
ON k.id = v.id
AND k.rn = v.rn;
msdb
在其他地方或其他地方创建函数。然后,正如@gbn 所指出的,在您的查询必须运行的任何地方通过 3 部分名称引用它。
结果:
计划资源管理器中显示的最终计划(免责声明:我是产品经理)不是我见过的最漂亮的东西(点击放大一点):
但是只有一次扫描#temp(4% 的成本)。最大的成本是两种和一个线轴,并且由于工作台而存在一些 I/O,我不确定这是否可以避免。
如果您知道这些字符串中的任何一个都只能有 50 个字符,那么您可以使用内置
Numbers
表格获得一个更简单的计划(人们反对这些,但它们非常有用,而且它们几乎总是在内存中如果你足够引用它们)。这对 I/O 没有帮助,但删除递归 CTE 和其他在函数内部构建数字的结构对 CPU 等非常有帮助。一、数字表:
然后是函数的第二个版本:
这是产生的更简单的计划(再次点击放大):
该计划仍然有两个排序操作,但线轴已经消失,仍然只有一次扫描
#temp
,并且在我的有限测试中,成本数字(绝对成本数字,而不是 %)每次都更好。我不确切知道这些中的任何一个都会扩展更多的行,但值得测试,如果您将其与其他解决方案进行权衡并且它不能很好地扩展,那么您可能需要重新考虑设计(存储这些关系而不是逗号分隔的集合)。
我用大量的行和四个列表列遇到了同样的问题。
以前的解决方案不适合我。
@AaronBertrand 的解决方案存在列表中元素数量不同的问题。该问题可以通过在 ROW_NUMBER 上添加分区来解决:
但是,由于我有大量的行和元素,仍然不适合我。
我创建了以下脚本来解决我的问题而不使用函数:
结果:
查询计划:
如您所见,查询计划非常简单,在#temp 上只有一次表扫描。
该解决方案也具有很强的可扩展性。