![]() |
Datenbank: SQL Server • Version: 2012 • Zugriff über: ADO
SQL: Jahresanteile des Vertags berechnen?
Hallo!
Ich habe eine Testtabelle, in der über 100.000 Verträge gespeichert sind. Jeder Vertrag hat unter anderem "Anfangsdatum", "Vertragsdauer" (in Monaten, mögliche Werte sind 1, 3, 6 und 12) und "Bezahlter Betrag". Ich möchte ich z. B. wissen, welcher Betrag aus allen Verträgen auf das Jahr 2012 anfällt. Das lässt sich errechnen, indem ich 1. Den Monatsanteil des Preises aus Vertragsdauer und Preis ermittle 2. Die Zahl der Vertragsmonate im Jahr 2012 aus Anfangsdatum und Vertragsdauer errechne 3. 1 mit 2 multipliziere Nun muss ich dafür eine SQL-Abfrage erstellen. Mangels tiefgreifender SQL-Kenntnisse liste ich testweise alle Vertäge auf und führe für jeden Vertrag die o.g. Schritte aus. Nach zwei Raucherpausen kommt der Euro-Betrag raus :cheers: :oops::oops::oops: Das muss doch alles mit einer einzelnen Abfrage gehen. Kann mir jemand mit einem passenden Beispiel helfen? Vielen Dank! |
AW: SQL: Jahresanteile des Vertags berechnen?
Deine Angaben sind noch etwas mager, also erstmal so bspw.
Code:
Hilfreich wäre z.B. die Angabe der Feldtypen (echte Datumstypen?) und des RDBMS. Gerade bei Datumsberechnungen gibt es viele herstellerspezifischen Funktionen.
select Preis/LaufzeitInMonaten*MonateIn2012 from Vertrag
where MonateIn2012>0 Vermutlich bezieht sich die Aufgabenstellung auch nicht nur auf das Jahr 2012? |
AW: SQL: Jahresanteile des Vertags berechnen?
MS SQL 2012 ist doch angegeben.
|
AW: SQL: Jahresanteile des Vertags berechnen?
Vielen Dank für die schnelle Reaktion!
Zitat:
Zitat:
|
AW: SQL: Jahresanteile des Vertags berechnen?
Zitat:
|
AW: SQL: Jahresanteile des Vertags berechnen?
Für einen SQL-Beispel wäre ich sehr dankbar! :roll:
|
AW: SQL: Jahresanteile des Vertags berechnen?
Wenn das überhaupt mit einer Abfrage (statt einer Stored Procedure) machbar ist, wird es wohl mächtig kompliziert. Ich habe zumindest keinen zündenden Einfall.
|
AW: SQL: Jahresanteile des Vertags berechnen?
Von mir aus kann das auch eine SP sein. Danke!
|
AW: SQL: Jahresanteile des Vertags berechnen?
Interessantes Problem, ich bin in MSSQL nicht so fit und die Datumsarithmetik hat auf Anhieb nicht geklappt (Rangeerror). Muss aber nichts heißen.
Hier eine Oracle Lösung, die Hälfte des Codes kann man weglassen, ist nur Erklärung bzw Daten. - fn "Round()" ggF durch mssql variante ersetzen - "from dual" weglassen - "decode" durch "case when" ersetzen - Datumsberechnung durch anloge mssql function ersetzen - fn "to_date()" durch "cast" ersetzen, später durch Datumsparameter dann müsste es unter mssql laufen Noch was zum Aufbau: Ich hab vermieden, das Datum zu zerschnippeln (ala YYYY='2012' usw), ich hoffe so bekommst Du gute Performance trotz großer Datenmengen. Da [myEnde] bei Dir nicht explizit vorliegt, sollte man es in der inneren Where Bedingung auf einen Vergleich mit [Mystart]<@ParamIntervalStart+Laufzeit umbauen. Die Logik (where Bedingung und Decode/Case) funktioniert (hoffentlich) nur bei sinnvollen Intervall Werten und. Sollte unabhängig von Intervallgrößen und Datumsgrenzen funktionieren.
Code:
select z.* ,
round(z.TageDiff/30,0) as MonateIn, (Preis/Laufzeit)*round(z.TageDiff/30,0) as PreisIntervall from ( select y.*, -- DefinedType = Manuell vordefiniert, zur Kontrolle -- DefinedType muss durch CalcType ersetzbar sein mit gleichem Ergebnis decode(Definedtype,'I Ende überlappend', (to_date('31.12.2012','DD.MM.YYYY')/*IntervallEnd*/ - y.myStart), 'I Start überlappend', (y.myEnde-to_date('01.01.2012','DD.MM.YYYY')/*IntervallStart*/ ), 'eingeschlossen von I', y.myEnde-y.myStart, 'I umschließend', to_date('31.12.2012','DD.MM.YYYY')/*IntervallEnd*/- to_date('01.01.2012','DD.MM.YYYY')/*IntervallStart*/, -1/*error*/) as TageDiff, 100.0 as Preis, round((MyEnde-MyStart)/30,0) as Laufzeit from ( select x.*, decode(-- MyStart kleiner als Intervallstart? least(MyStart, to_date('01.01.2012','DD.MM.YYYY')/*IntervallStart*/), MyStart, /*'ja, kleiner Case'*/ decode(greatest(MyEnde, to_date('31.12.2012','DD.MM.YYYY')/*IntervallStart*/), MyEnde, 'I umschließend','I Start überlappend'), /*'ne, größer Case'*/ decode(greatest(MyEnde, to_date('31.12.2012','DD.MM.YYYY')/*IntervallStart*/), MyEndE, 'I Ende überlappend','eingeschlossen von I') ) as CalcType from ( select 'vor I' as DefinedType, to_date('01.02.2011','DD.MM.YYYY') as myStart, to_date('30.04.2011','DD.MM.YYYY') as myEnde from dual union select 'hinter I' as DefinedType, to_date('01.02.2015','DD.MM.YYYY') as myStart, to_date('01.05.2015','DD.MM.YYYY') as myEnde from dual union select 'I umschließend' as DefinedType, to_date('01.02.2011','DD.MM.YYYY') as myStart, to_date('01.05.2013','DD.MM.YYYY')-1 as myEnde from dual union select 'eingeschlossen von I' as DefinedType, to_date('01.02.2012','DD.MM.YYYY') as myStart, to_date('01.05.2012','DD.MM.YYYY')-1 as myEnde from dual union select 'I Start überlappend' as DefinedType, to_date('01.02.2011','DD.MM.YYYY') as myStart, to_date('01.05.2012','DD.MM.YYYY')-1 as myEnde from dual union select 'I Ende überlappend' as DefinedType, to_date('01.02.2012','DD.MM.YYYY') as myStart, to_date('01.05.2013','DD.MM.YYYY')-1 as myEnde from dual )x where not x.myEnde < to_date('01.01.2012','DD.MM.YYYY') /*IntervallStart*/ and not x.myStart > to_date('31.12.2012','DD.MM.YYYY') /*IntervallEnde */ ) y ) z |
AW: SQL: Jahresanteile des Vertags berechnen?
Code:
SELECT
V.ID ,V.ANFANG ,V.DAUER ,V.BETRAG ,BERTRAG2012= CASE WHEN (DATEADD(month,V.DAUER,V.ANFANG)<'01/01/2012')OR(V.ANFANG>'31/12/2012')THEN 0 WHEN (DATEADD(month,V.DAUER,V.ANFANG)<='31/12/2012')AND(V.ANFANG>='01/01/2012')THEN 1 WHEN (V.ANFANG<'01/01/2012')AND(DATEADD(month,V.DAUER,V.ANFANG)<='31/12/2012')THEN CAST(DATEDIFF(day,'01/01/2012',DATEADD(month,V.DAUER,V.ANFANG)) AS NUMERIC(18,8))/DATEDIFF(day,V.ANFANG,DATEADD(month,V.DAUER,V.ANFANG)) WHEN (V.ANFANG>='01/01/2012')AND(DATEADD(month,V.DAUER,V.ANFANG)>'31/12/2012')THEN CAST(DATEDIFF(day,V.ANFANG,'31/12/2012') AS NUMERIC(18,8))/DATEDIFF(day,V.ANFANG,DATEADD(month,V.DAUER,V.ANFANG)) END * V.BETRAG FROM VERTRAEGE AS V |
AW: SQL: Jahresanteile des Vertags berechnen?
Wenn Verträge auch innerhalb eines Monats beginnen können musst Du aufbohren / umbauen
Code:
/* einmalig zu erstellende Function
Create Function EncodeDate( @day int,@month int,@year int) Returns DateTime begin Declare @Result DateTime SELECT @Result=CAST(CAST(10000*@year+100*@month+@day AS VARCHAR(8)) AS DATETIME); Return @Result end; */ Declare @Months Table(y int,m int) insert into @Months values (2012,1),(2012,2),(2012,3),(2012,4),(2012,5),(2012,6),(2012,7),(2012,8),(2012,9),(2012,10),(2012,11),(2012,12) Declare @a Table (ID int,Anfangsdatum datetime,Vertragsdauer int,[Bezahlter Betrag] money) insert into @a Values(1,'20111101',12,100),(2,'20120101',3,100),(3,'20120601',12,100); Select ID,COUNT(*) as Monate,[Bezahlter Betrag]/Vertragsdauer * COUNT(*) as Betrag from ( Select ID, Anfangsdatum,DATEADD(mm,Vertragsdauer,Anfangsdatum)-1 as EndDatum,[Bezahlter Betrag],Vertragsdauer from @a ) a join @Months m on Anfangsdatum<= dbo.EncodeDate(1,m.m,m.y) and Enddatum>= dbo.EncodeDate(1,m.m,m.y) Group by ID,Vertragsdauer,[Bezahlter Betrag] |
AW: SQL: Jahresanteile des Vertags berechnen?
:thumb:
Vielen Dank an alle! Ich muss jetzt alles ausprobieren! Danke!!! |
AW: SQL: Jahresanteile des Vertags berechnen?
--- blödsinn ---
|
AW: SQL: Jahresanteile des Vertags berechnen?
Nochmal kommentiert:
Code:
Diese Syntax funktioniert auch bei Verträgen, die mitten im Monat anfangen.
SELECT
V.ID ,V.ANFANG ,V.DAUER ,V.BETRAG ,BERTRAG2012= CASE WHEN (DATEADD(month,V.DAUER,V.ANFANG)<'01/01/2012')OR(V.ANFANG>'31/12/2012')THEN 0 WHEN (DATEADD(month,V.DAUER,V.ANFANG)<='31/12/2012')AND(V.ANFANG>='01/01/2012')THEN 1 WHEN (V.ANFANG<'01/01/2012')AND(DATEADD(month,V.DAUER,V.ANFANG)<='31/12/2012')THEN CAST(DATEDIFF(day,'01/01/2012',DATEADD(month,V.DAUER,V.ANFANG)) AS NUMERIC(18,8))/DATEDIFF(day,V.ANFANG,DATEADD(month,V.DAUER,V.ANFANG)) WHEN (V.ANFANG>='01/01/2012')AND(DATEADD(month,V.DAUER,V.ANFANG)>'31/12/2012')THEN CAST(DATEDIFF(day,V.ANFANG,'31/12/2012') AS NUMERIC(18,8))/DATEDIFF(day,V.ANFANG,DATEADD(month,V.DAUER,V.ANFANG)) END * V.BETRAG FROM VERTRAEGE AS V Betrag_2012 multipliziert den Betrag mit einem Wert. Dieser Wert ist:
Die Umwandlung des Dividenden in Numeric(18,8) hat den Hintergrund, daß sonst das Ergebnis in INT umgewandelt würde. Ist auf SQL Server 2005 getestet. Das Ergebnis ist abweichend von einer Implementierung auf Basis ganzer Monate (wenn Verträge immer am 01. beginnen würden). In diesem Fall wäre anstelle von DATEDIFF(day... DATEDIFF(month... zu verwenden. |
AW: SQL: Jahresanteile des Vertags berechnen?
Zitat:
|
AW: SQL: Jahresanteile des Vertags berechnen?
Zitat:
Das müsste die Menge der Datensätze beträchtlich reduzieren. Manche Dinge lassen sich einfach besser mit einer "richtigen" Programmiersprache berechnen und sind in SQL eine Quälerei. Wenn deine Zigarettenpause auf <= 20 Sekunden schrumpft dann würde ich bei der Clientseitigen Lösung bleiben. Auf jeden Fall solltest du vermeiden, den Restbetrag mit SQL zu berechnen. Man kann alles mit SQL machen (Quälerei) oder alles mit Delphi (langsam) oder man filtert nur alle Datensätze, die im Jahr 2012 noch laufen und bewerkstelligt die Berechnung mit einem Calculated Field (schnell & flexibel). |
AW: SQL: Jahresanteile des Vertags berechnen?
Hallo,
hier mal ein Versuch: Folgende Annahme: Die Monate werden immer vollständig berechnet und nicht taggenau. Beispiel: Für Anfangsdatum 01.08.2012 bei einer Vertragsdauer von 6 Monaten entfallen 5 Monate auf das Jahr 2012. Beispiel: Für Anfangsdatum 31.08.2012 bei einer Vertragsdauer von 6 Monaten entfallen 5 Monate auf das Jahr 2012. Für den August entfallen auf das Jahr 2012 bei einer Dauer von 1 = 1 Monat 3 = 3 Monate 6 = 5 Monate 12 = 5 Monate. Dies entspricht dem kleineren Wert von Vertragsdauer und (12 - Monat + 1). Auf das Jahr 2013 entfallen demnach 12 - diesem Wert. (Das Beispiel ist nicht getestet sondern nur im Editor so hingeschrieben. Das SQL-Statement bitte von innen nach außen lesen.)
Code:
Wenn's denn funktionieren sollte, so funktioniert es auch noch in 100 Jahren, da die Jahreszahl per Parameter an das SQL übergeben wird.
/* Die Summen für alle Verträge, bei denen der Vertrag im Jahr beginnt oder endet. */
select sum(BetragStartJahr) as BetragStartJahr, sum(BetragEndeJahr) as BetragEndeJahr from ( /* Nun die Anteile der bezahlten Beträge für das Beginnjahr und das Endejahr berechnen. */ select Anfangsdatum, Vertragsdauer, BezahlterBetrag, Monat, StartJahr, EndeJahr, DauerStartJahr, DauerEndeJahr, BezahlterBetrag / 12 * DauerStartJahr as BetragStartJahr, BezahlterBetrag / 12 * DauerEndeJahr as BetragEndeJahr from ( /* Feststellen, wieviele Monate auf das Jahr des Vertragsbeginnes entfallen */ /* bzw. auf das Jahr des Vertragsendes. */ select Anfangsdatum, Vertragsdauer, BezahlterBetrag, Monat, StartJahr, EndeJahr, min(Vertragsdauer,(12 - Monat + 1)) as DauerStartJahr, 12 - min(Vertragsdauer,(12 - Monat + 1)) as DauerEndeJahr from ( /* Zuerst Monat und Jahr des Vertragsbeginnes bzw. Endes ermitteln. */ select Anfangsdatum, Vertragsdauer, BezahlterBetrag, month(Anfangsdatum) as Monat, year(Anfangsdatum) as StartJahr, year(Anfangsdatum) + 1 as EndeJahr from testtabelle /* Eventuell hier schon das Jahr per SQL einschränken? */ /* where year(anfangsdatum) between :ParameterJahr - 1 and :ParameterJahr + 1 */ ) a ) b /* Auswahlmenge auf das gewünschte Jahr einschränken. */ where StartJahr = :ParameterJahr or EndeJahr = :ParameterJahr ) c Eventuell geht's auch noch etwas kürzer, aber nicht zwingend lesbarer (und durch redundante Berechnungen eventuell langsamer?):
Code:
/* Die Summen für alle Verträge, bei denen der Vertrag im Jahr beginnt oder endet. */
select sum(BetragStartJahr) as BetragStartJahr, sum(BetragEndeJahr) as BetragEndeJahr from ( /* Monat und Jahr des Vertragsbeginnes bzw. Endes ermitteln. */ /* Feststellen, wieviele Monate auf das Jahr des Vertragsbeginnes entfallen */ /* bzw. auf das Jahr des Vertragsendes. */ /* Die Anteile der bezahlten Beträge für das Beginnjahr und das Endejahr berechnen. */ select Anfangsdatum, Vertragsdauer, BezahlterBetrag, year(Anfangsdatum) as StartJahr, year(Anfangsdatum) + 1 as EndeJahr, BezahlterBetrag / 12 * (min(Vertragsdauer,(12 - month(Anfangsdatum) + 1))) as BetragStartJahr, BezahlterBetrag / 12 * (12 - min(Vertragsdauer,(12 - month(Anfangsdatum) + 1)) as BetragEndeJahr from testtabelle where year(anfangsdatum) between :ParameterJahr - 1 and :ParameterJahr + 1 ) a /* Auswahlmenge auf das gewünschte Jahr einschränken. */ where StartJahr = :ParameterJahr or EndeJahr = :ParameterJahr |
AW: SQL: Jahresanteile des Vertags berechnen?
Ich glaube, es geht kürzer:
SQL-Code:
---
--- Testumgebung ---------------------------------------------------------- --- USE [Test] GO CREATE TABLE [dbo].[Contracts]( [ctID] [int] IDENTITY(1,1) NOT NULL, [ctStart] [smalldatetime] NOT NULL, [ctDurationMonths] [int] NOT NULL, [ctPrice] [money] NOT NULL, [soll] [int] NULL ) ON [PRIMARY] declare @startPeriod dateTime declare @EndPeriod DateTime set @startPeriod='20120101' set @EndPeriod = '20121231' --- Abfrage --------------------------------------------------------------------------- select *, ctPrice*DateDiff(Month, dmin,dmax)/ctDurationMonths as PriceInPeriod from ( select *, (select max (X) from (values (@StartPeriod), (ctStart)) as T(X)) dMin, (select min (X) from (values (@EndPeriod), (DateAdd(Month, ctDurationMonths, ctStart))) as T(X)) dMax ----------*****---- Hier war ein Tippfehler. Danke tgvoelker! from contracts ) x where DateDiff(Month, dmin,dmax)>0 -------------------------------------------------------------------------------------- |
AW: SQL: Jahresanteile des Vertags berechnen?
Zitat:
Was ist das Ergebnis, wenn der Vertrag am 01.12.2011 anfängt und 3 monate läuft? :> |
AW: SQL: Jahresanteile des Vertags berechnen?
Moin,
Danke fürs Testen. Da war noch ein Tippfehler beim Refaktorisieren. Hier die korrekte Version (war ja zu deutlich):
SQL-Code:
select *, ctPrice*DateDiff(Month, dmin,dmax)/ctDurationMonths as PriceInPeriod
from ( select *, (select max (X) from (values (@StartPeriod), (ctStart)) as T(X)) dMin, (select min (X) from (values (@EndPeriod), (DateAdd(Month, ctDurationMonths, ctStart))) as T(X)) dMax -----------****----- hier stand 'max' from contracts ) x where DateDiff(Month, dmin,dmax)>0 |
AW: SQL: Jahresanteile des Vertags berechnen?
Die Lösung in #20 ist gut! Lob!
Die Datumermittlung ist pfiffig, mit dem Subquerys und values. Anfangs- und Ende-Überlappungen berücksichtigt und natürlich den Einschluss-Zeitraum. Funktionieren kann es nur nicht wenn der Vertrag länger als 12 Monate laufen könnte, dass ist hier ja aber nicht Anforderung gewesen. Man könnte die Selects noch in eine UDF auslagern. ![]() |
AW: SQL: Jahresanteile des Vertags berechnen?
Zitat:
|
AW: SQL: Jahresanteile des Vertags berechnen?
generic, FBs Source funktioniert durchaus, wenngleich mir der SQL Server 2012 zum testen fehlt. Logisch ist das einwandfrei, sofern Verträge immer nur zum 1. beginnen können:
* Als erstes wird für jede Zeile entweder der 1.1.2012 oder das Anfangsdatum zurückgegeben, je nachdem, was größer ist * dann wird das Enddatum oder der 31.12. zurückgegeben, je nachdem, was kleiner ist. * dann fliegen alle die raus, bei denen nicht mindestens 1 Monat (Rundungsverhalten DateDiff? - bei Verträgen, die mitten im Monat beginnen) zwischen Anfang und Ende liegt. Wenn Anfang nach dem 31.12.2012, dann fliegt der Tupel raus, weil dmax<dmin Wenn Ende vor dem 1.1.2012, dann fliegt der Tupel raus, weil dmax<dmin Wenn Anfang nach dem 1.1.2012 und Ende davor, dann ist dmin der richtige Anfang und dmax das richtige Ende. Wenn Anfang vor dem 1.1.2012 und Ende zwischen 1.1.und 31.12., dann ist dmin 1.1. und dmax das richtige Ende Wenn Anfang zwischen 1.1. und 31.12. und Ende danach, dann ist dmin der richtige Anfang und dmax 31.12. Paßt also alles. Ich würde das zwar anders machen (s.o.), weil ich meist alle Tupel haben und bei denen, die nicht betroffen sind, eine 0 drinstehen haben möchte, aber das ist kosmetisch. |
AW: SQL: Jahresanteile des Vertags berechnen?
Wie gesagt, war nicht Anforderung.
Wenn ein Vertrag länger als 12 Monate wäre, dann würde die Daten raus fallen. Wie du schreibst. Anteilig müsste so ein Vertrag aber mit 12 Monatsbeiträgen berechnet werden. Kommt nun auf das Verwaltungssystem an, es ist aber normal das ein Vertrag immer für 1 Jahr geschlossen wird und dann nur pro Jahr verlängert wird bzw. erneuert wird. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:01 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz