如何对多行使用交叉应用?
How can I use Cross Apply to multiple rows?
我有这个 table :
;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
次
问题
我怎样才能增强我的查询来做到这一点?
注:
我想到的一个非智能解决方案是创建一个 udf,它为 n
参数创建 Times
行 - 然后在 Cross Apply
中我只是这样做:select * 来自 udf_toTable(Times)
)
;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
您不需要使用交叉应用。
使用递归 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
我已经成功做到了:(因为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
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;
我能想到的唯一缺点是你必须手动输入数字,但这可以很容易地用数字 Table/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;
我在评论中还注意到,您 运行 进入了 sys.objects 的极限,并且有人 post 编写了精彩的 link 来生成序列。我的示例中使用的 RangeSmallInt 函数基于 post 并且非常高效。这是代码:
-- 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
);
要与 CROSS APPLY
一起使用,您可以创建一个 table 值函数,如下所示:
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)
我有这个 table :
;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
次
问题
我怎样才能增强我的查询来做到这一点?
注:
我想到的一个非智能解决方案是创建一个 udf,它为 n
参数创建 Times
行 - 然后在 Cross Apply
中我只是这样做:select * 来自 udf_toTable(Times)
)
;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
您不需要使用交叉应用。 使用递归 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
我已经成功做到了:(因为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
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;
我能想到的唯一缺点是你必须手动输入数字,但这可以很容易地用数字 Table/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;
我在评论中还注意到,您 运行 进入了 sys.objects 的极限,并且有人 post 编写了精彩的 link 来生成序列。我的示例中使用的 RangeSmallInt 函数基于 post 并且非常高效。这是代码:
-- 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
);
要与 CROSS APPLY
一起使用,您可以创建一个 table 值函数,如下所示:
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)