sql >> データベース >  >> RDS >> Sqlserver

LOGおよびEXP関数の丸めの問題

    純粋なT-SQLの場合LOG およびEXP floatで操作する タイプ(8バイト)。 15〜17桁の有効数字 のみが含まれます。 。十分な大きさの値を合計すると、最後の15桁目でさえ不正確になる可能性があります。データはnumeric(22,6)です 、したがって、有効数字15桁では不十分です。

    POWER numericを返すことができます 潜在的に高い精度で入力しますが、両方ともLOGであるため、ほとんど役に立ちません。 およびLOG10 floatのみを返すことができます とにかく。

    問題を示すために、例のタイプをnumeric(15,0)に変更します。 POWERを使用します 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;
    

    結果

    +------------+--------+-------+-----------------+
    | 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 |
    +------------+--------+-------+-----------------+
    

    ここでの各ステップは精度を失います。 LOGの計算は精度を失い、SUMは精度を失い、EXP/POWERは精度を失います。これらの組み込み関数では、それについて多くのことができるとは思いません。

    したがって、答えは-CLRをC#で使用する decimal> タイプ(doubleではありません )、より高い精度(28-29有効数字)をサポートします。元のSQLタイプnumeric(22,6) それに収まるでしょう。そして、LOG/EXPのトリックは必要ありません。 。

    おっとっと。 Productを計算するCLR集計を作成しようとしました。私のテストでは機能しますが、単純な集計としてのみ機能します。つまり、

    これは機能します:

    SELECT T.PAR_COLUMN, [dbo].[Product](T.VALUE) AS P
    FROM @TEST AS T
    GROUP BY T.PAR_COLUMN;
    

    そしてOVER (PARTITION BY) 動作:

    SELECT *,
        [dbo].[Product](T.VALUE) 
        OVER (PARTITION BY PAR_COLUMN) AS P
    FROM @TEST AS T;
    

    ただし、OVER (PARTITION BY ... ORDER BY ...)を使用して製品を実行する 動作しません(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;
    

    検索により、この接続アイテム が見つかりました 、これは「修正されません」として閉じられ、この質問

    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);
            }
        }
    }
    

    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
    

    この質問 実行中のSUMの計算について詳しく説明し、PaulWhiteが回答に示しています 実行中のSUMを効率的に計算するCLR関数の記述方法。実行中のProductを計算する関数を作成するための良いスタートです。

    彼は別のアプローチを使用していることに注意してください。カスタムの集計を作成する代わりに 関数、ポールはテーブルを返す関数を作成します。この関数は元のデータをメモリに読み込み、必要なすべての計算を実行します。

    選択したプログラミング言語を使用してクライアント側でこれらの計算を実装することにより、目的の効果を達成する方が簡単な場合があります。テーブル全体を読んで、クライアントで実行中の製品を計算するだけです。サーバーで計算された実行中の製品が、データをさらに集約するより複雑な計算の中間ステップである場合、CLR関数を作成することは理にかなっています。

    頭に浮かぶもう1つのアイデア。

    Logを提供するサードパーティの.NET数学ライブラリを探す およびExp 高精度で機能します。これらのスカラーのCLRバージョンを作成します 機能。次に、EXP + LOG + SUM() Over (Order by)を使用します。 アプローチ、ここでSUM は組み込みのT-SQL関数であり、Over (Order by)をサポートします。 およびExp およびLog floatを返さないカスタムCLR関数です 、ただし高精度のdecimal

    高精度の計算も遅くなる可能性があることに注意してください。また、クエリでCLRスカラー関数を使用すると、処理が遅くなる可能性があります。



    1. MySQLで重複する行を削除する

    2. MicrosoftAccessの将来について楽観的である理由

    3. postgresqlシーケンスの値のリストを指定する方法

    4. PostreSQL 10は、Windows SubsystemforLinuxで実行されているUbuntu18.04で起動に失敗します