列出具有相同总和的数字组合

时间:2019-01-16 12:07:57

标签: sql sql-server

**N** is a prositive number

需要清单的总和等于N

例如,如果N=4

ScenarioId     Value
----------     -----
1              1
1              1
1              1
1              1
2              2
2              1
2              1
3              2
3              2
4              3
4              1
5              4

以上为必填项。如果按ScenarioId求和,则所有和必须等于N

更新

这是我自己的解决方案。但是,我不确定两个不同数字集的乘积在任何时候都不相等。

我当前的问题是

a + b + c = d + e + fa * b * c = d * e * f是否有可能

Test link is here

    DECLARE @N int = 4;

    SELECT 
    [Value] = CAST(number + 1 as tinyint) 
    INTO #Values
    FROM master.dbo.spt_values
    WHERE number < @N
    AND [Type] = 'p'

    ;WITH COMBINATIONS AS(
       SELECT ScenarioKey = CAST(NULL AS nvarchar(MAX)), [Value], Total = 0, Multipication = 1, MemeberCount = 0
       FROM #Values
       UNION ALL
       SELECT ScenarioKey = ISNULL(S.ScenarioKey, '') + IIF(S.ScenarioKey IS NULL, '', N'-') + CAST(P.[Value] AS nvarchar(10)), S.[Value], Total = S.Total + P.[Value], Multipication = S.Multipication * P.[Value], MemeberCount = MemeberCount + 1
       FROM #Values P
       JOIN COMBINATIONS AS S ON S.Total < S.[Value]
    ),
    SCENARIOS AS(
       SELECT
        ScenarioKey
       ,ScenarioId = ROW_NUMBER() OVER(ORDER BY ScenarioKey)
       ,[Value]
       FROM
       (
        SELECT 
         ScenarioKey 
        ,[Value]
        ,Multipication
        ,MemeberCount
        -- this will prevent dublications. because 1 * 2 * 3 = 3 * 2 * 1
        -- however, I am not sure about multipication of two different number sets would not be equal any time
        ,RowNo = ROW_NUMBER() OVER(PARTITION BY [Value],Multipication,MemeberCount ORDER BY [Value],ScenarioKey)
        FROM COMBINATIONS
        WHERE Total = @N
      ) X
      WHERE RowNo = 1 AND [Value] = @N
    )
    SELECT
     R.ScenarioId
    ,[Value] = S.[value]
    FROM SCENARIOS R
    CROSS APPLY (SELECT [value] FROM STRING_SPLIT(R.ScenarioKey, '-')) S


    DROP TABLE #Values

2 个答案:

答案 0 :(得分:3)

评论太久了,因此我将其发布为答案。我想指出的是,这是一个静态示例,但我希望它可以轻松地转换为动态语句。

在语句中将步骤写为注释:

WITH rcte AS
(
   -- Recursive query to generate all numbers from 1 to 4
   SELECT 0 AS Number
   UNION ALL 
   SELECT Number + 1
   FROM rcte
   WHERE Number < 4
), permutations AS (
   -- All possible permutations with sum equal to 4
   -- There is additional column DuplicateMarker. 
   -- It will be used later, because 0,0,0,4 and 0,4,0,0 are the same
   SELECT 
      t1.Number AS Number1,
      t2.Number AS Number2,
      t3.Number AS Number3,
      t4.Number AS Number4,
      CONCAT(LTRIM(STR(t1.Number)), '.', LTRIM(STR(t2.Number)), '.', LTRIM(STR(t3.Number)), '.', LTRIM(STR(t4.Number))) AS DuplicateMarker
   FROM rcte t1, rcte t2, rcte t3, rcte t4
   WHERE (t1.Number + t2.Number + t3.Number + t4.Number) = 4
), duplicates AS (
   -- Get data with splitted DuplicateMarker column
   SELECT *
   FROM permutations
   CROSS APPLY (SELECT [value] FROM STRING_SPLIT(DuplicateMarker, '.')) t
), results AS (
   -- Get unique combinations
   -- WITHIN GROUP (ORDER BY) will order strings and 0.0.0.4 and 0.4.0.0 will be the same
   SELECT DISTINCT STRING_AGG([value], '.') WITHIN GROUP (ORDER BY [value]) AS ScenarioValue
   FROM duplicates
   GROUP BY Number1, Number2, Number3, Number4
)
SELECT 
   DENSE_RANK() OVER (ORDER BY r.ScenarioValue) AS ScenarioID,
   s.[value]
FROM results r
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(r.ScenarioValue, '.')) s
WHERE [value] <> '0'

输出:

ScenarioID  value
1           4
2           1
2           3
3           2
3           2
4           1
4           1
4           2
5           1
5           1
5           1
5           1

更新:

由于@AndriyM的评论,我进行了一些更改,现在您可以消除字符串操作了:

