如何对多行使用交叉应用?

Posted

技术标签:

【中文标题】如何对多行使用交叉应用?【英文标题】:How can I use Cross Apply to multiple rows? 【发布时间】:2015-05-19 11:48:53 【问题描述】:

我有这张桌子:

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' ,2
    UNION ALL
    SELECT 'george' , 3
    UNION ALL
    SELECT 'ringo' , 1
)

我想显示每一行,Times 次:

John 1
Paul 2
Paul 2
george 3
george 3
george 3
ringo 1

所以我知道如果我写Cross apply 就像:

SELECT *
FROM   cte
       CROSS APPLY(
        SELECT 1 AS ca
        UNION 
        SELECT 2
       ) y

然后每行显示2次。

但我不想要 2 次。我要Times

问题

如何增强我的查询来做到这一点?

nb:

我想到的一个非智能解决方案是创建一个 udf,它为 n 参数创建 Times 行 - 然后在 Cross Apply 我只是这样做:从 udf_toTable(Times) 中选择 *)

【问题讨论】:

【参考方案1】:
;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' , Times=2
    UNION ALL
    SELECT 'george' , Times=3
    UNION ALL
    SELECT 'ringo' , Times=1
),
multi as
(
    select 
        Name, Times, Times as num
    from cte
    union all
    select 
        Name, Times, num - 1
    from multi 
    where num > 1
)
select Name, Times from multi
order by Name

更新

没有递归

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' , Times=2
    UNION ALL
    SELECT 'george' , Times=3
    UNION ALL
    SELECT 'ringo' , Times=1
)
select cte.*
from cte join 
    -- generate sequence of numbers 1,2 ... MAX(Times)
    (select top (select MAX(Times) from cte) ROW_NUMBER() over (order by object_id) rowNum from sys.objects) t
on cte.Times >= t.rowNum 
order by name

【讨论】:

recursive... 很好 :-) 但我希望交叉应用来创建 Times 行...有可能吗? 查看我的解决方案(如果您可以帮助解决 sys.objects 长度限制 - 我会很高兴) @RoyiNamir,作为一种可能的变体,将sys.objects 与自身交叉连接;另见关于生成数字序列的文章:sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1 另外sys.all_objects 是一个更大的集合【参考方案2】:

您不需要使用交叉应用。 使用递归 CTE:

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' , Times=2
    UNION ALL
    SELECT 'george' , Times=3
    UNION ALL
    SELECT 'ringo' , Times=1
)
, res as (
select Name, 1 RowNum
from cte
union all
select cte.Name, res.RowNum+1
from cte
  join res on cte.Name=res.Name
where res.RowNum+1<=cte.Times
)
select res.*, cte.Times
from res
  join cte on cte.Name=res.Name
order by 1, 2

更新 另一个动态最大值。

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' , Times=2
    UNION ALL
    SELECT 'george' , Times=3
    UNION ALL
    SELECT 'ringo' , Times=1
), times
AS
(
  select 1 n, MAX(cte.Times) Times
  from cte
  union all
  select t.n+1, t.Times
  from times t
  where t.n+1<=t.Times
)
SELECT 
  c.*
FROM CTE AS c
INNER JOIN times AS t ON c.Times >= t.n
order by 1, 2

【讨论】:

【参考方案3】:
SELECT A.Name, A.Times
FROM (VALUES
    ('John', 1)
  , ('Paul', 2)
  , ('George', 3)
  , ('Ringo', 1)
) A (Name, Times)
CROSS APPLY(VALUES
    (1), (2), (3)
) B (n)
WHERE A.Times >= B.n
ORDER BY A.Name;

我能想到的唯一缺点是您必须手动输入数字,但这可以通过数字表/TVF 轻松解决:

SELECT A.Name, A.Times
FROM (VALUES
    ('John', 1)
  , ('Paul', 2)
  , ('George', 3)
  , ('Ringo', 1)
) A (Name, Times)
CROSS APPLY dbo.RangeSmallInt(1, A.Times) B;

我还在 cmets 中注意到您遇到了 sys.objects 的限制,并且有人发布了生成序列的精彩链接。我的示例中使用的 RangeSmallInt 函数基于该帖子,并且非常高效。代码如下:

