Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Vấn đề làm tròn trong các chức năng LOG và EXP

Trong T-SQL thuần túy LOGEXP 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ả LOGLOG10 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 LogExp 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)ExpLog 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm thế nào để so sánh dấu thời gian SQL trong .NET?

  2. Nhóm tệp chính có đầy đủ trong tiêu chuẩn SQL Server 2008 mà không có lý do rõ ràng

  3. Cách ghi động tên thuộc tính vào truy vấn chọn

  4. Cách loại trừ các bản ghi có giá trị nhất định trong sql select

  5. Sự khác biệt giữa Người dùng và Đăng nhập trong SQL Server