T-SQL 将存在于另一个字段中的连字符分隔值的平均值放入一个字段中

Posted

技术标签:

【中文标题】T-SQL 将存在于另一个字段中的连字符分隔值的平均值放入一个字段中【英文标题】:T-SQL Put in a field the average of hyphen separated values present in another field 【发布时间】:2017-02-07 13:58:17 【问题描述】:

我有一张这样的桌子:

ID  |  FIX_1 | FTO   | FIX_2                      |
_____________________________________________________
1   |        | 15452 |1.3-1.7-1.8-2.4-2.0         |
2   |        | 15454 |1.4-1.1-1.4-2.7-2.6-1.8-2.4 | 
3   |        | 15454 |1.9-1.3-1.3                 | 
....  ......   ....    .................
....  ......   ....    .................
100 |        | 15552 |0.4-1.7-1.2-2.1-2.6-1.6     |

我需要选择 FIX_1 字段等于 FIX_2 字段中连字符分隔值的平均值。 是否可以在不使用临时表的情况下使用 T-SQL? 提前致谢

【问题讨论】:

你能显示你的预期输出吗 为什么没有临时表?如果您没有临时表,我假设您无法创建函数? FIX_1 字段输出必须是:1.8 用于 id = 1 的记录,1.9 用于 id = 2 的记录,1.5 用于 id = 3 的记录和 1.6 用于 id = 100 的记录 这个表格违反了第一范式。 为什么您不将分隔值存储在另一个表中?无论如何,SQL Server 2016 提供了STRING_SPLIT 方法来通过分隔符分割字符串并为每个结果返回一行 您想要解决的真正问题是什么?即时计算平均值并进行比较会缓慢 - 服务器必须扫描整个表。使用适当的索引将值正确存储在单独的表中会更快 - 即使服务器必须计算所有平均值,它也可以使用FIX_1 上的索引来查找匹配但是... 【参考方案1】:

带有 UDF 的选项

Declare @YourTable table (ID int,FIX_1 money,FTO int,FIX_2 varchar(max))
Insert Into @YourTable values
(1,null,15452,'1.3-1.7-1.8-2.4-2.0'),
(2,null,15454,'1.4-1.1-1.4-2.7-2.6-1.8-2.4'),
(3,null,15454,'1.9-1.3-1.3')

Update @YourTable Set FIX_1=B.Value
 From  @YourTable A
 Cross Apply (
                Select Value = Avg(cast(RetVal as money))
                 From (Select * from [dbo].[udf-Str-Parse](A.FIX_2,'-')) B1
             ) B

Select * From @YourTable

没有 UDF 的选项

