शुद्ध 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 |
+------------+--------+-------+-----------------+
यहां प्रत्येक चरण सटीकता खो देता है। लॉग की गणना सटीकता खो देती है, एसयूएम परिशुद्धता खो देता है, EXक्स्प/पावर परिशुद्धता खो देता है। इन अंतर्निहित कार्यों के साथ मुझे नहीं लगता कि आप इसके बारे में बहुत कुछ कर सकते हैं।
तो, इसका उत्तर है - C# के साथ CLR का उपयोग करें decimal
टाइप करें (double
नहीं) ), जो उच्च परिशुद्धता (28-29 महत्वपूर्ण अंक) का समर्थन करता है। आपका मूल SQL प्रकार numeric(22,6)
उसमें फिट होगा। और आपको LOG/EXP
. के साथ ट्रिक की आवश्यकता नहीं होगी ।
उफ़। मैंने एक सीएलआर समुच्चय बनाने की कोशिश की जो उत्पाद की गणना करता है। यह मेरे परीक्षणों में काम करता है, लेकिन केवल एक साधारण समुच्चय के रूप में, अर्थात
यह काम करता है:
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 ...)
का उपयोग करके उत्पाद चलाना काम नहीं करता (एसक्यूएल सर्वर 2014 एक्सप्रेस 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;
एक खोज में यह कनेक्ट आइटम मिला। , जो "ठीक नहीं होगा" के रूप में बंद है और यह प्रश्न ।
सी# कोड:
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);
}
}
}
सीएलआर असेंबली स्थापित करें:
-- 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
यह प्रश्न चल रहे एसयूएम की गणना के बारे में विस्तार से चर्चा करता है और पॉल व्हाइट अपने जवाब में दिखाता है एक सीएलआर फ़ंक्शन कैसे लिखें जो एसयूएम को कुशलतापूर्वक चलाने की गणना करता है। चल रहे उत्पाद की गणना करने वाले फ़ंक्शन को लिखने के लिए यह एक अच्छी शुरुआत होगी।
ध्यान दें, कि वह एक अलग दृष्टिकोण का उपयोग करता है। कस्टम कुल . बनाने के बजाय फ़ंक्शन, पॉल एक फ़ंक्शन बनाता है जो एक तालिका देता है। फ़ंक्शन मूल डेटा को मेमोरी में पढ़ता है और सभी आवश्यक गणना करता है।
अपनी पसंद की प्रोग्रामिंग भाषा का उपयोग करके अपने ग्राहक पक्ष पर इन गणनाओं को लागू करके वांछित प्रभाव प्राप्त करना आसान हो सकता है। बस पूरी तालिका पढ़ें और क्लाइंट पर चल रहे उत्पाद की गणना करें। सीएलआर फ़ंक्शन बनाना समझ में आता है यदि सर्वर पर गणना किया गया चल रहा उत्पाद अधिक जटिल गणनाओं में एक मध्यस्थ कदम है जो डेटा को आगे बढ़ाएगा।
एक और विचार जो दिमाग में आता है।
एक तृतीय-पक्ष .NET गणित पुस्तकालय खोजें जो Log
प्रदान करता हो और Exp
उच्च परिशुद्धता के साथ कार्य करता है। इन स्केलर . का CLR संस्करण बनाएं कार्य। और फिर EXP + LOG + SUM() Over (Order by)
. का इस्तेमाल करें दृष्टिकोण, जहां SUM
बिल्ट-इन टी-एसक्यूएल फंक्शन है, जो Over (Order by)
को सपोर्ट करता है और Exp
और Log
कस्टम CLR फ़ंक्शन हैं जो float
नहीं लौटाते हैं , लेकिन उच्च परिशुद्धता decimal
।
ध्यान दें, उच्च परिशुद्धता गणना भी धीमी हो सकती है। और क्वेरी में CLR स्केलर फ़ंक्शंस का उपयोग करने से यह धीमा भी हो सकता है।