Quantcast
Channel: SQL – SQLBlog Nederland
Viewing all 15 articles
Browse latest View live

Centraliseren van KPI’s in je Datawarehouse

$
0
0

Je kent het wel. Je maakt schitterende rapportages met de mooiste cijfers, omzetten, productiviteit en KPI’s. Door de tijd komen er steeds meer rapportages bij binnen de organisatie. Tot iemand ineens opmerkt dat de waarden van KPI’s niet gelijk zijn tussen de verschillende rapportages. Oops! In dit artikel bespreek ik een manier om te probleem het hoofd te bieden. Het is een best-practice die is ontstaan door veel ervaring met definities en KPI’s.

Veel mensen programmeren hun formules binnen hun rapportage. Een groot nadeel hiervan is, buiten performance om, dat de formules niet centraal zijn opgeslagen. Stel dat de definitie van een bepaalde formule door de tijd wijzigt (en dit gebeurt veel) dan moet je alle rapportages langs om de formules aan te passen! Een hels karwei. Een methode om dit te voorkomen is door de formules te centraliseren. Hoe dat moet leg ik hier uit.

Ik heb een Datwarehouse met historie (een soort Data Vault) en daarachter een Datamart/stermodel waarin alle business logica is geprogrammeerd. In de laatste zijn de verschillende feiten en dimensies te vinden. Wat ik wil is een centrale plek voor mijn KPI definities. Wat ik graag doe is werken met een SQL-functie waarin ik alle KPI formules programmeer. Iedere KPI krijgt een eigen nummer en een definitie. Vervolgens kan ik de functie met een begin- en einddatum en een KPI-ID. De functie retourneert dan het gewenste resultaat. Voorwaarde is wel dat de grain van de kpi’s gelijk is.

Schematisch ziet deze manier er zo uit:

 

Links vind je het Datawarehouse en rechts de datamart. Binnen de datamart vind je verschillende feiten. De functie is opgeslagen in de datamart en berekent zijn resultaten op de feiten. Het is dus belangrijk dat alle feiten en dimensie gevuld zijn voordat de functie werkt. Achter de functie vind je de FactKPI. Hierin slaan we het resultaat op van de functie.

Het idee is dat je in de ETL eerst het DWH bijwerkt, dan de Datamart en als laatste stap van het bijwerken van de datamart de KPI tabel.

Handig? Jazeker. Alle formules staan in de functie. Als er een kpi formule wijzigt pas je de functie aan. Dit is veel minder werk dan alle rapporten langslopen. Alle rapporten maken namelijk gebruik van de KPI tabel en niet meer van de “normale” facttables (indien mogelijk).

Voor het vullen van de KPI tabel roep je meerdere keren de functie aan, per KPI, en merge dit resultaat aan elkaar met een UNION.

De grain van de KPI tabel is in mijn geval: DatumID, MedewerkerID, KostenPlaatsID. Daarna volgen er allemaal kolommen met de berekende KPI’s.

Ik moet zeggen, dit werkt erg goed in de praktijk. Het voorkomt veel gedoe en tijd. Zorg ervoor de er in de functie die je gebruikt genoeg commentaar staat zodat helder is wat de definitie is van de KPI’s.

Mocht je ook een goede methode hebben om hiermee om te gaan, deel je ervaring!

Grts. Ronald


Nederlandse feestdagen in SQL Server

$
0
0

Er is helaas niet een standaard functie in SQL Server om te bepalen of een bepaalde datum een feestdag is. Om dit te bepalen is het handig om gebruik te maken van een datumdimensie zoals hier uitgelegd.

Vervolgens kun je eenvoudig zien of iets een feestdag is:

SELECT *
FROM dbo.DimDatum
WHERE IsFeestDag_NL = 1

Alle feestdagen in 2012:

Op dezelfde manier kun je de dagnaam in het Nederlands ophalen voor een bepaalde datum:

Dichtstbijzijnde werkdag uitrekenen

$
0
0

Ik had vorige week een leuke uitdaging. In de KPI tabel die ik gebruik voor bepaalde rapportages zijn enkel de normale werkdagen uitgerekend, maandag t/m vrijdag. Feestdagen komen ook voor. De weekenden zijn hier bewust uitgelaten ivm FTE tellingen die anders niet lekker lopen.

Bij het uitrekenen van KPI’s kwam ik in de problemen omdat bepaalde KPI’s waarden vanuit de bronsystemen op de weekenden werden ingevoerd. De keus was nu, op welke dag gaan we deze waarden plaatsen? Mijn eerste idee was om ze gewoon op de vrijdag ervoor te zetten. Dit is niet altijd juist omdat de vrijdag ervoor ook in een andere maand/jaar kan vallen :-) Mijn rapportage wordt op maand gedraaid dus dit is ook geen goede oplossing.

Voor dit probleem kon ik voordat ik de data in de fact ging laden een ingewikkelde SQL maken om de juiste dag te vinden waarop de waarde gezet moest worden. Wat een betere oplossing zou zijn was om de juiste datum al vooraf te bepalen en op te slaan in de Datumtabel. Iedere datum krijgt dan een “BoekDatumID”.  De defintie van dit veld is dat het de dichtsbijzijnde werkdag moest zijn die in dezelfde maand/jaar viel en geen feestdag moet zijn.

Nu ben je natuurlijk benieuwd hoe de SQL eruit zien om de juiste werkdag te vinden. Deze is betrekkelijk eenvoudig maar je moet er wel even opkomen!

Uitleg: er vindt een explosie plaatst op datum waarbij per datum 10 dagen ervoor en 10 dagen erna worden geselecteerd die in dezelfde maand/jaar vallen. Vervolgens wordt de dichtstbijzijnde dag geselecteerde met RowNumber = 1.

Easy?!?! Dit resultaat gebruik je om je datumtabel bij te werken. Zie bijlage voor de SQL

Drag

Datetime-veld omzetten naar een Integer

$
0
0

In Datawarehouseland gebruiken we in onze Datum-Dimensie integers als primaire sleutels voor de datum. Zo slaan we de datum “2008-12-01 00:00:00.000″ op als  “20081201″. Hierdoor kun je vanuit je feitentabel een snelle join leggen naar je datumdimensie, joins op integers gaan nou eenmaal sneller dan joins op datetime velden. 

Datums liggen in bronsystemen vaak opgeslagen in het DateTime-formaat. We zullen deze DateTime dus moeten converteren naar een integer. Dit kan met het volgende T-SQL Statement:

SELECT YEAR(GETDATE())*10000+MONTH(GETDATE())*100+DAY(GETDATE()) AS DatetimeToInt

GETDATE() vervang je door het datumveld dat je wilt omzetten naar een integer.

Een andere methode is de volgende, deze is in de praktijk iets langzamer:

SELECT convert(varchar,dateadd(yy,-2, getdate()),112)

Je kunt je ook voorstellen dat je dit veld weer wilt gebruiken als een normaal datumveld, bijvoorbeeld in een rapportage in Reporting Services. Dit kun je eenvoudig realiseren door onderstaande functie aan te maken:

CREATE FUNCTION [dbo].[FromDateIDtoDateValue](@Date varchar(8))
RETURNS datetime
AS
 
DECLARE @FunctionResult datetime
 
IF isdate(@Date) = 1 SET @FunctionResult = cast(@Date as datetime)
Else SET @FunctionResult = null
 
RETURN(@FunctionResult)
END

Vervolgens kun je deze functie aanroepen:

SELECT
dbo.FromDateIDtoDateValue(20081201)

