【问题标题】:Table valued function (TVF) on a comma-separated field逗号分隔字段上的表值函数 (TVF)
【发布时间】:2021-03-28 17:52:47
【问题描述】:

我有这张桌子:

create table Test (Value varchar(111))
insert Test select 'a,b,c'

我想创建一个表值函数,我通过 Test.Value 并返回下表:

其中Value 来自Test 表,而Item 值以这种方式生成:第一项本身就是整个值(例如,它由三个逗号分隔的值组成),一旦没有任何三个逗号分隔的值,我们从左到右查找两个逗号分隔的值。

我们严格按照从左到右的顺序进行,因此不需要像 a,cb,a 这样的项目。然后我们最终得到一个逗号分隔的值,即abc

ItemLayer 只是一个正在处理的层。显然,分隔符应该是逗号,tvf 应该返回ItemItemLayer。我认为查询应该是这样的:

SELECT *
FROM Test t
CROSS JOIN fn_getItemsFromValues(t.Value) f

我认为应该有某种递归 CTE,但我不知道如何。

如果Value'a,b,c,d',这是一个输出:

我正在使用 SQL Server 2017。我已经尝试过了,但我被卡住了。这里有些不对劲。

DECLARE @data VARCHAR(100) = 'a,b,c'
;WITH CTE AS
(
    SELECT @data TXT, LEFT(@data,1) Col1
    UNION ALL
    SELECT STUFF(TXT,1,1,'') TXT, LEFT(TXT,1) Col1 FROM CTE
    WHERE LEN(TXT) > 0
)
select Col1,txt from CTE

【问题讨论】:

  • 请将您的问题编辑为:1) 指定您正在使用的 SQL Server 版本(在这种情况下很重要)和 2) 显示您迄今为止编写的 UDF 代码并解释它为什么不起作用给你。
  • @AlwaysLearning ,完成
  • @EricKlaus 。 . .为什么没有'a,c' 的组合?
  • @GordonLinoff ,我不需要。我需要严格地从左到右走,没有任何跳跃。在 a,c 我们跳过 b,这就是我们儿子没有它的原因

标签: sql sql-server tsql common-table-expression


【解决方案1】:

我不确定为什么a,c 不在列表中。但是您可以通过拆分字符串然后使用递归 CTE 来生成所有组合:

with t as (
      select t.value, convert(varchar(max), s.value) as val
      from test t cross apply
           string_split(t.value, ',') s
     ),
     cte as (
      select t.value, t.val as str, t.val as lastval, 1 as lev
      from t
      union all
      select cte.value, concat(cte.str, ',', t.val), t.val, lev + 1
      from cte join
           t
           on cte.value = t.value and cte.lastval < t.val
     )
select cte.*, dense_rank() over (order by lev desc) as itemlayer
from cte;

Here 是一个 dbfiddle。

编辑:

我认为解决“相邻”限制的最佳方法实际上是对以前的解决方案进行调整。这为元素添加了一个定位器,并且只允许在递归步骤中引入下一个元素:

with t as (
      select t.value, convert(varchar(max), s.value) as val,
             row_number() over (order by charindex(',' + s.value + ',', ',' + t.value + ',')) as ind
      from test t cross apply
           string_split(t.value, ',') s
     ),
     cte as (
      select t.value, t.val as str, t.ind, 1 as lev
      from t
      union all
      select cte.value, concat(cte.str, ',', t.val), t.ind, lev + 1
      from cte join
           t
           on cte.value = t.value and t.ind = cte.ind + 1
     )
 select cte.*, dense_rank() over (order by lev desc) as itemlayer
from cte;

如果您有重复的元素,这将不起作用。如果是这种情况,请提出一个新问题。

【讨论】:

  • 非常接近的东西。我不需要 a,c,因为其中的两个项目并不相邻(它跳过 b)。
  • 好的,这仅在值字符串为 1 个字符时有效,但如果它有 'a,bz,c' 则失败。我还有一件事可能会有所帮助。我没有使用 string_split,而是使用另一个类似的 tvf (fn_splitwithorder(t.Value,',')),它返回 2 个字段:Value,ValueOrder,其中ValueOrder 是出现的顺序,也就是 a 具有 ValueOrder 1、b-2、c-3。我不确定如何将其合并到上面的脚本中,但我认为它可能会有所帮助
  • @EricKlaus 。 . .我用更好的方法编辑了这个问题。
  • 非常感谢,我可能会花一天时间来完全了解它是如何工作的。我知道我应该使用递归 CTE,但我的大脑只能做一些简单的事情
  • 什么是“空调”?您的意思是“a,c”(不是反问句)吗?
【解决方案2】:

这是我得到的:

drop function if exists fn_getItemsFromValues 
go
create function fn_getItemsFromValues (@val varchar(100))
returns @ret table
(
[value] varchar (100)
,item varchar (100)
,ItemLayer int
) 
as
begin
    declare
        @v varchar (1000)=@val
        ,@i int=1
        ,@J int=1
        ,@v_n varchar (1000)=''

    set @v=@val
    declare
        @t table (val varchar (1000))


    while @i<=len(@v)
    begin
        set @j=1
        while @i+@J-1<=len(@v)
        begin
            set @v_n=substring (@v, @i, @j)
            if len(@v_n)>1
                set @v_n=isnull(nullif(LEFT(@v_n,1),',') ,'') +substring (@v_n,2, len(@v_n)-2)+isnull(nullif(right(@v_n,1),',') ,'')

            insert into @t
            select @v_n where @v_n!=','
            set @j=@j+1
        end
    set @i=@i+1
    end

    insert into @ret
    select @v, val, count(ln) over (partition by ln) 
    from (
        select distinct val, len(val) ln from @t
        )t
    return
end

--select * from  dbo.fn_getItemsFromValues ('a,b,c,d') order by 3 

【讨论】:

  • 解释一下。
猜你喜欢
  • 1970-01-01
  • 2010-10-18
  • 1970-01-01
  • 2013-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
相关资源
最近更新 更多