在插入触发器中将范围值解析为逗号分隔的值数组
Posted
技术标签:
【中文标题】在插入触发器中将范围值解析为逗号分隔的值数组【英文标题】:Parsing range values to comma separated array of values in insert trigger 【发布时间】:2013-03-25 12:31:39 【问题描述】:我需要一些帮助来解析 SQL2008 服务器上的插入触发器之前的值。
我有一个包含文本字段的表(我们称之为源)。 字段值可能如下所示
10-15,20-22,25-26,
我想在另一个字段中使用逗号分隔值(比如说目标):
10,11,12,13,14,15,20,21,22,25,26,
这可以在插入触发器之前完成,还是我需要某种外部应用程序?
谢谢。
【问题讨论】:
当然,如果您编写自己的函数,这是可行的,但它有点奇怪 IMO,您能解释一下这个应用程序的逻辑,也许我们可以提出更好的方法 我已将我的原因解释为对 YoungBob 的评论。 【参考方案1】:首先您需要创建一个Table Valued function
,它采用start
和end
值来生成sequence
。这是使用recursive cte
完成的
CREATE FUNCTION FnGetRange(@startValue int,@endValue int)
RETURNS @rtnTable TABLE
(
generatedVal VARCHAR(MAX)
)
AS
BEGIN
;with cte(startValue,rangeVal,generatedVal)
as
(
Select @startValue,@endValue,@startValue as generatedVal
union all
Select startValue, rangeVal, generatedVal+1
from cte r
where rangeVal > generatedVal
)
Insert into @rtnTable
Select generatedVal from cte
return
END
您需要将split
和single column
转换为rows
以便您可以获取范围并将其传递给function
;with cte(range) as
(
SELECT
RIGHT(LEFT(T.rangeVal,Number-1),
CHARINDEX(',',REVERSE(LEFT(','+T.rangeVal,Number-1)))) as range
FROM
master..spt_values,
yourTable T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.rangeVal)+1
AND
(SUBSTRING(T.rangeVal,Number,1) = ',' OR SUBSTRING(T.rangeVal,Number,1) = '')
)
上面的解决方案发布在here,基本上是使用master..spt_values来生成序列
cte 会返回类似
的结果range
10-15
20-22
25-26
现在你需要把split
的范围变成StartValue
和EndValue
rangeCte (startValue,endValue) as
(
Select parsename(replace(range,'-','.'),2) as startValue,
parsename(replace(range,'-','.'),1) as endValue
from cte
)
上面的rangeCTE
会返回类似的数据
startValue endValue
10 15
20 22
25 26
获得这些值后,您只需使用 cross apply
将其传递给函数 FnGetRange
RowValue (rangeSep) as
( Select val.generatedVal as rangeSep from rangeCte r
CROSS APPLY
dbo.FnGetRange(r.StartValue,r.endValue) AS val
)
这将生成序列,但它将在多行中。要将其转换为单个 row
使用 xml path
SELECT STUFF(
(SELECT ',' + rangeSep
FROM RowValue
FOR XML PATH(''),type).value('.','varchar(max)'),1,1,'')
现在结合所有CTE's
最终查询是
;with cte(range) as
(
SELECT
RIGHT(LEFT(T.rangeVal,Number-1),
CHARINDEX(',',REVERSE(LEFT(','+T.rangeVal,Number-1)))) as range
FROM
master..spt_values,
yourTable T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.rangeVal)+1
AND
(SUBSTRING(T.rangeVal,Number,1) = ',' OR SUBSTRING(T.rangeVal,Number,1) = '')
),rangeCte (startValue,endValue) as
(
Select parsename(replace(range,'-','.'),2) as startValue,
parsename(replace(range,'-','.'),1) as endValue
from cte
),RowValue (rangeSep) as
( Select val.generatedVal as rangeSep from rangeCte r
CROSS APPLY
dbo.FnGetRange(r.StartValue,r.endValue) AS val
)
SELECT STUFF(
(SELECT ',' + rangeSep
FROM RowValue
FOR XML PATH(''),type).value('.','varchar(max)'),1,1,'')
结果将是
10,11,12,13,14,15,20,21,22,25,26
正如其他人所建议的那样,您应该认真更改table design
。而不是将其存储为string
创建columns
以存储range
类型的int
更新
只是为了在同一页上。您在包含 10-15,20-22,25-26
等值的源表上创建 Insert Trigger
。您需要将这些值转换为 sequence
并将其插入到 Target
表中。如果是这种情况,您可以使用以下代码。
基本上,触发器创建了Derived Table
,它从inserted
插入数据,这些数据是trigger中的逻辑表。然后使用上面的nested CTE's
,您将sequence
插入target
表中
create trigger tri_inserts on a
after insert
as
set nocount on
Declare @RangeTable table
(rangeVal varchar(max))
Insert into @RangeTable
Select rangeColumn from INSERTED
;with cte(range) as
(
SELECT
RIGHT(LEFT(T.rangeVal,Number-1),
CHARINDEX(',',REVERSE(LEFT(','+T.rangeVal,Number-1)))) as range
FROM
master..spt_values,
@RangeTable T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.rangeVal)+1
AND
(SUBSTRING(T.rangeVal,Number,1) = ',' OR SUBSTRING(T.rangeVal,Number,1) = '')
),rangeCte (startValue,endValue) as
(
Select parsename(replace(range,'-','.'),2) as startValue,
parsename(replace(range,'-','.'),1) as endValue
from cte
),RowValue (rangeSep) as
( Select val.generatedVal as rangeSep from rangeCte r
CROSS APPLY
dbo.FnGetRange(r.StartValue,r.endValue) AS val
)
Insert into Target(DestColumn) --Change the target name
SELECT STUFF(
(SELECT ',' + rangeSep
FROM RowValue
FOR XML PATH(''),type).value('.','varchar(max)'),1,1,'')
GO
【讨论】:
普拉文这是很棒的东西。既然你为此付出了巨大的努力(我非常感谢你),你能帮我把这段代码放在 AFTER INSERT 触发器中吗?假设 SOURCE 列保存原始值,而 TARGET 列应该保存逗号分隔值?我在理解第一个 CTE 中的 WHERE 子句时也遇到了一些问题(类型 = P...)master..spt_values
表中有 'A,B,D,P 等不同类型'。但是这些关联类型的数字没有任何顺序,但是对于Type = P
,我们从0 to 2047
开始以正确的顺序显示数字。但是我没有使用所有这些数字,而是只得到那些位于1 and length of the string value
之间的数字跨度>
这个 Where 子句 (SUBSTRING(T.rangeVal,Number,1) = ',' OR SUBSTRING(T.rangeVal,Number,1) = '')
实际上是在表达式遇到 - or ''
时获取数字。那是它读取10-15
的时候。遇到-
的那一刻,它从表达式-
中提取右边的数字
感谢大家的帮助!还有一件事......控制源字段中的逗号的是什么。让我们说一下“10-20,20-30”和“10-20,20-30”之间的区别(注意结尾的逗号)。
在RowValue
CTE 中将值传递给函数只需将代码从 r.endValue 替换为 replace(r.endValue,',','')
,因此函数调用将是 dbo.FnGetRange(r.StartValue,replace(r.endValue,',',''))
【参考方案2】:
我建议这些数字确实应该存储在单独的表中,例如Range (start int, end int) 带有指向您所引用的表的多 1 链接,这应该会使这样的查询变得更简单,并为您节省其他潜在的麻烦,但也许您有充分的理由这样做。在这种情况下,我建议创建一个 UDF 来生成 CSV 字符串并将该字段声明为引用 UDF 的计算列。如果这是一个频繁使用或经常更新的表,请注意潜在的性能问题。
【讨论】:
你好年轻的鲍勃。不幸的是,它是一个封闭的系统(不是我的),无法访问源代码。所以我只需要通过插入触发器来做到这一点。以上是关于在插入触发器中将范围值解析为逗号分隔的值数组的主要内容,如果未能解决你的问题,请参考以下文章