Het bericht Datetime-veld omzetten naar een Integer verscheen eerst op SQLBlog Nederland.

Recursieve Query met CTE I

$
0
0

Ik liep onlangs tegen een vraagstuk aan die ik wel interessant vond om op mijn blog te plaatsen. Het behandelt een veelvoorkomende situatie waarbij je een dataset meerdere keren gebruikt, ook wel een recursieve query genoemd. Men vroeg het aantal gewerkte weken per periode per uitzendkracht.

Per periode kan iemand meerdere declaraties hebben waarbij het getal “weken gewerkt” wordt bijgehouden. Om te bepalen hoeveel weken iemand in een periode heeft gewerkt pakken we de laatste “weken gewerkt” in een periode en trekken daar de laatste “weken gewerkt” uit de vorige periode vanaf, klinkt simpel toch?

Onderstaande afbeelding toont het resultaat wat we als basis gaan gebruiken om “weken gewerkt” per periode vast te kunnen stellen:

1

We hebben onze data gegroepeerd op jaar, Periode444Nummer, krachtcode en pakken de MAX(WekenGewerkt) per periode. Als we nu willen weten hoeveel weken iemand heeft gewerkt in periode 6-2008 moeten we 76-72 uitvoeren. Iemand heeft tenslotte aan het einde van periode 6-2008 76 weken gewerkt en aan het einde van periode 5-2008 72 weken gewerkt. Dit verschil is wat hij in periode 6 heeft gewerkt.

2

Om dit alles mogelijk te maken genereren we bovenstaande standaard dataset die we vervolgens kunnen hergebruiken, ook wel Common Table Expression genoemd (CTE). Ook voegen we een rijnummer toe per krachtcode en sorteren dit op krachtcode, jaar en periodenummer. Verder hebben we ook het vorige jaar nodig om te kunnen bepalen hoeveel weken iemand gewerkt heeft in de eerste periode van het huidige jaar! Dit zorgt voor een resultaat dat er ongeveer zo uitziet:

3
De sortering zorgt ervoor dat alles netjes in volgorde staat zodat we straks eenvoudig de weken van elkaar kunnen aftrekken!

Vervolgens maken we onderstaande query en voeren dit op de eerdere gegenereerde dataset uit:
4_2

De kracht zit hem in het stukje met de cirkel eromheen. Hierbij joinen we de tabel op zichzelf zodat we het resultaat van de vorige week gewerkt kunnen vinden. Dit trekken we van de huidige week af en vinden zo het resultaat.

De complete query ziet er ongeveer zo uit:

5
Uiteraard kun je dit voorbeeld in veel meer situaties gebruiken, bijvoorbeeld voor het berekenen van cumulatieven.

Het bericht Recursieve Query met CTE I verscheen eerst op SQLBlog Nederland.

Query Active Directory

$
0
0

Het kan handig zijn om data uit je Active Directory (AD) op te nemen in je Datawarehouse. In mijn situatie was het handig omdat de klant graag wilde zien of een gebruiker die uit dienst was nog steeds in AD bestond. Deze koppeling had men op dit moment niet, de helpdesk moest dus handmatig van alles controleren.


De oplossing is gezocht in de integratie van AD-data in het Datawarehouse. Is het mogelijk om Active Directory te Queryen vanuit SQL Server Management Studio? Het antwoord daarop is JA. Of het moeilijk is kan ik ook een antwoord geven, NEE. Maar zoals alle dingen die makkelijk zijn, je moet even weten hoe het moet. Zoek je op Google op “Qeury Active Directory” dan zul je veel resultaten krijgen. Hoe je het precies moet doen en waar je op moet letten zal ik hieronder beschrijven.

Stap 1 Add Linked-Server
Active Directory is voor SQL Server een externe bron. Je zult dus een Linked-Server object moeten aanmaken in je SQL Server Management Studio. Daarna is het van groot belang om de Linked-Server een gebruikersnaam en wachtwoord mee te geven welke toegang heeft tot Active-Directory. Dit kan bijvoorbeeld je eigen gebruikersaccount zijn maar wellicht heb je een standaard account waarop je SQL-Services al draaien dus gebruik die dan ook!

Voer onderstaande statement-in (je hoeft niets aan te passen):

sp_addlinkedserver 'ADSI', 'Active Directory Service
Interfaces', 'ADSDSOObject', 'adsdatasource

Ik dacht eerst dat je de laatste term ‘adsdatasource’ moest veranderen in je eigen datasource, dit hoeft dus niet!

 

Je hebt nu een Linked-Server aangemaakt, de connectie tussen je SQL-Server en Active Directory. Nu is het belangrijk de credentials (username/password) mee te geven aan de Linked-Server. Klik me je rechtermuis op de Linked-Server die je net hebt aangemaakt (ADSI) en klik op “Properties”. Let even op dat je sysadmin moet zijn om de Linked-Server properties in te kunnen stellen.

 

Klik op het “Security-Tabblad” en vul onderin de credentials in waarmee de Linked-Server toegang heeft tot Active Directory, bijvoorbeeld DOMAINUSER . Doe je dit niet zul je AD niet kunnen Queryen! Je krijgt dan een foutmelding die hierop lijkt:
“……..for execution against OLE DB provider “ADSDSOObject” for linked server “ADSI”.”

Stap 2 Query Active Directory
Nu is het tijd om een Qeury af te vuren op de Linked Server. Even ter notitie, AD werkt met zogenaamde “Pages” die hij teruggeeft als resultaat van een Qeury. AD geeft maximaal 1000 rijen terug per Qeury die je afvuurt.

 

