sql >> Database teknologi >  >> RDS >> Sqlserver

Afrundingsproblem i LOG- og EXP-funktioner

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.



  1. Råd om strukturering af et kommentarsystem

  2. Autoinkrementér manuelt en kolonne MySQL

  3. Singleton alternativ til PHP PDO

  4. Kontroller, om strengen indeholder accenttegn i SQL?