WITH rcte AS
(
   -- Recursive query to generate all numbers from 0 to 4
   SELECT 0 AS Number
   UNION ALL 
   SELECT Number + 1
   FROM rcte
   WHERE Number < 4
), combinations AS (
   -- All different combinations with sum equal to 4
   SELECT 
      t1.Number AS Number1,
      t2.Number AS Number2,
      t3.Number AS Number3,
      t4.Number AS Number4,
      ROW_NUMBER() OVER (ORDER BY t1.Number, t2.Number, t3.Number, t4.NUmber) AS ScenarioID
   FROM rcte t1, rcte t2, rcte t3, rcte t4
   WHERE 
      ((t1.Number + t2.Number + t3.Number + t4.Number) = 4) AND
      (t1.Number <= t2.Number) AND
      (t2.Number <= t3.Number) AND 
      (t3.Number <= t4.Number)
)
SELECT c.ScenarioID, v.[value]
FROM combinations c
CROSS APPLY (VALUES (c.NUmber1), (c.Number2), (c.Number3), (c.Number4)) AS v ([value])
WHERE v.[value] > 0

更新2:

使用动态语句的方法-可能不是最好的方法,但是基于第一次更新的语句:

-- Set your @n value
DECLARE @n int
SET @n = 4

-- Declarations
DECLARE @combinationsSelect nvarchar(max)
DECLARE @combinationsRowNumber nvarchar(max)
DECLARE @combinationsFrom nvarchar(max)
DECLARE @combinationsWhere1 nvarchar(max)
DECLARE @combinationsWhere2 nvarchar(max)
DECLARE @combinationsValues nvarchar(max)

SET @combinationsSelect = N''
SET @combinationsRowNumber = N''
SET @combinationsFrom = N''
SET @combinationsValues = N''
SET @combinationsWhere1 = N''
SET @combinationsWhere2 = N''

-- Generate dynamic parts of the statement
;WITH numbers AS
(
   SELECT 1 AS Number
   UNION ALL 
   SELECT Number + 1
   FROM Numbers
   WHERE Number < @n
)
SELECT 
    @combinationsSelect = @combinationsSelect + N', t' + LTRIM(STR(Number)) + N'.Number AS Number' + LTRIM(STR(Number)),
    @combinationsRowNumber = @combinationsRowNumber + N', t' + LTRIM(STR(Number)) + N'.Number',
    @combinationsValues = @combinationsValues + N', (c.Number' + LTRIM(STR(Number)) + N')',
    @combinationsFrom = @combinationsFrom + N', rcte t' + LTRIM(STR(Number)),
    @combinationsWhere1 = @combinationsWhere1 + N'+ t' + LTRIM(STR(Number)) + N'.Number ',
    @combinationsWhere2 = @combinationsWhere2 + 
        CASE
            WHEN Number = 1 THEN N''
            ELSE N'AND (t' + LTRIM(STR(Number-1)) + N'.Number <= t' +  + LTRIM(STR(Number)) + N'.Number) '
        END
FROM 
    numbers
SET @combinationsSelect = STUFF(@combinationsSelect, 1, 2, N'')
SET @combinationsRowNumber = STUFF(@combinationsRowNumber, 1, 2, N'')
SET @combinationsValues = STUFF(@combinationsValues, 1, 2, N'')
SET @combinationsFrom = STUFF(@combinationsFrom, 1, 2, N'')
SET @combinationsWhere1 = STUFF(@combinationsWhere1, 1, 2, N'')
SET @combinationsWhere2 = STUFF(@combinationsWhere2, 1, 4, N'')

-- Dynamic statement
DECLARE @stm nvarchar(max)
SET @stm =
    N'WITH rcte AS (
       SELECT 0 AS Number
       UNION ALL 
       SELECT Number + 1
       FROM rcte
       WHERE Number < ' + LTRIM(STR(@n)) +
    N'), combinations AS (
       SELECT ' + 
          @combinationsSelect +
          N', ROW_NUMBER() OVER (ORDER BY ' + @combinationsRowNumber + N') AS ScenarioID
       FROM ' + @combinationsFrom +
       N' WHERE ((' + @combinationsWhere1 + N') = ' + LTRIM(STR(@n)) + ') AND ' + @combinationsWhere2 + 
    N')
    SELECT c.ScenarioID, v.[value]
    FROM combinations c
    CROSS APPLY (VALUES ' + @combinationsValues + N') AS v ([value])
    WHERE v.[value] > 0'

-- Execute dynamic statement
EXEC (@stm)

答案 1 :(得分:1)

如果您有以下示例数据

enter image description here

您可以编写如下查询

Declare @N int =4

Select T.*
From #T T
    cross apply (
                select S, SUM(V) Total
                From #T
                Group By S) Totals
Where Totals.Total=@N and T.S = Totals.S