Update @YourTable Set FIX_1=B.Value
 From  @YourTable A
 Cross Apply (
                Select Value = Avg(cast(RetVal as money))
                 From (
                        Select RetSeq = Row_Number() over (Order By (Select null))
                              ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
                        From  (Select x = Cast('<x>' + replace((Select replace(A.FIX_2,'-','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
                        Cross Apply x.nodes('x') AS B(i)
                      ) B1
             ) B

两者都会回归

ID  FIX_1   FTO     FIX_2
1   1.84    15452   1.3-1.7-1.8-2.4-2.0
2   1.9142  15454   1.4-1.1-1.4-2.7-2.6-1.8-2.4
3   1.50    15454   1.9-1.3-1.3

UDF(如果需要)

CREATE FUNCTION [dbo].[udf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (  
    Select RetSeq = Row_Number() over (Order By (Select null))
          ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
    From  (Select x = Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
    Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')

【讨论】:

您是在引用来自@Shnugo 的帖子或答案,如果是这样,我想这需要适当的归属?!如果不是没问题。 @Tanner 实际上,这是我的解析实用程序之一,但 Shnugo 在另一篇文章中建议了 XML 安全解析(我对此有所了解)。这对他来说是一个提示。 没问题,看起来像是在尝试归因但不清楚 @Tanner 这是实际链接***.com/questions/41999151/…【参考方案2】:

具有用户定义的功能...

create FUNCTION dbo.AvgOfDashSepVals ( @vals varchar(500))
returns float as
BEGIN
declare @avg decimal
declare @cnt int = 0
declare @sum float = 0.0
While charIndex('-', @vals) > 0 Begin
      if isnumeric(left(@vals, charIndex('-', @vals)-1)) = 0 
         return null          
    set @cnt+= 1
    set @sum += cast(left(@vals, charIndex('-', @vals)-1) as float)
    set @vals = substring(@vals, charIndex('-', @vals)+1, len(@vals))
End
RETURN case @cnt when 0 then null else @sum / @cnt end

然后更改您的表以添加计算列。

alter table myTable
   add Fix_1 as ([dbo].[AvgOfDashSepVals]([Fix_2]))

【讨论】:

干得好!我已经创建了一个类似于您的 udf 的函数,但是当我尝试选择 dbo.AvgOfDashSepVals([FIX_2]) 时,我收到消息“除以零错误遇到”。你知道为什么吗?谢谢 可能是因为它在一个空字符串上运行 (@cnt = 0) 我添加了代码来处理它。 太棒了!这几乎是完美的,但是当在 FIX_2 字段中只有一个值时,函数返回 NULL。此外,是否可以从标量结果的小数点后的第三位截断? 好的,我在 RETURN 语句中将 null 替换为 @vals,但有些返回值不正确。例如 FIX_2 = 1.6-1.6-1.8-1.8-2.0-2.0-2.3-2.3 给出 1.87142857142857 而不是 1.925。你知道为什么吗? 字符串中只有一个值时的错误。修复它......现在应该可以工作(我们不是总是这么说吗?)【参考方案3】:
Declare @YourTable table (ID int,FIX_1 money,FTO int,FIX_2 varchar(max))
    Insert Into @YourTable values
    (1,null,15452,'1.3-1.7-1.8-2.4-2.0'),
    (2,null,15454,'1.4-1.1-1.4-2.7-2.6-1.8-2.4'),
    (3,null,15454,'1.9-1.3-1.3'),
    (4,null,15454,'1.5')

;WITH cte AS
(
    SELECT ID, SUBSTRING(FIX_2, 1, CHARINDEX('-',FIX_2) - 1) AS VALUE, SUBSTRING(FIX_2, CHARINDEX('-',FIX_2) + 1, LEN(FIX_2)) AS NEW_FIX_2
    FROM @YourTable
    WHERE CHARINDEX('-',FIX_2) > 0
    UNION ALL
    SELECT cte.ID, SUBSTRING(NEW_FIX_2, 1, CHARINDEX('-',NEW_FIX_2) - 1) AS VALUE, SUBSTRING(NEW_FIX_2, CHARINDEX('-',NEW_FIX_2) + 1, LEN(NEW_FIX_2)) AS NEW_FIX_2
    FROM @YourTable y
        JOIN cte ON cte.ID = y.ID
    WHERE CHARINDEX('-', NEW_FIX_2) > 0
    UNION ALL
    SELECT ID, NEW_FIX_2, NULL 
    FROM cte
    WHERE CHARINDEX('-', NEW_FIX_2) = 0
)
SELECT t.ID, ISNULL(v.VALUE, t.FIX_2) AS FIX_1, t.FTO, t.FIX_2
FROM @YourTable t
    LEFT JOIN (
        SELECT cte.ID, AVG(CAST(cte.VALUE AS MONEY)) AS VALUE
        FROM cte
        GROUP BY cte.ID
) v ON v.ID = t.ID

【讨论】:

以上是关于T-SQL 将存在于另一个字段中的连字符分隔值的平均值放入一个字段中的主要内容,如果未能解决你的问题,请参考以下文章

使用 T-SQL,从字符串中返回第 n 个分隔元素

检查Table1中的字段组合是不是存在于另一个Table2中(SQL)

选择一个数组字段中的所有值都存在于另一个数组中的文档

如何使用 linq 检测一个集合字段是不是存在于另一个列表中?

T-SQL基于分隔符拆分列并将拆分后的字符串数组输入到多个表列中

接受多个 Id 值的 T-SQL 存储过程