【问题标题】:Calculation in Sql ServerSql Server 中的计算
【发布时间】:2017-06-07 19:39:04
【问题描述】:

我尝试执行以下计算

样本数据:

CREATE TABLE #Table1
  (
     rno   int identity(1,1),
     ccp   varchar(50),
     [col1] INT,
     [col2] INT,
     [col3] INT,
     col4 as [col2]/100.0
  );

INSERT INTO #Table1
            (ccp,[col1],[col2],[col3])
VALUES      ('ccp1',15,10,1100),
            ('ccp1',20,10,1210),
            ('ccp1',30,10,1331),
            ('ccp2',10,15,900),
            ('ccp2',15,15,1000),
            ('ccp2',20,15,1010)

+-----+------+------+------+------+----------+
| rno | ccp  | col1 | col2 | col3 |   col4   |
+-----+------+------+------+------+----------+
|   1 | ccp1 |   15 |   10 | 1100 | 0.100000 |
|   2 | ccp1 |   20 |   10 | 1210 | 0.100000 |
|   3 | ccp1 |   30 |   10 | 1331 | 0.100000 |
|   4 | ccp2 |   10 |   15 |  900 | 0.150000 |
|   5 | ccp2 |   15 |   15 | 1000 | 0.150000 |
|   6 | ccp2 |   20 |   15 | 1010 | 0.150000 |
+-----+------+------+------+------+----------+

注意:不仅仅是3记录每个ccp可以有Nno.of记录

预期结果:

1083.500000 --1100 - (15 * (1+0.100000))
1169.850000 --1210 - ((20 * (1+0.100000)) + (15 * (1+0.100000)* (1+0.100000)) )
1253.835000 --1331 - ((30 * (1+0.100000)) + (20 * (1+0.100000)* (1+0.100000)) + (15 * (1+0.100000)* (1+0.100000) *(1+0.100000)) )
888.500000  --900 - (10 * (1+0.150000))
969.525000  --1000 - ((15 * (1+0.150000)) + (10 * (1+0.150000)* (1+0.150000)) )
951.953750  --1010 - ((20 * (1+0.150000)) + (15 * (1+0.150000)* (1+0.150000)) + (10 * (1+0.150000)* (1+0.150000) *(1+0.150000)) )

我知道我们可以使用递归 CTE 来做到这一点,它效率不高,因为我必须对超过 500 万条记录执行此操作。

我希望实现类似这种基于集合的方法

对于 ccpccp1

SELECT col3 - ( col1 * ( 1 + col4 ) )
FROM   #Table1
WHERE  rno = 1

SELECT rno,
       col3 - ( ( col1 * Power(( 1 + col4 ), 1) ) + ( Lag(col1, 1)
                                                        OVER(
                                                          ORDER BY rno ) * Power(( 1 + col4 ), 2) ) )
FROM   #Table1
WHERE  rno IN ( 1, 2 )

SELECT rno,
       col3 - ( ( col1 * Power(( 1 + col4 ), 1) ) + ( Lag(col1, 1)
                                                        OVER(
                                                          ORDER BY rno ) * Power(( 1 + col4 ), 2) ) + ( Lag(col1, 2)
                                                                                                          OVER(
                                                                                                            ORDER BY rno ) * Power(( 1 + col4 ), 3) ) )
FROM   #Table1
WHERE  rno IN ( 1, 2, 3 ) 

有没有办法在单个查询中计算?

更新:

仍然愿意接受建议。我坚信应该有人使用SUM () Over(Order by) 窗口聚合函数来做到这一点。

【问题讨论】:

  • 你有 500 万条记录,是否意味着在 Id 4 上,你将添加 id 3、2、1,在 Id 10 上,你将添加 9、8 ...、3、2、1 ?或者你继续连续 3 次?
  • @Veljko89 - 对于 id 4,我将添加 3,2,1。
  • @Veljko89 - 添加更多示例数据以清除事物
  • @Prdp .. 每个 ccp 的 col2 是否相同?
  • 我怀疑你会找到任何使用SUM () Over(Order by) 的方法 - 所做的只是得到一个表达式的总和。不允许您操纵之前的运行总计,然后将结果用作新的运行总计。

标签: sql sql-server tsql sql-server-2012


【解决方案1】:

最后我用下面的方法得到了结果

