在Sql Server中计算
我试图执行下面的计算
样本数据:
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 | +-----+------+------+------+------+----------+
注意:每个ccp不能只有3
logging可以有N
个logging
预期结果 :
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)) )
我知道我们可以使用recursionCTE来做到这一点,因为我不得不为500多万条logging做这个。
我期待实现像这样基于集合的方法
对于ccp : ccp1
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 )
有一种方法来计算单个查询?
更新:
我仍然接受build议。 我强烈相信应该有一些使用SUM () Over(Order by)
窗口聚合函数来做到这一点。
一个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
最后我用下面的方法得到了结果
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 | +-----+------+------+------+------+----------+-------------+
这个答案可能会令人失望,但是你可能会发现一个迭代的CLR方法可以和任何TSQL方法竞争。
尝试以下(基于再次运行总和:SQLCLR节省一天! )
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(); } } };
另外一个select
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;
玩了一段时间后,我相信这个问题的答案是否可以用sum() over (order by)
是NO。 此代码尽可能接近我可以得到:
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
的最后一行都是正确的。
看来只有通过语法build议的简单方式来实现这个function的唯一方法是:
- 语法支持引用聚合函数中的实际行。 在我能find的情况下,这在T-SQL中确实存在。
- 窗口函数中的窗口函数的语法支持。 这在T-SQL中也不允许出现以下错误:
窗口函数不能用在另一个窗口函数或聚合的上下文中。
这是一个有趣的问题。 即使实际结果不正确,我也会好奇这个解决scheme如何处理大数据集。
尝试这个:
;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
但是性能取决于索引。 邮政索引结构的细节。 当你将它分成更多的临时表时,可以达到最佳性能。