I ren T-SQL LOG
og EXP
arbejde med float
type (8 bytes), som kun har 15-17 signifikante cifre
. Selv det sidste 15. ciffer kan blive unøjagtigt, hvis du summerer store nok værdier. Dine data er numeric(22,6)
, så 15 signifikante cifre er ikke nok.
STRØM
kan returnere numerisk
type med potentielt højere præcision, men det er til lidt nytte for os, fordi både LOG
og LOG10
kan kun returnere float
alligevel.
For at demonstrere problemet vil jeg ændre typen i dit eksempel til numeric(15,0)
og brug POWER
i stedet for EXP
:
DEKLARE @TESTTABEL ( 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 SOM STYRKE(2.606,600) numeric(15,0)), Sum(LOG10( Abs(NULLIF(VALUE, 0)) )) OVER(PARTITION BY PAR_COLUMN ORDER BY PERIOD)) AS MulFROM @TEST;
Resultat
+------------+--------+-------+------------- ----+| PAR_COLUMN | PERIODE | VÆRDI | 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 |+------------+--------+-------+---------------- +
Hvert trin her mister præcision. Beregning af LOG mister præcision, SUM mister præcision, EXP/POWER mister præcision. Med disse indbyggede funktioner tror jeg ikke du kan gøre meget ved det.
Så svaret er - brug CLR med C# decimal
type (ikke dobbelt
), som understøtter højere præcision (28-29 signifikante cifre). Din oprindelige SQL-type numeric(22,6)
ville passe ind i det. Og du behøver ikke tricket med LOG/EXP
.
Ups. Jeg forsøgte at lave et CLR-aggregat, der beregner produkt. Det virker i mine tests, men kun som et simpelt aggregat, dvs.
Dette virker:
VÆLG T.PAR_COLUMN, [dbo].[Product](T.VALUE) AS PFROM @TEST AS TGROUP BY T.PAR_COLUMN;
Og endda OVER (PARTITION BY)
virker:
VÆLG *, [dbo].[Product](T.VALUE) OVER (OPDELING EFTER PAR_COLUMN) SOM PFROM @TEST AS T;
Men kører produktet ved hjælp af OVER (PARTITION BY ... ORDER BY ...)
virker ikke (tjekket med SQL Server 2014 Express 12.0.2000.8):
VÆLG *, [dbo].[Produkt](T.VÆRDI) OVER (OPDELING EFTER T.PAR_COLUMN RÆKKER EFTER T.PERIOD RÆKKER MELLEM UBEGRÆNSET FOREGÅENDE OG AKTUELLE RÆKKE) SOM CUM_MULFROM @TEST SOM T;
En søgning fandt dette forbindelseselement , som er lukket som "Vil ikke rette" og denne spørgsmål .
C#-koden:
bruger System;bruger System.Data;bruger System.Data.SqlClient;bruger System.Data.SqlTypes;bruger Microsoft.SqlServer.Server;bruger System.IO;bruger System.Collections.Generic;bruger System. Tekst;navneområde RunningProduct{ [Serializable] [SqlUserDefinedAggregate( Format.UserDefined, MaxByteSize =17, IsInvariantToNulls =true, IsInvariantToDuplicates =false, IsInvariantToOrder =true, IsNullIfEmpty =true )] {bIBin private struct =true )] // 1 byte lagring privat decimal m_Produkt; // 16 bytes lager 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 =falsk; this.m_Product *=ParamValue.Value; } public void Merge(Produkt andet) { SqlDecimal otherValue =other.Terminate(); this.Accumulate(otherValue); } [return:SqlFacet(Precision =22, Scale =6)] public SqlDecimal Terminate() { if (m_bIsNull) { return SqlDecimal.Null; } andet { returner m_Produkt; } } 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); } }}
Installer CLR-samling:
-- Slå avancerede indstillinger onEXEC sys.sp_configure @configname ='vis avancerede indstillinger', @configvalue =1;GORECONFIGURE MED TILSIDE;GO-- Aktiver CLREXEC sys.sp_configure @configname ='clr aktiveret', @ configvalue =1;GORECONFIGURE WITH OVERRIDE;GOCREATE ASSEMBLY [RunningProduct]AUTHORIZATION [dbo]FRA 'C:\RunningProduct\RunningProduct.dll'WITH TILLADELSE_SET =SAFE;GOCREATE AGGREGATE [dbo](@Paramic2alue].[Product2 ))RETURNERER numeric(22,6)EKSTERNT NAVN [RunningProduct].[RunningProduct.Product];GO
Dette spørgsmål diskuterer beregningen af en løbende SUM i store detaljer og Paul White viser i sit svar hvordan man skriver en CLR-funktion, der beregner løbende SUM effektivt. Det ville være en god start for at skrive en funktion, der beregner kørende produkt.
Bemærk, at han bruger en anden tilgang. I stedet for at lave et tilpasset aggregat funktion laver Paul en funktion, der returnerer en tabel. Funktionen læser de originale data ind i hukommelsen og udfører alle nødvendige beregninger.
Det kan være lettere at opnå den ønskede effekt ved at implementere disse beregninger på din klientside ved at bruge det programmeringssprog, du vælger. Bare læs hele tabellen og beregn løbende produkt på klienten. Oprettelse af CLR-funktion giver mening, hvis det kørende produkt beregnet på serveren er et mellemtrin i en mere kompleks beregning, der ville aggregere data yderligere.
Endnu en idé, der kommer til at tænke på.
Find et tredjeparts .NET matematikbibliotek, der tilbyder Log
og Exp
fungerer med høj præcision. Lav en CLR-version af disse skalare funktioner. Og brug derefter EXP + LOG + SUM() Over (Bestil efter)
tilgang, hvor SUM
er den indbyggede T-SQL-funktion, som understøtter Over (Bestil efter)
og Exp
og Log
er tilpassede CLR-funktioner, der ikke returnerer float
, men højpræcision decimal
.
Bemærk, at beregninger med høj præcision også kan være langsomme. Og brug af CLR-skalarfunktioner i forespørgslen kan også gøre den langsom.