【问题标题】:Split number into rows so that they sum up to the original number将数字拆分为行,以便它们总和为原始数字
【发布时间】:2023-03-28 13:28:01
【问题描述】:

我有一张表 [tbl],上面有货币值

id   mon
1    10.17
2    36.00

我需要通过一组特定范围 [1.00,10.00,25.00] 将这些值拆分为行。按 id 分组的新值的总和将等于原始值。

id   mon    sum
1    1.00   1.00
1    9.17   10.17
2    1.00   1.00
2    10.00  11.00
2    25.00  36.00

有什么方法可以在不使用光标的情况下做到这一点?

【问题讨论】:

  • 应该有一种使用排名/分析功能的方法。您使用的是哪个版本的 SQL 版本?
  • 我使用的是 SQL Server 2012
  • 那么具体的值只能出现一次?例如 100 将是 25 + 10 + 1 + 64?
  • 这是正确的。创建的行的总和应该等于原始值。

标签: sql-server split sql-server-2012


【解决方案1】:

这是一种方法:

;with CTE as (select t2.value, t1.id, sum(t2.value)
  over (partition by t1.id order by t2.value asc) as total 
from table1 t1 join table2 t2 on t1.mon >= t2.limit
)

select id, value, total from CTE

union all

select t1.id, t1.mon - c.total, t1.mon 
from table1 t1
outer apply (select top 1 id, total from CTE c 
    where c.id = t1.id order by c.value desc) c
where t1.mon > c.total
order by 1,3

这使用具有存储限制的附加表与原始数据连接,然后使用 CTE 中的运行总计并将其连接到原始表以获得剩余金额

您可以在SQL Fiddle中测试示例

【讨论】:

    【解决方案2】:

    这是我尝试使用窗口函数和CROSS APPLY:

    ;WITH Cte(s) AS(
        SELECT CAST(1 AS MONEY) UNION ALL 
        SELECT 10 UNION ALL 
        SELECT 25
    )
    ,CteRange AS(
        SELECT
            s,
            e = SUM(s) OVER(ORDER BY s)
        FROM Cte
    )
    SELECT
        t.id,
        mon = CASE WHEN t.mon > x.e THEN x.s ELSE mon - LAG(x.e) OVER(PARTITION BY t.id ORDER BY x.s) END,
        [sum] = CASE WHEN t.mon < x.e THEN t.mon ELSE x.e END
    FROM tbl t
    CROSS APPLY(
        SELECT * FROM CteRange
    )x
    WHERE t.mon > x.s
    
    UNION ALL
    
    SELECT
        t.id,
        mon = t.mon - x.e,
        [sum] = t.mon
    FROM tbl t
    CROSS APPLY(
        SELECT TOP 1 e
        FROM CteRange
        ORDER BY e DESC
    )x(e)
    WHERE t.mon > e
    ORDER BY t.id, mon
    

    SQL Fiddle

    【讨论】:

      【解决方案3】:

      这适用于您给定的示例数据,您只需要自己预定义范围(我使用过CROSS JOIN VALUES,但这可以按照您的意愿/偏好来完成)。我认为这不是问题。我已经使用运行 SUM 和分析函数来实现这一点。

      DECLARE @tbl TABLE
      (
          id INT IDENTITY (1, 1)
          , mon MONEY
      );
      
      INSERT INTO @tbl (mon)
      VALUES (10.17), (36.00);
      
      SELECT id
          , [sum] - SUM(lagRange) OVER (PARTITION BY ID ORDER BY rangeId) AS mon
          , [sum]
      FROM (
          SELECT id, rangeId
              , LAG(rangeValue, 1, 0) OVER (PARTITION BY ID ORDER BY rangeId) AS lagRange
              , CASE
                  WHEN SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId) > mon THEN mon
                  ELSE SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId)
              END AS [sum]
          FROM @tbl
          CROSS JOIN (VALUES ((1), (1.00)), ((2), (10.00)), ((3), (25.00))) AS T(rangeId, rangeValue)
          WHERE rangeValue <= mon
          ) AS T;
      

      结果:

      id  mon     sum
      -----------------
      1   1.00    1.00
      1   9.17    10.17
      2   1.00    1.00
      2   10.00   11.00
      2   25.00   36.00
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-22
        • 1970-01-01
        • 2016-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多