为 SQL 中的每个字符生成所有值组合和值集列表

Generate all combinations of values with set list of values for each character in SQL

我有一个看起来像这样的数据集

Position Value
1 1
1 2
1 3
2 8
3 5
3 6

我想生成字符串的所有组合以及每个位置的值。

对于这个例子,输出看起来像

Output
185
285
385
186
286
386

顺序并不特别重要。

注意:可以有任意数量的组和每个组的值。

下面SQL设置示例输入。

Declare @Table Table 
(
    groupId int, 
    value int
)

Insert Into @Table 
    Select 1,1
    union select 1,2
    union select 1,3
    union select 2,8
    union select 3,5
    union select 3,6

Select 
        * 
    From 
        @Table

由于您不知道组数,我想您需要使用动态 SQL。

例如:

DROP TABLE IF EXISTS #TestGroup;

CREATE TABLE #TestGroup
(
    GroupId int,
    Value int
);

INSERT INTO #TestGroup
VALUES
    (1, 1),
    (1, 2),
    (1, 3),
    (2, 8),
    (3, 5),
    (3, 6)
;

DECLARE @MaxGroup int;
SELECT @MaxGroup = MAX(GroupId) FROM #TestGroup;

DECLARE @sql nvarchar(max) = N'SELECT ';

DECLARE @i int = 1;
WHILE @i <= @MaxGroup
BEGIN
    If @i <> 1 SET @sql = @sql + N', ';
    SET @sql = @sql + FORMATMESSAGE(N'T%i.Value As C%i', @i, @i);
    SET @i = @i + 1;
END;

SET @sql = @sql + N' FROM ';
SET @i = 1;
WHILE @i <= @MaxGroup
BEGIN
    If @i <> 1 SET @sql = @sql + N' CROSS JOIN ';
    SET @sql = @sql + FORMATMESSAGE(' (SELECT Value FROM #TestGroup WHERE GroupId = %i) As T%i ', @i, @i);
    SET @i = @i + 1;
END;

PRINT @sql;
EXEC(@sql);

DROP TABLE IF EXISTS #TestGroup;

输出:

SELECT T1.Value As C1, T2.Value As C2, T3.Value As C3 
FROM  (SELECT Value FROM #TestGroup WHERE GroupId = 1) As T1  
CROSS JOIN  (SELECT Value FROM #TestGroup WHERE GroupId = 2) As T2  
CROSS JOIN  (SELECT Value FROM #TestGroup WHERE GroupId = 3) As T3 
C1 C2 C3
1 8 5
2 8 5
3 8 5
1 8 6
2 8 6
3 8 6

我认为这里真正的问题是你有任意数量的组。使用静态数量的组,您可以执行以下操作:

SELECT CONCAT(YT1.[Value],YT2.[Value],YT3.[Value])
FROM dbo.YourTable YT1
     JOIN dbo.YourTable YT2 ON YT1.Position < YT2.Position
     JOIN dbo.YourTable YT3 ON YT2.Position < YT3.Position;

因此,一种方法是使用动态解决方案。请注意,在没有详细说明您使用的 SQL 服务器版本的情况下,我 假设 您使用的是完全受支持的版本。我还假设你不能有差距。例如,如果 position 5 被省略,但 position 6 不是,那么您不希望 0 代表第 5 位(并且position 6 将改为第 5 位)。

这给了你这个,老实说,这很乱,但确实有用:

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
WITH DisctinctPositions AS(
    SELECT DISTINCT
           Position
    FROM dbo.YourTable),
Positions AS(
    SELECT Position,
           LAG(Position) OVER (ORDER BY Position) AS PrevPosition
    FROM DisctinctPositions)
SELECT @SQL = N'SELECT CONCAT(' + STRING_AGG(QUOTENAME(CONCAT(N'YT',P.Position)) + N'.[Value]',',') WITHIN GROUP (ORDER BY P.Position) + N')' + @CRLF +
              N'FROM dbo.YourTable YT1' + @CRLF +
              STRING_AGG(CASE P.Position WHEN 1 THEN NULL ELSE N'     JOIN dbo.YourTable' + QUOTENAME(CONCAT(N'YT',P.Position)) + N' ON ' + QUOTENAME(CONCAT(N'YT',P.PrevPosition)) + N'.Position < ' + QUOTENAME(CONCAT(N'YT',P.Position)) + N'.Position' END, @CRLF) WITHIN GROUP (ORDER BY P.Position)
FROM Positions P;

--PRINT @SQL; Your best friend

EXEC sys.sp_executesql @SQL;

您可以为此使用递归 CTE,不需要动态 SQL

WITH cte AS (
    SELECT
      1 AS Position,
      CAST(Value AS varchar(100)) AS Value
    FROM #YourTable t
    WHERE Position = 1
    
    UNION ALL
    
    SELECT
      cte.Position + 1,
      CAST(cte.Value + t.Value AS varchar(100))
    FROM #YourTable t
    JOIN cte ON cte.Position + 1 = t.Position
)
SELECT *
FROM cte
WHERE cte.Position = (SELECT TOP 1 t2.Position FROM #YourTable t2 ORDER BY t2.Position DESC);

SQL Fiddle

如果存在差距,那么您需要更复杂的解决方案:

CREATE FUNCTION dbo.GetNextValues (@gtThanPosition int)
RETURNS TABLE AS RETURN

SELECT TOP (1) WITH TIES
  t.Position,
  t.Value
FROM YourTable t
WHERE t.Position > @gtThanPosition OR @gtThanPosition IS NULL
ORDER BY t.Position;

GO
WITH cte AS (
    SELECT
      t.Position,
      CAST(Value AS varchar(100)) AS Value
    FROM dbo.GetNextValues(NULL) t

    UNION ALL
    
    SELECT
      t.Position,
      CAST(cte.Value + t.Value AS varchar(100)) AS Value
    FROM cte
    CROSS APPLY dbo.GetNextValues(cte.Position) t
)
SELECT Value
FROM cte
WHERE cte.Position = (SELECT TOP 1 t2.Position FROM YourTable t2 ORDER BY t2.Position DESC);

SQL Fiddle

你可以使用递归cte:

with cte(c, s) as (
  select 2, cast(value AS varchar(100)) from vals where position = 1
  union all
  select c.c+1, cast(concat(c.s,v.value) as varchar(100)) from cte c join vals v on c.c = v.position
)
select c.s from cte c where c.c = (select top 1 v1.position from vals v1 order by v1.position desc) + 1