SELECT a.*,
       col3 - res AS Result
FROM   #TABLE1 a
       CROSS apply (SELECT Sum(b.col1 * Power(( 1 + b.COL2 / 100.00 ), new_rn)) AS res
                    FROM   (SELECT Row_number()
                                     OVER(
                                       partition BY ccp
                                       ORDER BY rno DESC) new_rn,*
                            FROM   #TABLE1 b
                            WHERE  a.ccp = b.ccp
                                   AND a.rno >= b.rno)b) cs

结果:

+-----+------+------+------+------+----------+-------------+
| rno | ccp  | col1 | col2 | col3 |   col4   |   Result    |
+-----+------+------+------+------+----------+-------------+
|   1 | ccp1 |   15 |   10 | 1100 | 0.100000 | 1083.500000 |
|   2 | ccp1 |   20 |   10 | 1210 | 0.100000 | 1169.850000 |
|   3 | ccp1 |   30 |   10 | 1331 | 0.100000 | 1253.835000 |
|   4 | ccp2 |   10 |   15 |  900 | 0.150000 | 888.500000  |
|   5 | ccp2 |   15 |   15 | 1000 | 0.150000 | 969.525000  |
|   6 | ccp2 |   20 |   15 | 1010 | 0.150000 | 951.953750  |
+-----+------+------+------+------+----------+-------------+

【讨论】:

  • 干得好!不记得上次我在 SO 上看到这个很酷的问题了
  • @Pரதீப் 令人印象深刻。您是否将其重写为使用窗口函数(ORDER BY UNBOUNDED PRECEDING)?它会提高可读性吗?这实际上非常紧凑并且易于阅读。性能(你上面提到的 15 到 17 秒)来自这个查询?
  • @suresubs - 使用聚合窗口函数编写此查询是不可能的。相信我,我已经做了足够的研究
  • 注意:您使用的是b.COL2 / 100.00 而不是COL4。如果您可以选择将 COL4 更改为 PERSISTED,您可能会获得另一个小幅提升。
【解决方案2】:

这个答案可能令人失望,但您可能会发现迭代 CLR 方法的性能与任何 TSQL 方法相比都具有竞争力。

尝试以下(基于Running sums yet again: SQLCLR saves the day!

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void StackoverflowQuestion41803909()
    {
        using (SqlConnection conn = new SqlConnection("context connection=true;"))
        {
            SqlCommand comm = new SqlCommand();
            comm.Connection = conn;
            comm.CommandText = @"
SELECT [rno],
       [ccp],
       [col1],
       [col2],
       [col3],
       [col4]
FROM   Table1
ORDER  BY ccp,
          rno 
";

            SqlMetaData[] columns = new SqlMetaData[7];
            columns[0] = new SqlMetaData("rno", SqlDbType.Int);
            columns[1] = new SqlMetaData("ccp", SqlDbType.VarChar, 50);
            columns[2] = new SqlMetaData("col1", SqlDbType.Int);
            columns[3] = new SqlMetaData("col2", SqlDbType.Int);
            columns[4] = new SqlMetaData("col3", SqlDbType.Int);
            columns[5] = new SqlMetaData("col4", SqlDbType.Decimal, 17, 6);
            columns[6] = new SqlMetaData("result", SqlDbType.Decimal, 17, 6);

            SqlDataRecord record = new SqlDataRecord(columns);

            SqlContext.Pipe.SendResultsStart(record);

            conn.Open();

            SqlDataReader reader = comm.ExecuteReader();

            string prevCcp = null;
            decimal offset = 0;

            while (reader.Read())
            {
                string ccp = (string)reader[1];
                int col1 = (int)reader[2];
                int col3 = (int)reader[4];
                decimal col4 = (decimal)reader[5];

                if (prevCcp != ccp)
                {
                    offset = 0;
                }

                offset = ((col1 + offset) * (1 + col4));
                record.SetInt32(0, (int)reader[0]);
                record.SetString(1, ccp);
                record.SetInt32(2, col1);
                record.SetInt32(3, (int)reader[3]);
                record.SetInt32(4, col3);
                record.SetDecimal(5, col4);
                record.SetDecimal(6, col3 - offset);

                SqlContext.Pipe.SendResultsRow(record);

                prevCcp = ccp;
            }

            SqlContext.Pipe.SendResultsEnd();
        }
    }
};

