Trong T-SQL thuần túy LOG
và EXP
hoạt động với float
loại (8 byte), chỉ có 15-17 chữ số có nghĩa
. Ngay cả chữ số 15 cuối cùng đó cũng có thể trở nên không chính xác nếu bạn tính tổng các giá trị đủ lớn. Dữ liệu của bạn là numeric(22,6)
, vì vậy 15 chữ số có nghĩa là không đủ.
POWER
có thể trả về numeric
nhập với độ chính xác có thể cao hơn, nhưng nó ít được sử dụng cho chúng tôi, vì cả LOG
và LOG10
chỉ có thể trả về float
dù sao đi nữa.
Để giải thích vấn đề, tôi sẽ thay đổi kiểu trong ví dụ của bạn thành numeric(15,0)
và sử dụng POWER
thay vì EXP
:
DECLARE @TEST TABLE
(
PAR_COLUMN INT,
PERIOD INT,
VALUE NUMERIC(15, 0)
);
INSERT INTO @TEST VALUES
(1,601,10 ),
(1,602,20 ),
(1,603,30 ),
(1,604,40 ),
(1,605,50 ),
(1,606,60 ),
(2,601,100),
(2,602,200),
(2,603,300),
(2,604,400),
(2,605,500),
(2,606,600);
SELECT *,
POWER(CAST(10 AS numeric(15,0)),
Sum(LOG10(
Abs(NULLIF(VALUE, 0))
))
OVER(PARTITION BY PAR_COLUMN ORDER BY PERIOD)) AS Mul
FROM @TEST;
Kết quả
+------------+--------+-------+-----------------+
| PAR_COLUMN | PERIOD | VALUE | Mul |
+------------+--------+-------+-----------------+
| 1 | 601 | 10 | 10 |
| 1 | 602 | 20 | 200 |
| 1 | 603 | 30 | 6000 |
| 1 | 604 | 40 | 240000 |
| 1 | 605 | 50 | 12000000 |
| 1 | 606 | 60 | 720000000 |
| 2 | 601 | 100 | 100 |
| 2 | 602 | 200 | 20000 |
| 2 | 603 | 300 | 6000000 |
| 2 | 604 | 400 | 2400000000 |
| 2 | 605 | 500 | 1200000000000 |
| 2 | 606 | 600 | 720000000000001 |
+------------+--------+-------+-----------------+
Mỗi bước ở đây mất độ chính xác. Tính toán LOG làm mất độ chính xác, SUM làm mất độ chính xác, EXP / POWER làm mất độ chính xác. Với những chức năng tích hợp này, tôi không nghĩ bạn có thể làm được gì nhiều.
Vì vậy, câu trả lời là - sử dụng CLR với C # decimal
loại (không phải double
), hỗ trợ độ chính xác cao hơn (28-29 chữ số có nghĩa). Kiểu SQL ban đầu của bạn numeric(22,6)
sẽ phù hợp với nó. Và bạn sẽ không cần thủ thuật với LOG/EXP
.
Ối. Tôi đã cố gắng tạo tổng hợp CLR để tính toán Sản phẩm. Nó hoạt động trong các thử nghiệm của tôi, nhưng chỉ là một tổng hợp đơn giản, tức là
Điều này hoạt động:
SELECT T.PAR_COLUMN, [dbo].[Product](T.VALUE) AS P
FROM @TEST AS T
GROUP BY T.PAR_COLUMN;
Và thậm chí OVER (PARTITION BY)
hoạt động:
SELECT *,
[dbo].[Product](T.VALUE)
OVER (PARTITION BY PAR_COLUMN) AS P
FROM @TEST AS T;
Tuy nhiên, sản phẩm đang chạy bằng OVER (PARTITION BY ... ORDER BY ...)
không hoạt động (được kiểm tra bằng SQL Server 2014 Express 12.0.2000.8):
SELECT *,
[dbo].[Product](T.VALUE)
OVER (PARTITION BY T.PAR_COLUMN ORDER BY T.PERIOD
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CUM_MUL
FROM @TEST AS T;
Một tìm kiếm đã tìm thấy mục kết nối này , được đóng là "Sẽ không khắc phục" và điều này câu hỏi .
Mã C #:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace RunningProduct
{
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined,
MaxByteSize = 17,
IsInvariantToNulls = true,
IsInvariantToDuplicates = false,
IsInvariantToOrder = true,
IsNullIfEmpty = true)]
public struct Product : IBinarySerialize
{
private bool m_bIsNull; // 1 byte storage
private decimal m_Product; // 16 bytes storage
public void Init()
{
this.m_bIsNull = true;
this.m_Product = 1;
}
public void Accumulate(
[SqlFacet(Precision = 22, Scale = 6)] SqlDecimal ParamValue)
{
if (ParamValue.IsNull) return;
this.m_bIsNull = false;
this.m_Product *= ParamValue.Value;
}
public void Merge(Product other)
{
SqlDecimal otherValue = other.Terminate();
this.Accumulate(otherValue);
}
[return: SqlFacet(Precision = 22, Scale = 6)]
public SqlDecimal Terminate()
{
if (m_bIsNull)
{
return SqlDecimal.Null;
}
else
{
return m_Product;
}
}
public void Read(BinaryReader r)
{
this.m_bIsNull = r.ReadBoolean();
this.m_Product = r.ReadDecimal();
}
public void Write(BinaryWriter w)
{
w.Write(this.m_bIsNull);
w.Write(this.m_Product);
}
}
}
Cài đặt lắp ráp CLR:
-- Turn advanced options on
EXEC sys.sp_configure @configname = 'show advanced options', @configvalue = 1 ;
GO
RECONFIGURE WITH OVERRIDE ;
GO
-- Enable CLR
EXEC sys.sp_configure @configname = 'clr enabled', @configvalue = 1 ;
GO
RECONFIGURE WITH OVERRIDE ;
GO
CREATE ASSEMBLY [RunningProduct]
AUTHORIZATION [dbo]
FROM 'C:\RunningProduct\RunningProduct.dll'
WITH PERMISSION_SET = SAFE;
GO
CREATE AGGREGATE [dbo].[Product](@ParamValue numeric(22,6))
RETURNS numeric(22,6)
EXTERNAL NAME [RunningProduct].[RunningProduct.Product];
GO
câu hỏi này thảo luận về cách tính SUM đang chạy rất chi tiết và Paul White thể hiện trong câu trả lời của anh ấy làm thế nào để viết một hàm CLR để tính toán SUM đang chạy một cách hiệu quả. Nó sẽ là một khởi đầu tốt để viết một hàm tính toán Sản phẩm đang chạy.
Lưu ý rằng anh ta sử dụng một cách tiếp cận khác. Thay vì tạo tổng hợp tùy chỉnh , Paul tạo một hàm trả về một bảng. Hàm đọc dữ liệu gốc vào bộ nhớ và thực hiện tất cả các phép tính cần thiết.
Có thể dễ dàng hơn để đạt được hiệu quả mong muốn bằng cách thực hiện các phép tính này ở phía khách hàng của bạn bằng cách sử dụng ngôn ngữ lập trình bạn chọn. Chỉ cần đọc toàn bộ bảng và tính toán sản phẩm đang chạy trên máy khách. Việc tạo hàm CLR có ý nghĩa nếu sản phẩm đang chạy được tính toán trên máy chủ là một bước trung gian trong một phép tính phức tạp hơn sẽ tổng hợp dữ liệu hơn nữa.
Thêm một ý tưởng xuất hiện trong đầu.
Tìm thư viện toán học .NET của bên thứ ba cung cấp Log
và Exp
các chức năng với độ chính xác cao. Tạo phiên bản CLR của vô hướng này chức năng. Và sau đó sử dụng EXP + LOG + SUM() Over (Order by)
phương pháp tiếp cận, trong đó SUM
là hàm T-SQL được tích hợp sẵn, hỗ trợ Over (Order by)
và Exp
và Log
là các hàm CLR tùy chỉnh trả về không phải float
, nhưng decimal
có độ chính xác cao .
Lưu ý rằng các phép tính có độ chính xác cao cũng có thể bị chậm. Và việc sử dụng các hàm vô hướng CLR trong truy vấn cũng có thể làm chậm.