在插入触发器中将范围值解析为逗号分隔的值数组

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,它采用startend 值来生成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

您需要将splitsingle 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的范围变成StartValueEndValue

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 的计算列。如果这是一个频繁使用或经常更新的表,请注意潜在的性能问题。

【讨论】:

你好年轻的鲍勃。不幸的是,它是一个封闭的系统(不是我的),无法访问源代码。所以我只需要通过插入触发器来做到这一点。

以上是关于在插入触发器中将范围值解析为逗号分隔的值数组的主要内容,如果未能解决你的问题,请参考以下文章

在Oracle中将连字符分隔的字符串拆分为行

在 Python 3 中将逗号分隔的字符串转换为 Numpy 数组

SQL Server 2005 中的逗号分隔值插入

如何在 Perl 中将列表输出为逗号分隔值?

如何在oracle中将值列表转换为逗号分隔值

在python中将空格分隔文件转换为逗号分隔值文件