SELECT * FROM OpenQuery(ADSI, 'SELECT PwdLastSet, GivenName, sn,
sAMAccountName, cn, mail, displayName, department, telephoneNumber,
streetAddress, st, distinguishedName, physicalDeliveryOfficeName,
userPrincipalName, mailNickname, l, postalCode, msExchHomeServerName,
whenCreated, whenChanged, userAccountControl FROM ''LDAP://dc=nl,dc=mycompany,dc=com''
WHERE objectClass=''User'' AND objectCategory=''Person'' ')

** De opbouw van LDAP:// is:DC=ldap-server,DC=my-company,DC=com
Bijvoorbeeld: LDAP://dc=nl,dc=mycompany,dc=com

Uiteraard zit er wel meer in AD dan enkel de gebruikers. Wat je kunt doen om je dataset te verkleinen is nog enkele voorwaarden opnemen in je Query. Je wilt bijvoorbeeld alleen de AD-accounts hebben die beginnen met 000000. Je statement ziet er dan ongeveer zo uit:

SELECT * FROM OpenQuery(ADSI, 'SELECT PwdLastSet, GivenName, sn, 
 
sAMAccountName, cn, mail, displayName, department, telephoneNumber,
streetAddress, st, distinguishedName, physicalDeliveryOfficeName,
userPrincipalName, mailNickname, l, postalCode, msExchHomeServerName,
whenCreated, whenChanged, userAccountControl FROM ''dc=nl,dc=mycompany,dc=com''
WHERE objectClass=''User'' AND objectCategory=''Person'' AND cn = ''000000*''')

Vervolgens kun je meerdere resultaten aan elkaar gaan plakken door gebruik te maken van een UNION. Je wilt bijvoorbeeld alle account die beginnen met een 1, daarna alle account met een 2, etc.

SELECT * FROM OpenQuery(ADSI, 'SELECT PwdLastSet, GivenName, sn,
sAMAccountName, cn, mail, displayName, department, telephoneNumber,
streetAddress, st, distinguishedName, physicalDeliveryOfficeName,
userPrincipalName, mailNickname, l, postalCode, msExchHomeServerName,
whenCreated, whenChanged, userAccountControl FROM ''dc=nl,dc=mycompany,dc=com''
WHERE objectClass=''User'' AND objectCategory=''Person'' AND cn = ''000001*''')
UNION ALL
SELECT * FROM OpenQuery(ADSI, 'SELECT PwdLastSet, GivenName, sn,
sAMAccountName, cn, mail, displayName, department, telephoneNumber,
streetAddress, st, distinguishedName, physicalDeliveryOfficeName,
userPrincipalName, mailNickname, l, postalCode, msExchHomeServerName,
whenCreated, whenChanged, userAccountControl FROM ''dc=nl,dc=mycompany,dc=com''
WHERE objectClass=''User'' AND objectCategory=''Person'' AND cn = ''000002*''')

Nog een opmerking op het veld UserAccountControl. Dit veld geeft de status van een AD-User aan. De volgende combinaties kwamen bij mij voor:

512: Normaal account
514: Account disable (512 + 2)
544: Password not required (512 + 32)
546: Disabled + Password not required
66048: Normal Account + Password verloopt nooit
66080: Normal Account + Password verloopt nooit + Password not Required

Meer informatie vind je hier.

De datumvelden die AD wegschijft zijn opgeslagen in het formaat: 100 nanosecond intervals since January 1, 1601 (UTC). Ik heb me rot gezocht hoe ik dit kan omzetten naar een datetimeveld in SQL. Uiteindelijk vond ik een handige functie:

 

CREATE FUNCTION [dbo].[SystemTimeToDateTime] (
@biFileTime BIGINT
)
RETURNS datetime AS
BEGIN
DECLARE @Ret AS datetime
DECLARE @ms AS BIGINT
DECLARE @MIN AS INT
DECLARE @DAY AS INT
 
SET @ms = (@biFileTime - 94354848000000000)/10000
IF (@ms < 0) RETURN CAST(0 AS datetime)
SET @MIN = CAST(@ms/60000 AS INT)
SET @DAY = CAST(@MIN/1440 AS INT)
 
SET @Ret = DATEADD(ms,
CAST(@ms%60000 AS INT),
DATEADD(mi,
CAST(@min%1440 AS INT),
DATEADD(dd,
@DAY,
CAST(0 AS datetime)
)
)
)
RETURN @Ret
END

Wanneer je meerdere domain controllers hebt zul je problemen hebben met het veld LastLogon. Als je namelijk een query draait op het hele domeinn zal hij een willekeurige Last Logon pakken. De Last Logons worden namelijk niet goed gerepliceerd. Een oplossing is de specifieke domain controllers te queryen en dan het MAXIMALE datumveld te pakken, in dit geval de meest recente Last Logon!

 

 FromSystemTimeToDateTime

Het bericht Query Active Directory verscheen eerst op SQLBlog Nederland.

Dubbele rijen vinden

$
0
0

Iedere rij in een tabel wordt gekenmerkt door een primaire sleutel. Nu komt het nog weleens voor dat in je systeem de sleutel twee keer voorkomt. Dit mag eigenlijk niet. Met deze simpele Query kun je snel dubbele rijen vinden in je tabel!


Onderstaande Query toont aan hoe je dubbele medewerkers kunt vinden, gebaseerd op EMPLOYEENUMBER. Dit zou de primaire sleutel moeten zijn uit de bron.

SELECT DISTINCT
EMPLOYEENUMBER
FROM    EMPLOYEES
GROUP BY EMPLOYEENUMBER
HAVING  COUNT(EMPLOYEENUMBER) > 1<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;"> </span>

Vervolgens wil je deze dubbele nummers vaak niet meenemen bij het laden van je data. Met onderstaande QUERY kun je dubbele medewerkers filteren.

SELECT  *
FROM    (
SELECT EmployeeNumber,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEENUMBER ORDER BY EMPLOYEENUMBER ASC) AS Rij
FROM   EMPLOYEES AS LM
) AS X
WHERE   X.Rij <= 1

Wat we hier doen is een rijnummer toevoegen per uniek EmployeeNumber. Als een nummer twee keer voorkomt krijgt deze rijnummer 2. Vervolgens halen we alle rijen met het getal > 1 weg!

Het bericht Dubbele rijen vinden verscheen eerst op SQLBlog Nederland.

Rekenen met datums

$
0
0

Rekenen met datums kan in SQL Server 2005 op verschillende manieren. SQL Server 2005 kent verschillende functies die je kunt gebruiken als je met datums gaat werken. Dit artikel beschrijft een aantal praktische voorbeelden die je in de praktijk zult tegenkomen als je met databases en datums werkt.

Zo wordt beschreven hoe je terug kunt rekenen in de tijd, hoe je het verschil in dagen tussen diverse datums kunt uitrekenen en hoe je de maand kunt bepalen in een datum.

DATEADD()

Deze functie gebruik je om een datum te bepalen die voor of na de opgegeven datum ligt. Je wilt bijvoorbeeld weten wat de datum is die 2 maanden na vandaag ligt of je wilt de datum weten die twee dagen voor 1 augustus ligt.
Syntax
DateAdd(datumgedeelte, getal, datumwaarde)Voorbeelden
De datum van overmorgen
SELECT DATEADD(dd, 2, GETDATE () )

In bovenstaand voorbeeld zeggen we dat hij in dagen moet rekenen (dd), de 2 geeft aan dat hij 2 dagen vooruit moet rekenen. Het derde argument geeft de datum vanaf wanneer hij moet rekenen, in dit geval vandaag (GETDATE() ). Bij GETGATE() kun je ook een datum meegeven, bijvoorbeeld ’2008-12-05′ . Let op dat dit tussen quotes moet staan!

De datum van twee maanden geleden

SELECT DATEADD(mm, -2, GETDATE () )

mm geeft maanden aan. -2 geeft aan dat hij 2 maanden terug moet gaan en de GETDATE() geeft de datum van vandaag aan.

DATEPART()

Met deze functie kun je de dag, maand, jaar en tijd bepalen in een datumwaarde. Je wilt het maandgedeelte van de datum 2008-12-05 hebben, in dit geval dus 12. Je kunt ook de dag nodig hebben van 12 augustus 2007, de 12 dus.

Syntax
DatePart(datumgedeelte, datumwaarde)

Voorbeelden

De dag van vandaag

SELECT DATEPART(dd,GETDATE() )

Het jaarnummer van vandaag

SELECT DATEPART(yy,GETDATE() )

DATEDIFF()

Datediff gebruik je om een interval te berekenen tussen twee datums. Zo kun je het aantal dagen bepalen dat tussen twee datums ligt.

Syntax

DateDiff(datumgedeelte, datumwaarde1, datumwaarde2)

Voorbeelden

Het aantal dagen verschil tussen 1 december 2008 en vandaag (5 december 2008)

SELECT DATEDIFF(dd,'2008-12-01',GETDATE() )

DATENAME()

Datename lijkt erg op datepart. Het verschil is dat deze functie een string retourneerd (stukje tekst) en datepart retourneerd een nummer. Deze functie kan de dag van de week in tekst retourneren of de naam van de maand.

Syntax

DateName(datumgedeelte, datumwaarde)VoorbeeldenDe dagnaam van vandaag
SELECT DATENAME (dw, GETDATE() )

Het dagnummer van het jaar van vandaag

SELECT DATENAME (dy, GETDATE() )

Hieronder een tabel met waarden die je kunt invoeren als eerste argument:

Een beperking van de functie is dat de functie de engelse namen retourneerd. Ik raad daarom aan altijd gebruik te maken van een datumdimensie. Het grote voordeel van een datumdimensie is dat je voor iedere datum weet in welke week hij valt, welk jaar, welk kwartaal, etc. Daarbij moet je er rekening mee houden dat 1 januari in week 52 van het vorige jaar kan vallen. Het jaar van 1 januari is in dit geval bijvoorbeeld 2008 maar het jaar van het weeknummer is 2007.

Het bericht Rekenen met datums verscheen eerst op SQLBlog Nederland.


After update trigger

$
0
0

Soms is het handig om een mutatiedatum of mutatieuser bij te houden op een veld, met name in een master data omgeving. Je kunt hiervoor gebruik maken van een trigger op een tabel. Dit moet zowel werken als er nieuwe data wordt ingevoerd als wanneer er data wordt bijgewerkt.

CREATE TRIGGER [dbo].[tblTrigger_AfterUpdate] ON [dbo].[REF_KlantNorm]
AFTER UPDATE, INSERT
AS
 
UPDATE KN
SET MetaMutationDate = GETDATE()
, MetaMutationUser = SYSTEM_USER
FROM dbo.REF_KlantBudget AS KN, INSERTED AS i
WHERE i.DebiteurenMapCode = KN.DebiteurenMapCode
AND i.BranchCode = KN.BranchCode
AND i.Jaar = KN.Jaar

Deze trigger update de datum/user van klanten die zijn bijgewerkt (die voorkomen in de Inserted tabe/Updatedl). Let op dat de join op sleutel wordt uigevoerd.

Het bericht After update trigger verscheen eerst op SQLBlog Nederland.

Calculate time in hours and minutes between two dates

$
0
0

 Sounds easy right? I want to calculate the difference in hours and minutes between two dates. A good readable notition for my report. I want a format like this:

00:36:53 OR 02:13:45

This can easy be done using a T-SQL Statement like this:

CONVERT(nvarchar(64), EindDatum-StartDatum, 108) AS Duration

So how does this look like in SQL Server?

T-SQL_Calculate_Difference_In_Minutes_Between_Two_Dates

Het bericht Calculate time in hours and minutes between two dates verscheen eerst op SQLBlog Nederland.

Hoe maak ik een datum dimensie?

$
0
0

 Datum dimensies zijn enorm handig. Door gebruik te maken van een datum dimensie / datumtabel kun je voor een datum bepalen in welke week deze datum valt, welk jaar, welke maand, of het een feestdag is, etc. Dit artikel laat zien wat je met een datum dimensie kunt doen. Ook kun je een script downloaden om zelf een datum(tabel)/ dimensie aan te maken.

Een datum dimensie is niets meer of minder dan een datumtabel. Iedere datum in het jaar is een aparte regel. Daarbij vind je een aantal kolommen die voor die dag laten zien in welke weerk hij valt, de maand, kwartaal en nog veel meer. Zo is er ook een kolom aanwezig die aangeeft of een datum een feestdag is. Zo kun je snel tellingen doen voor feestdagen.

Let op; dit is een voorbeelddimensie. Je kunt hem uiteraard zelf verfraaien met velden die voor jou belangrijk zijn!

dd dimdatum Hoe maak ik een datum dimensie?

Als we nu de datumdimensie aanmaken in de AdventureWorks database (dus niet het dwh van adventureworks) kun je de kracht zien. We gaan de datumdimensie joinen aan de OrderDate. Vervolgens kunnen we eenvoudig meerdere eigenschappen voor deze datum vinden!

SELECT TOP 100
SOH.SalesOrderID,
SOH.OrderDate,
DD.DatumId,
DD.WeekNummer,
DD.MaandNummerVanJaar,
DD.Kwartaal445Code,
DD.Kwartaal445Nummer        
FROM Sales.SalesOrderHeader AS SOH
-- Hier zetten we de OrderDate om naar een integer en joinen hem met de Datum-dimensie DimDatum
LEFT OUTER JOIN DimDatum AS DD ON YEAR(SOH.OrderDate) * 10000 + MONTH(SOH.OrderDate) * 100 + DAY(SOH.OrderDate) = DD.DatumId

dd adventureworks Hoe maak ik een datum dimensie?

Wat ook handig is voor Excel PowerPivot is om de datumtabel te laden als tabblad. Je kunt hem dan ook in Excel PowerPivot gebruiken.

Nog een voorbeeld, geef me alle feestdagen in SQL Server voor 2013:

feestdagen 2013 Hoe maak ik een datum dimensie?

 

Als je bedrijfsspecifieke feestdagen hebt en je weet die vooraf kun je die ook aangeven in deze tabel. Dit is handig als je bepaalde berekeningen wilt doorvoeren en rekening wilt houden met feestdagen.

Als bijlage bij dit artikel tref je een script aan om de datumdimensietabel te maken. Daarna een MS SQL Server stored procedure om de tabel te kunnen vullen.

Eventueel kan ik ook een script maken die de tabel maakt en gelijk de data insert.

Als je specifieke wensen hebt maar niet de technische kennis, kijk op mijn bedrijfspagina.

 

Het bericht Hoe maak ik een datum dimensie? verscheen eerst op SQLBlog Nederland.

Calculate Lost Customers using T-SQL

$
0
0

A common question in the Business Intelligence world from customers is “how many customers did we lose this year” ? Businesses wants to know this so they can act faster to keep customers doing business with them. In this article, I will help you to create SQL to calculate this number. On the internet I have found more articles but most time it will explain how to do this in MDX. I really don’t like MDX. It’s complicated and hard to understand. I like OLAP cubes but I also like to keep things simple. By simple I mean keep all logic in SQL and calculate kpi’s in SQL and write them in a table.

The first step in the calculation of lost customers is to determine the definition of a lost customer. For example; a lost customer is a customer who hasn’t received any invoice this calendar year BUT received an invoice last calendar year. You have to be very explicit with this definition by using timeframes and criteria. Maybe you want a rolling 52 weeks period for the invoice time? In this example, I will take calendar year.

By using the definition from above, all customer are lost in the beginning of the year because they didn’t receive any invoice yet in the first month of the year. This is fine. The line will go down when weeks or months passes by. The outcome of the formula will look something like this (chart created in SQL Server Reporting Service 2008R2);

Lost Customer TSQL Calculate Lost Customers using T SQL

The first step is to determine a reference point for the calculation. I use the first day of the month . You can also do a calculation every week of every day if you want. This is important because you want to count number of lost customers by month or by week in your chart.

Now let’s jump to the SQL. It may look a bit complicated but it isn’t. The comments are in Dutch but I will explain the principles here.

Step 1 – Generate a list of all customer/DateID combinations, bases on the invoice date

Step 2 – Generate an “explosion” of all customers and first-day-of-the-month combination

Step 3- Combine step 1 and step 2 and determine logic if the customer is lost or not. We check if the customer received an invoice, prior to the date you look at but in the same year and in the WHERE clause, we check if it has received an invoice last year. If not, it’s lost. Sounds easy right?

Now here comes the code. If you got questions, leave a comment. This code is written on Microsoft Dynamix AX for one of my clients. It costed me maybe 2 hours to create it. A little investment for the client but really handy!

WITH KL
AS
-- Genereer een overzicht van alle datum/klant combinaties
 (SELECT DISTINCT DK.ACCOUNTNUM AS KlantID
 , CONVERT(VARCHAR, FH.InvoiceDate, 112) AS DatumID
 , FH.InvoiceDate AS Datum
 , DD.Jaar
 , FR.LINEAMOUNT AS Bedrag
 FROM dbo.CUSTINVOICETRANS AS FR -- FactuurRegel
 
 INNER JOIN dbo.CUSTINVOICEJOUR AS FH -- FactuurHeader
 ON (FR.INVOICEID = FH.INVOICEID)
 
 INNER JOIN dbo.CUSTTABLE AS DK -- Klant
 ON (FH.ORDERACCOUNT = DK.ACCOUNTNUM)
 
 INNER JOIN dbo.SMMBUSRELSALESDISTRICTGROUP AS SD -- SalesDistrict
 ON (DK.SALESDISTRICTID = SD.SALESDISTRICTID)
 AND (SD.DATAAREAID = 'pp')
 
 INNER JOIN DimDatum AS DD -- DimDatum
 ON ( CONVERT(VARCHAR, FH.INVOICEDATE, 112) = DD.DatumId) 
 
 INNER JOIN INVENTTABLE AS DP -- DimProduct
 ON (FR.ITEMID = DP.ITEMID) 
 
 WHERE FR.DATAAREAID = 'pp'
 AND FH.DATAAREAID = 'pp'
 AND DK.DATAAREAID = 'pp'
 AND DP.DATAAREAID = 'pp'
 ) 
 
-- Genereer een lijst van alle klanten en eerste dag van maand-combinaties
-- voor alle dagen in het huidige jaar en in het vorige jaar.
-- Later gaan we voor deze datums bepalen of de klant verloren is op deze datum
, KD -- KlantDatum
AS (SELECT DISTINCT ACCOUNTNUM AS KlantID, 
 DK.NAME AS KlantNaam,
 SD.[DESCRIPTION] AS SalesDistrict,
 DD.DatumId,
 DD.DatumWaarde,
 DD.Jaar
 FROM CUSTTABLE AS DK
 INNER JOIN SMMBUSRELSALESDISTRICTGROUP AS SD
 ON (DK.SALESDISTRICTID = SD.SALESDISTRICTID)
 CROSS JOIN DimDatum AS DD
 
 WHERE DD.Jaar IN ( YEAR(DATEADD(YY,-1,GETDATE())), YEAR(GETDATE()) )
 AND DD.DagNummerVanMaand = 1
 AND DD.DatumId <= CONVERT( VARCHAR, GETDATE(), 112)
 -- evt aanzetten als je het hele jaar wilt tonen, ook de toekomst
 -- AND DD.Jaar <= YEAR(GETDATE())
 
 )
 
SELECT DISTINCT KLD.KlantID
 , KlantNaam
 , KLD.SalesDistrict
 , KLD.DatumId
 , DD.Jaar
 , DD.MaandNummerVanJaar
 , DD.MaandCode
 , (CASE WHEN KL.KlantID IS NULL THEN 1 ELSE 0 END) AS IsVerloren
FROM KD AS KLD
-- Met deze join controleer je of de klant in het huidige jaar gefactureerd is op een tijdstip
-- die voor de datum ligt waarop je kijkt. Als dit niet het geval is dan is de klant op dit moment
-- verloren. Op het moment dat hij gefactureerd is dan is hij niet meer verloren
LEFT OUTER JOIN KL AS KL ON KLD.KlantID = KL.KlantID
 AND KL.DatumID <= KLD.DatumId
 AND KLD.Jaar = KL.Jaar
 
INNER JOIN DimDatum AS DD
 ON (KLD.DatumId = DD.DatumId)
 
-- Controleer of hij vorig jaar gefactureerd is
WHERE EXISTS ( SELECT 1 FROM KL AS VJ WHERE VJ.Jaar= KLD.Jaar-1 AND KLD.KlantID = VJ.KlantID)
 
ORDER BY 1,2

Het bericht Calculate Lost Customers using T-SQL verscheen eerst op SQLBlog Nederland.

Script alle databases en objecten

$
0
0

In navolging op een voorgaand artikel over het scripten van je databaseobjecten heb ik een stored procedure gemaakt die automatisch alle databases voor je scripts en het script wegschrijft naar een folder. Dit script is gemaakt in SQL Server 2005. Dit is handig omdat je nu alle objecten zoals tabellen, stored procedures, views en functies in een script hebt.

CREATE PROCEDURE [dbo].[sp_GenDBscript] @vcDatabase AS VARCHAR(50)
AS
/*
Getest 27-7-2011 met de volgende settings
C:\Program Files (x86)\Microsoft SQL Server\90\Tools\Publishing\1.2>sqlpubwiz
e script -schemaonly -d DWH_NL_REF -S MSINTFR07666\BENL_DWH_1 c:\script.sql
 
Example:
EXEC dbo.[sp_GenDBscript] 'DWH_NL_REF'
*/
 
DECLARE @vcPath AS VARCHAR(150)
DECLARE @vcCMD AS VARCHAR(150)
DECLARE @vcParam AS VARCHAR(150)
DECLARE @vcTotcmd AS VARCHAR(250)
DECLARE @vcDate AS VARCHAR(10)
DECLARE @vcServer AS VARCHAR(150)
DECLARE @vcTextPath AS VARCHAR(150)
DECLARE @vcTextFile AS VARCHAR(150)
DECLARE @vcMkdirStr AS VARCHAR(150)
DECLARE @vcTime AS VARCHAR(150)
DECLARE @iRC AS INT
 
SET @vcTime = REPLACE(CONVERT(VARCHAR,getdate(), 114),':','')
SET @vcDate = CONVERT(VARCHAR, getdate(), 112)
SET @vcTextPath = 'F:\DWH_NL\SCRIPTS\' + @vcDatabase
SET @vcTextFile = @vcDatabase + '_' + @vcDate + '_' + @vcTime + '.SQL'
SET @vcMkdirStr = 'mkdir ' + @vcTextPath
SET @vcServer = @@SERVERNAME
 
--checking if dir exist, If it doesnt exist it creates it
EXEC @iRC = master.dbo.xp_cmdshell @vcMkdirStr, NO_OUTPUT
IF @iRC <> 0
BEGIN
 exec master.dbo.xp_cmdshell @vcMkdirStr , NO_OUTPUT
END 
 
SET @vcPath = '"C:\Program Files (x86)\Microsoft SQL Server\90\Tools\Publishing\1.2\SqlPubWiz.exe"'
SET @vcParam = ' script ' + @vcTextPath + '\' + @vcTextFile + ' -S ' + @vcServer + ' -schemaonly -d ' + @vcDatabase
SET @vcTotcmd = @vcPath + @vcParam
 
print @vcTotcmd
 
EXEC master..xp_cmdshell @vcTotcmd

 

Het bericht Script alle databases en objecten verscheen eerst op SQLBlog Nederland.

T-TSQL – Create 4 week Moving Total

$
0
0

This article will offer you a way to calculate a 4 week moving total in SQL using SQL Server. There are more ways to Rome but this is a way you can use to calculate it using a date dimension.

What do I mean by a 4 week moving total? It’s a sum or average number, calculated over a number of weeks. The number moves one week back, getting the next four weeks. In the example below, we take week 37-40, we call this block 1. Then we move one week back in the past and take week 36-39, we call this block 2. Etc.

Sql_4Week_Moving_total

Why is this useful? We can use this moving 4 week in a dashboard to show a trendline or sparkline for important business data.

The first step is determine how many “groups” you need with 4 weeks of data. In my example, I have four groups of weeks, block 1 – block 4. For the SQL example, I use six blocks. The weeks must move dynamic within time without hardcoded stuff inside because the dashboard shows data till last complete week.

Step 1: Determine latest complete week

DECLARE @EindWeek AS INT = (SELECT MIN(WeekNummer) FROM dbo.DimDatum WHERE DatumID = CONVERT(VARCHAR, DATEADD(dd,-7,GETDATE()),112) )

Step 2: Declare blocks of weeks

DECLARE @StartWeekBlok6 VARCHAR(6) -- (EindWeek -3 Weken) (dit is het laatste blok)
DECLARE @EindWeekBlok6 VARCHAR(6) 
DECLARE @StartWeekBlok5 VARCHAR(6) -- (EindWeek -1 week) - 4 weken, of eigenlijk @EindWeek -4
DECLARE @EindWeekBlok5 VARCHAR(6)
DECLARE @StartWeekBlok4 VARCHAR(6) -- (EindWeek -2 weken) - 4 weken, of eigenlijk @EindWeek -5
DECLARE @EindWeekBlok4 VARCHAR(6)
DECLARE @StartWeekBlok3 VARCHAR(6) -- (EindWeek -3 weken) - 4 weken, of eigenlijk @EindWeek -6
DECLARE @EindWeekBlok3 VARCHAR(6)
DECLARE @StartWeekBlok2 VARCHAR(6) -- (EindWeek -4 weken) - 4 weken, of eigenlijk @EindWeek -7
DECLARE @EindWeekBlok2 VARCHAR(6)
DECLARE @StartWeekBlok1 VARCHAR(6) -- (EindWeek -5 weken) - 4 weken, of eigenlijk @EindWeek -8
DECLARE @EindWeekBlok1 VARCHAR(6)

Step 2: Assign values to blocks

SET @StartWeekBlok6 = (SELECT dbo.fnDateAddWeek(@EindWeek,-3,0)) 
SET @StartWeekBlok5 = (SELECT dbo.fnDateAddWeek(@EindWeek,-4,0)) 
SET @StartWeekBlok4 = (SELECT dbo.fnDateAddWeek(@EindWeek,-5,0)) 
SET @StartWeekBlok3 = (SELECT dbo.fnDateAddWeek(@EindWeek,-6,0)) 
SET @StartWeekBlok2 = (SELECT dbo.fnDateAddWeek(@EindWeek,-7,0)) 
SET @StartWeekBlok1 = (SELECT dbo.fnDateAddWeek(@EindWeek,-8,0))
 
SET @EindWeekBlok6 = (SELECT dbo.fnDateAddWeek(@EindWeek,0,0)) 
SET @EindWeekBlok5 = (SELECT dbo.fnDateAddWeek(@EindWeek,-1,0)) 
SET @EindWeekBlok4 = (SELECT dbo.fnDateAddWeek(@EindWeek,-2,0)) 
SET @EindWeekBlok3 = (SELECT dbo.fnDateAddWeek(@EindWeek,-3,0)) 
SET @EindWeekBlok2 = (SELECT dbo.fnDateAddWeek(@EindWeek,-4,0)) 
SET @EindWeekBlok1 = (SELECT dbo.fnDateAddWeek(@EindWeek,-5,0))

** Note that I use a function to calculate the weeks, you need the data dimension. This function is handy to get a week in the past or future, based on an input week. You can also include the startweek as a count of weeks.

CREATE FUNCTION [dbo].[fnDateAddWeek]
(
@StartWeek INT,
@NrWeeks INT,
@IncludeStartWeek BIT=0 -- als je hier 1 zet telt hij een week minder terug of verder
)
-- [fnDateAddWeek]
-- Berekent een weeknummer dat voor of na de opgegeven week ligt obv de opgegeven weken
-- Bijv: het is nu week 50. Je wilt weten welke week 4 weken terug is, dit is 46
-- SELECT dbo.fnDateAddWeek (201550, -4,0)
-- SELECT dbo.fnDateAddWeek (201601, -4,0)
 
RETURNS INT
AS
BEGIN
DECLARE @RESULT INT
 
 
SET @RESULT = (SELECT DD.WeekNummer
FROM
(SELECT CONVERT(VARCHAR, DATEADD(WK, @NrWeeks 
- (CASE WHEN @IncludeStartWeek=1 AND @NrWeeks < 0 THEN -1
WHEN @IncludeStartWeek=1 AND @NrWeeks > 0 THEN 1 ELSE -0 END)
, D.DatumWaarde),112) AS DatumID
FROM dbo.DimDatum AS D
WHERE DatumID = (SELECT MAX(DatumID) AS DatumID 
FROM dbo.DimDatum
WHERE WeekNummer = @StartWeek
)
) AS D
JOIN DimDatum AS DD ON D.DatumID = DD.DatumID
) 
RETURN @RESULT
END

Step 3: Setup CTE with measures to group

;
WITH BS 
AS
(SELECT DP.PeriodeNummer
, DP.Jaar
, RIGHT(DP.PeriodeNummer,2) AS PeriodeNaamKortGeenJaar
, ISNULL(SUM(KPI.AantalPlaatsing),0) AS AantalPlaatsing
, ISNULL(SUM(KPI.AantalVacatures),0) AS AantalVacatures
, ISNULL(SUM(KPI.AantalSollicitaties),0) AS AantalSollicitaties
FROM ps.FactPeriodeKPI AS KPI
JOIN dbo.DimPeriode AS DP ON KPI.PeriodeID = DP.PeriodeID
JOIN dbo.DimOrganisatie AS DO ON KPI.OrganisatieID = DO.OrganisatieID
JOIN dbo.DimBranch AS DB ON DO.BranchID = DB.BranchID
WHERE (@GeselecteerdeOrganisatieLevelID = 4
OR (@GeselecteerdeOrganisatieLevelID = 3 AND DB.ZoneID = @GeselecteerdeOrganisatieID)
OR (@GeselecteerdeOrganisatieLevelID = 2 AND DB.AreaID = @GeselecteerdeOrganisatieID)
OR (@GeselecteerdeOrganisatieLevelID = 1 AND DB.BranchID = @GeselecteerdeOrganisatieID) 
) 
AND DP.PeriodeGrootheidID = 1 
AND DP.PeriodeNummer BETWEEN @EindWeekBlok1 AND @EindWeekBlok6
GROUP BY DP.PeriodeNummer,DP.Jaar
 
)

Step 4: Setup CTE to genereate groups

, WG -- WG: WeekGroepjes: genereer een nummer voor groepjes van 4 weken, eindigt
-- bij de laatste volledige week.
AS
(
 
SELECT 1 AS PeriodeNummerRolling4Weken
, @StartWeekBlok6 AS StartWeek
, @EindWeekBlok6 AS EindWeek
, @StartWeekBlok6 + ' t/m ' + @EindWeekBlok6 AS SparklineLabel
FROM dbo.DimDatum AS DD
 
 
UNION
 
SELECT 2 AS PeriodeNummerRolling4Weken
, @StartWeekBlok5 AS StartWeek
, @EindWeekBlok5 AS EindWeek 
, @StartWeekBlok5 + ' t/m ' + @EindWeekBlok5 AS SparklineLabel
 
 
UNION
 
SELECT 3 AS PeriodeNummerRolling4Weken
, @StartWeekBlok4 AS StartWeek
, @EindWeekBlok4 AS EindWeek 
, @StartWeekBlok4 + ' t/m ' + @EindWeekBlok4 AS SparklineLabel
 
 
UNION
 
SELECT 4 AS PeriodeNummerRolling4Weken
, @StartWeekBlok3 AS StartWeek
, @EindWeekBlok3 AS EindWeek 
, @StartWeekBlok3 + ' t/m ' + @EindWeekBlok3 AS SparklineLabel
 
UNION
 
SELECT 5 AS PeriodeNummerRolling2Weken
, @StartWeekBlok2 AS StartWeek
, @EindWeekBlok2 AS EindWeek 
, @StartWeekBlok2 + ' t/m ' + @EindWeekBlok2 AS SparklineLabel
 
 
UNION
 
SELECT 6 AS PeriodeNummerRolling1Weken
, @StartWeekBlok1 AS StartWeek
, @EindWeekBlok1 AS EindWeek 
, @StartWeekBlok1 + ' t/m ' + @EindWeekBlok1 AS SparklineLabel
)

Step 5: Combine both CTE’s

SELECT WG.PeriodeNummerRolling4Weken
, WG.StartWeek
, WG.EindWeek
, WG.SparklineLabel
, SUM(BS.AantalPlaatsing) AS AantalPlaatsing
, SUM(BS.AantalVacatures) AS AantalVacatures
, SUM(BS.AantalSollicitaties) AS AantalSollicitaties
FROM WG 
JOIN BS ON BS.PeriodeNummer BETWEEN WG.StartWeek AND WG.EindWeek
GROUP BY WG.PeriodeNummerRolling4Weken ,
WG.StartWeek ,
WG.EindWeek ,
WG.SparklineLabel

Step 6: combined result

 

DECLARE @EindWeek AS INT = (SELECT MIN(WeekNummer) FROM dbo.DimDatum WHERE DatumID = CONVERT(VARCHAR, DATEADD(dd,-7,GETDATE()),112) )
 
DECLARE @StartWeekBlok6 VARCHAR(6) -- (EindWeek -3 Weken) (dit is het laatste blok)
DECLARE @EindWeekBlok6 VARCHAR(6) 
DECLARE @StartWeekBlok5 VARCHAR(6) -- (EindWeek -1 week) - 4 weken, of eigenlijk @EindWeek -4
DECLARE @EindWeekBlok5 VARCHAR(6)
DECLARE @StartWeekBlok4 VARCHAR(6) -- (EindWeek -2 weken) - 4 weken, of eigenlijk @EindWeek -5
DECLARE @EindWeekBlok4 VARCHAR(6)
DECLARE @StartWeekBlok3 VARCHAR(6) -- (EindWeek -3 weken) - 4 weken, of eigenlijk @EindWeek -6
DECLARE @EindWeekBlok3 VARCHAR(6)
DECLARE @StartWeekBlok2 VARCHAR(6) -- (EindWeek -4 weken) - 4 weken, of eigenlijk @EindWeek -7
DECLARE @EindWeekBlok2 VARCHAR(6)
DECLARE @StartWeekBlok1 VARCHAR(6) -- (EindWeek -5 weken) - 4 weken, of eigenlijk @EindWeek -8
DECLARE @EindWeekBlok1 VARCHAR(6)
 
SET @StartWeekBlok6 = (SELECT dbo.fnDateAddWeek(@EindWeek,-3,0)) 
SET @StartWeekBlok5 = (SELECT dbo.fnDateAddWeek(@EindWeek,-4,0)) 
SET @StartWeekBlok4 = (SELECT dbo.fnDateAddWeek(@EindWeek,-5,0)) 
SET @StartWeekBlok3 = (SELECT dbo.fnDateAddWeek(@EindWeek,-6,0)) 
SET @StartWeekBlok2 = (SELECT dbo.fnDateAddWeek(@EindWeek,-7,0)) 
SET @StartWeekBlok1 = (SELECT dbo.fnDateAddWeek(@EindWeek,-8,0))
 
SET @EindWeekBlok6 = (SELECT dbo.fnDateAddWeek(@EindWeek,0,0)) 
SET @EindWeekBlok5 = (SELECT dbo.fnDateAddWeek(@EindWeek,-1,0)) 
SET @EindWeekBlok4 = (SELECT dbo.fnDateAddWeek(@EindWeek,-2,0)) 
SET @EindWeekBlok3 = (SELECT dbo.fnDateAddWeek(@EindWeek,-3,0)) 
SET @EindWeekBlok2 = (SELECT dbo.fnDateAddWeek(@EindWeek,-4,0)) 
SET @EindWeekBlok1 = (SELECT dbo.fnDateAddWeek(@EindWeek,-5,0))
 
--SELECT @StartWeekBlok6,@StartWeekBlok5,@StartWeekBlok4,@StartWeekBlok3,@StartWeekBlok2,@StartWeekBlok1 
--SELECT @EindWeekBlok6,@EindWeekBlok5,@EindWeekBlok4,@EindWeekBlok3,@EindWeekBlok2,@EindWeekBlok1
 
-- ** ----------------------------------------------------------------------------------------------------------- 
-- ** -----------------------------------------------------------------------------------------------------------
;
WITH BS 
AS
(SELECT DP.PeriodeNummer
, DP.Jaar
, RIGHT(DP.PeriodeNummer,2) AS PeriodeNaamKortGeenJaar
, ISNULL(SUM(KPI.AantalPlaatsing),0) AS AantalPlaatsing
, ISNULL(SUM(KPI.AantalVacatures),0) AS AantalVacatures
, ISNULL(SUM(KPI.AantalSollicitaties),0) AS AantalSollicitaties
FROM ps.FactPeriodeKPI AS KPI
JOIN dbo.DimPeriode AS DP ON KPI.PeriodeID = DP.PeriodeID
JOIN dbo.DimOrganisatie AS DO ON KPI.OrganisatieID = DO.OrganisatieID
JOIN dbo.DimBranch AS DB ON DO.BranchID = DB.BranchID
WHERE (@GeselecteerdeOrganisatieLevelID = 4
OR (@GeselecteerdeOrganisatieLevelID = 3 AND DB.ZoneID = @GeselecteerdeOrganisatieID)
OR (@GeselecteerdeOrganisatieLevelID = 2 AND DB.AreaID = @GeselecteerdeOrganisatieID)
OR (@GeselecteerdeOrganisatieLevelID = 1 AND DB.BranchID = @GeselecteerdeOrganisatieID) 
) 
AND DP.PeriodeGrootheidID = 1 
AND DP.PeriodeNummer BETWEEN @EindWeekBlok1 AND @EindWeekBlok6
GROUP BY DP.PeriodeNummer,DP.Jaar
 
)
 
, WG -- WG: WeekGroepjes: genereer een nummer voor groepjes van 4 weken, eindigt
-- bij de laatste volledige week.
AS
(
 
SELECT 1 AS PeriodeNummerRolling4Weken
, @StartWeekBlok6 AS StartWeek
, @EindWeekBlok6 AS EindWeek
, @StartWeekBlok6 + ' t/m ' + @EindWeekBlok6 AS SparklineLabel
FROM dbo.DimDatum AS DD
 
 
UNION
 
SELECT 2 AS PeriodeNummerRolling4Weken
, @StartWeekBlok5 AS StartWeek
, @EindWeekBlok5 AS EindWeek 
, @StartWeekBlok5 + ' t/m ' + @EindWeekBlok5 AS SparklineLabel
 
 
UNION
 
SELECT 3 AS PeriodeNummerRolling4Weken
, @StartWeekBlok4 AS StartWeek
, @EindWeekBlok4 AS EindWeek 
, @StartWeekBlok4 + ' t/m ' + @EindWeekBlok4 AS SparklineLabel
 
 
UNION
 
SELECT 4 AS PeriodeNummerRolling4Weken
, @StartWeekBlok3 AS StartWeek
, @EindWeekBlok3 AS EindWeek 
, @StartWeekBlok3 + ' t/m ' + @EindWeekBlok3 AS SparklineLabel
 
UNION
 
SELECT 5 AS PeriodeNummerRolling2Weken
, @StartWeekBlok2 AS StartWeek
, @EindWeekBlok2 AS EindWeek 
, @StartWeekBlok2 + ' t/m ' + @EindWeekBlok2 AS SparklineLabel
 
 
UNION
 
SELECT 6 AS PeriodeNummerRolling1Weken
, @StartWeekBlok1 AS StartWeek
, @EindWeekBlok1 AS EindWeek 
, @StartWeekBlok1 + ' t/m ' + @EindWeekBlok1 AS SparklineLabel
)
 
 
SELECT WG.PeriodeNummerRolling4Weken
, WG.StartWeek
, WG.EindWeek
, WG.SparklineLabel
, SUM(BS.AantalPlaatsing) AS AantalPlaatsing
, SUM(BS.AantalVacatures) AS AantalVacatures
, SUM(BS.AantalSollicitaties) AS AantalSollicitaties
FROM WG 
JOIN BS ON BS.PeriodeNummer BETWEEN WG.StartWeek AND WG.EindWeek
GROUP BY WG.PeriodeNummerRolling4Weken ,
WG.StartWeek ,
WG.EindWeek ,
WG.SparklineLabel

You can save this query as a procedure and run it… The result will look something like this (dummy data):

Sql_4Week_Moving_total_SPCD

I’m using this data for my sparkline to show the trend from the latest weeks:

Sql_4Week_Moving_total_Sparkline

Het bericht T-TSQL – Create 4 week Moving Total verscheen eerst op SQLBlog Nederland.

Running Totals / Cumulatieven berekenen

$
0
0
Op internet is er al veel over geschreven maar ik vond het de moeite waard om een artikel te posten over Running Totals / Cumulatieven berekenen binnen Microsoft SQL Server 2005 / 2008. Er zijn diverse manieren om dit te doen. De meest snelle manier wordt in dit artikel besproken.
SQL Server 2005 + heeft zelf geen goede manier om running totals te berekenen. Hij kan namelijk niet uit zichzelf de vorige rij bij de huidige rij optellen. Er moet een manier worden gevonden om de vorige rij bij de huidige rij op te tellen.
De meest gebruikte methode en het eenvoudigste te begrijpen is de self-join. Bij deze methode “Join” je de dataset op zichzelf om iedere keer het resultaat van vorige records op te kunnen tellen. Dit is alleen niet de meest effectieve methode!
De meest snelle methode is door gebruik te maken van een table variable of tijdelijke tabel (@ of #). Laat ik dit uitleggen met een voorbeeld. We gebruiken hiervoor AdventureWorksDW. Stel dat we per klant de internetsales cumulatieven willen uitrekenen over de tijd.
Eerst maken we een table variable (@) aan en vullen deze met alle internetsales per klant per week. We sorteren de data bij het invoegen. Dit is absoluut noodzakelijk omdat we daarna de records een voor een bijwerken met cumulatieven.
USE AdventureWorksDW
GO
DECLARE @RunningTotalAantal INT
DECLARE @RunningTotalBedrag MONEY
DECLARE @KlantNaam AS VARCHAR(128)
 
DECLARE @Sales TABLE (WeekNaam VARCHAR(128), KlantNaam VARCHAR(128), Aantal INT, Bedrag MONEY, AantalCumulatief INT, BedragCumulatief MONEY)
 
INSERT INTO @Sales
SELECTDT.CalendarYear * 100 + DT.WeekNumberOfYear AS WeekNaam
, DC.FirstName + ' ' + DC.LastName+ ' (' + CAST(DC.CustomerKey AS VARCHAR(16)) + ')' AS KlantNaam
, SUM(FIS.OrderQuantity) AS Aantal
, SUM(FIS.SalesAmount) AS Bedrag
, 0
, 0
FROM FactInternetSales AS FIS
INNER JOIN DimProduct AS DP ON FIS.ProductKey = DP.ProductKey
INNER JOIN DimCustomer AS DC ON FIS.CustomerKey = DC.CustomerKey
INNER JOIN DimTime AS DT ON FIS.OrderDateKey = DT.TimeKey
GROUP BY DC.FirstName,DC.LastName, DC.CustomerKey
, DT.CalendarYear,DT.WeekNumberOfYear
-- Sorting is very important here!
ORDER BY DC.FirstName,DC.LastName
, DT.CalendarYear,DT.WeekNumberOfYear
Zoals in bovenstaand voorbeeld is te zien sommeren we per klant per week de aantallen verkochte artikelen en het orderbedrag. De waarden AantalCumulatief en BedragCumulatief zetten we op 0.
De tijdelijke tabel is nu gevuld met alle internet sales per klant. Vervolgens gaan we de laatste twee kolommen berekenen, hier gaat het om. Dit is een heel grappig truukje en zeer effectief!
UPDATE @Sales
SET @RunningTotalAantal = AantalCumulatief =
(CASE WHEN KlantNaam = @KlantNaam THEN @RunningTotalAantal + Aantal
ELSE AantalEND)
,@RunningTotalBedrag = BedragCumulatief =
(CASE WHEN KlantNaam = @KlantNaam THEN @RunningTotalBedrag + Bedrag
ELSE BedragEND)
,@KlantNaam = KlantNaam
FROM @Sales AS S
SELECT *
FROM @Sales
Wat je hier doet is de variable RunningTotalAantal vullen met AantalCumulatief. Deze is bij het eerste record leeg. Deze “Aantal Cumulatief” wordt vervolgens gevuld met het veld Aantal. Als we het eerste records pakken is klantnaam <> @Klantnaam, deze variable is immers nog niet gevuld. RunningTotalAantal en AantalCumulatief worden nu gevuld met de waarde Aantal. Hetzelfde gebeurt er bij de kolom BedragCumulatief. Nu komt de truuk, @KlantNaam = Klantnaam. De variabele wordt gevuld met de huidige klantnaam. Dit doen we om te bepalen of we bij een nieuwe klant zijn, dan moet er namelijk weer opnieuw worden begonnen met tellen. Je moet even goed naar de SQL kijken om het te begrijpen.
Deze methode kun je dus toepassen om statistiekkolommen in je Datawarehouse uit te rekenen. Ik had het nodig om aantal gewerkte uren bij klanten uit te rekenen per medewerker. Nu zul je op AdventureWorksDW niet veel performancewinst boeken t.o.v.self joins of cursors maar als je grote tabellen hebt met een paar miljoen rijen zal dit echt snel gaan. Ik heb dit getest op een tabel met 5 miljoen records. Het uitrekenen van de running totals duurde 1 minuut. Het bijwerken van de FactTable duurde ook 1 minuut.
Voor vragen, leave a message :-)

Het bericht Running Totals / Cumulatieven berekenen verscheen eerst op SQLBlog Nederland.


Viewing all 15 articles
Browse latest View live