【问题标题】:Generate all combinations of values with set list of values for each character in SQL使用 SQL 中每个字符的一组值生成所有值组合
【发布时间】:2021-12-01 13:33:47
【问题描述】:

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

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 server 正在使用 sintax CROSS JOIN
  • @James 交叉连接的问题是(据我所知)它不会动态处理任意数量的组。我必须为每个组编写一个交叉连接。
  • 这是个好问题。我的看法——我不是 100% 确定——这不能通过单个查询来完成。也许它可以只用非常有限的组来完成。无论如何,由于解决方案需要迭代,因此使用自定义函数似乎非常简单。看起来并不令人生畏。
  • 你的 SQL Server 版本是多少?
  • @ub_coding 18.6

标签: sql sql-server combinations


【解决方案1】:

您可以为此使用递归 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

【讨论】:

    【解决方案2】:

    由于您不知道组的数量,我怀疑您需要使用动态 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

    【讨论】:

      【解决方案3】:

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

      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 Server 版本的详细信息,我假设您使用的是完全受支持的版本。我也假设你不能有差距。例如,如果 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?
      • 老实说,我什至没有想到,@Charlieface。我并没有说你不能,只是这是一种方法。
      【解决方案4】:

      你可以使用递归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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-23
        • 2022-11-02
        • 1970-01-01
        • 2022-11-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多