-- Generate a range of up to 65,536 contiguous BIGINTS
CREATE FUNCTION dbo.RangeSmallInt (
    @n1 BIGINT = NULL
  , @n2 BIGINT = NULL
)
RETURNS TABLE
AS
RETURN (
    WITH Numbers AS (
        SELECT N FROM(VALUES
            (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
        ) V (N)
    )    
    SELECT TOP (
               CASE
                   WHEN @n1 IS NOT NULL AND @n2 IS NOT NULL THEN ABS(@n2 - @n1) + 1
                   ELSE 0
               END
           )
           N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 + CASE WHEN @n1 <= @n2 THEN @n1 ELSE @n2 END
    FROM Numbers A, Numbers B
    WHERE ABS(@n2 - @n1) + 1 < 65537
);

扩展它以支持大小 INT:

-- Generate a range of up to 4,294,967,296 contiguous BIGINTS
CREATE FUNCTION dbo.RangeInt (
    @num1 BIGINT = NULL
  , @num2 BIGINT = NULL
)
RETURNS TABLE
AS
RETURN (
    WITH Numbers(N) AS (
        SELECT N
        FROM dbo.RangeSmallInt(0, 65535)
    )
    SELECT TOP (
               CASE
                   WHEN @num1 IS NOT NULL AND @num2 IS NOT NULL THEN ABS(@num1 - @num2) + 1
                   ELSE 0
               END
           )
           N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
    FROM Numbers A
       , Numbers B 
    WHERE ABS(@num1 - @num2) + 1 < 4294967297
);

【讨论】:

【参考方案4】:

我已经做到了:(仍然因为 sys.objects 而脸色难过)

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' ,2
    UNION ALL
    SELECT 'george' , 3
    UNION ALL
    SELECT 'ringo' , 1
)  

SELECT *
FROM   cte
       CROSS APPLY(
                select top (cte.Times) 'bla'=1 from sys.objects
       ) y

更新,查看答案后:这是一个将它们与 CROSS APPLY 一起使用的解决方案:

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' ,2
    UNION ALL
    SELECT 'george' , 3
    UNION ALL
    SELECT 'ringo' , 1
)   ,  times
AS
(
  select 1 n, MAX(cte.Times) Times
  from cte
  union all
  select t.n+1, t.Times
  from times t
  where t.n+1<=t.Times
)

SELECT *
FROM   cte
       CROSS APPLY(  
                select top (cte.Times) n from times
       ) y

【讨论】:

【参考方案5】:

要与CROSS APPLY 一起使用,您可以像这样创建一个表值函数:

CREATE FUNCTION FN_MULTIPLY_ROW (@ROWS INT) RETURNS TABLE AS
RETURN
WITH A AS (
        SELECT ROW_LOOP = 1
    UNION ALL
        SELECT ROW_LOOP + 1
        FROM A
        WHERE ROW_LOOP < @ROWS
    )
SELECT ROW_LOOP FROM A

只需简单地使用交叉应用:

;WITH cte AS (
    SELECT Name='john' , Times=1    
    UNION ALL
    SELECT 'paul' ,2
    UNION ALL
    SELECT 'george' , 3
    UNION ALL
    SELECT 'ringo' , 1
)
SELECT * --> You have the "ROW_LOOP" here if you want to know the number exactly
FROM CTE
CROSS APPLY FN_MULTIPLY_ROW(TIMES)

另外:如果循环大于 100,则需要添加 MAXRECURSION 选项,请谨慎使用

...
CROSS APPLY FN_MULTIPLY_ROW(TIMES)
OPTION (MAXRECURSION 0)

【讨论】:

以上是关于如何对多行使用交叉应用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Word文档中使用内容交叉引用

如何将df.loc []应用于多行并应用转换?

如何使用 iOS Swift SKOverlay 和 UIKit 来交叉推广另一个应用程序?

如何使用交叉应用字符串拆分结果更新sql中的表?

加快交叉应用查询

如何使用带有交叉应用的 SQL OPENJSON 函数测量 json 文件中数组内数组的长度