【讨论】:

    【解决方案3】:

    另一种选择

    CREATE TABLE #Table1
      (
         rno   int identity(1,1),
         ccp   varchar(50),
         [col1] INT,
         [col2] INT,
         [col3] INT,
         col4 as [col2]/100.0
      );
    
    INSERT INTO #Table1
                (ccp,[col1],[col2],[col3])
    VALUES      ('ccp1',15,10,1100),
                ('ccp1',20,10,1210),
                ('ccp1',30,10,1331),
                ('ccp1',40,10,1331),
                ('ccp2',10,15,900),
                ('ccp2',15,15,1000),
                ('ccp2',20,15,1010);
    
    select t.*, col3-s
    from(
        select *, rn = row_number() over(partition by ccp order by rno)
        from #Table1
    ) t
    cross apply (
        select s=sum(pwr*col1)
        from(
            select top(rn)
               col1, pwr = power(1+col4, rn + 1 - row_number() over(order by rno))
            from #Table1 t2
            where t2.ccp=t.ccp
            order by row_number() over(order by rno)
            )t3
        )t4
    order by rno;
    

    【讨论】:

    • 用 300 万条记录对其进行了测试(50000 ccp's60 rno 用于每个 ccp)。您的耗时 155-160 秒。
    【解决方案4】:

    使用self join 的方法。不确定这是否比您使用cross apply 的版本更有效。

    WITH T AS
      (SELECT *,
              ROW_NUMBER() OVER(PARTITION BY CCP
                                ORDER BY RNO) AS RN
       FROM #TABLE1)
    SELECT T1.RNO,
           T1.CCP,
           T1.COL1,
           T1.COL2,
           T1.COL3,
           T1.COL3-SUM(T2.COL1*POWER(1+T1.COL2/100.0,T1.RN-T2.RN+1)) AS RES
    FROM T T1
    JOIN T T2 ON T1.CCP=T2.CCP
    AND T1.RN>=T2.RN
    GROUP BY T1.RNO,
             T1.CCP,
             T1.COL1,
             T1.COL2,
             T1.COL3
    

    Sample Demo

    【讨论】:

    • 用 300 万条记录对其进行了测试(50000 ccp's60 rno 用于每个 ccp)。我的查询花了 15-17 秒。您的耗时 60-65 秒。
    【解决方案5】:

    试试这个:

    ;with 
        val as (
            select 
                *, 
                (1 + col2 / 100.00) val,
                row_number() over(partition by ccp order by rno desc) rn
            from #Table1),
    res as (
            select 
                v1.rno, 
                --min(v1.ccp) ccp,
                --min(v1.col1) col1, 
                --min(v1.col2) col2, 
                min(v1.col3) col3, 
                sum(v2.col1 * power(v2.val, 1 + v2.rn - v1.rn)) sum_val
            from val v1
            left join val v2 on v2.ccp = v1.ccp and v2.rno <= v1.rno
            group by v1.rno)
    select *, col3 - isnull(sum_val, 0)
    from res
    

    但性能取决于索引。发布索引结构以获取详细信息。当您将其拆分为更多临时表时,可以获得最佳性能。

    【讨论】:

      【解决方案6】:

      在玩了一段时间之后,我相信对于是否可以使用sum() over (order by) 来完成这个悬赏问题的答案是否定的。这段代码尽可能接近:

      select  *, col3 - sum(col1 * power(1 + col4, row_num)) over (partition by ccp order by col1)
      from    (
              select  *, row_number() over (partition by ccp order by rno asc) row_num
              from    @Table1
              ) a
      order   by 1,2;
      

      这将为每个ccp 组中的第一行返回正确的结果。通过使用rno desc 计算row_num,而不是每个ccp 中的最后一行将是正确的。

      似乎以语法建议的简单方式使其工作的唯一方法是:

      1. 支持在聚合函数中引用实际行的语法。据我所知,这确实存在于 T-SQL 中。
      2. 窗口函数中的窗口函数的语法支持。根据以下错误,这在 T-SQL 中也是不允许的:

      窗口函数不能在另一个窗口的上下文中使用 函数或聚合。

      这是一个有趣的问题。即使实际结果不正确,我也很好奇此解决方案如何针对您的大型数据集执行。

      【讨论】:

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