Salve,
sono stanco ma ci provo lo stesso... sicuramente altri faranno di meglio...
prendiamo la popolazione
INSERT INTO dbo.tbl_Arc
VALUES ( '2000-01-01' )
, ( '2010-01-01' )
, ( '2018-01-01' ), (' 2018-08-01')
, ( '2019-01-01' ), (' 2018-04-01'), (' 2018-07-01')
, ( '2020-01-01' )
, (' 2020-04-01')
, (' 2020-05-01')
, (' 2020-07-01')
, (' 2020-08-01')
, (' 2020-09-01')
, (' 2020-10-01')
, (' 2020-10-02')
, (' 2020-11-01')
, (' 2020-12-01')
, (' 2021-01-01');
dove ovviamente ho fatto inserimenti un po' piu' articolati per il 2020... mentre gli altri anni sono scarichi o addirittura vuoti...
la mia tabella contiente 1 sola colonna visto che devo farne solo il count...
non so che versione di SQL Server tu stia utilizzando, ma dalla v. 2012 la funzione COUNT(..) supporte le feature di windowing, quindi puoi aggregare COUNT(..) OVER (ORDER BY nomi colonne) per avere il tuo raggruppamento desiderato...
cio' ti permette di avere il tuo "running total",
-- Costruzione del running total per Anno, Trimestre
-- utilizzando la windowing feature di COUNT() OVER...
SELECT DATEPART(YEAR, DataIns) AS Y, DATEPART(QUARTER, DataIns) AS Q
, COUNT(*) OVER(ORDER BY DATEPART(YEAR, DataIns), DATEPART(QUARTER, DataIns)) AS C
FROM dbo.tbl_Arc;
--<-----------
Y Q C
----------- ----------- -----------
2000 1 1
2010 1 2
2018 1 3
2018 2 4
2018 3 6
2018 3 6
2019 1 7
2020 1 8
2020 2 10
2020 2 10
2020 3 13
2020 3 13
2020 3 13
2020 4 17
2020 4 17
2020 4 17
2020 4 17
2021 1 18
altra funzionalita' interessante, e' l'utilizzo di common table expresisons ricorsive per la costruzione anche dinamica di working set di tipo calendario, quindi nel nostro caso utilizzeremo una CTE per costruire i numeri di trimestre da 1 a 4, e un'altra CTE per costruire i numeri degli anni da MIN(data in tabella) ad ora (oppure altro filtro che tu vorrai gestire)...
ti questi 2 set, da 1 a 4 (trimestre) e da 2000 a 2021 (anno), effettueremo il cross join per avere
2001 T1 + 2001 T2 + 2001 T3 + 2001 T4
....
2021 T1 + 2021 T2 + 2021 T3 + 2021 T4
tipicamente:
DECLARE @MinYear int;
SELECT @MinYear = MIN(DATEPART(YEAR, DataIns))
FROM dbo.tbl_Arc;
-- Costruzione delle Righe Anno + Trimestre
-- usando delle common table expressions
-- dove le ricorsioni generano i Q da 1 a 4
-- in cross join con gli anni da @MinYear a anno corrente
WITH Qrtr(Q) AS (
SELECT 1
UNION ALL
SELECT Q + 1 FROM Qrtr
WHERE Q < 4
),
Yr(Y) AS (
SELECT DATEPART(YEAR, GETDATE())
UNION ALL
SELECT Y - 1
FROM Yr
WHERE Y > @MinYear
),
cteTime AS (
SELECT DISTINCT Yr.Y, Qrtr.Q
FROM Yr, Qrtr
)
SELECT *
FROM cteTime
ORDER BY cteTime.Y, cteTime.Q
sono 88 righe e non le posto qui
con questi "2" pezzi, possiamo andare a combinare la nostra query...
mettiamo in join il nostro prodotto temporale (anni + trimestri) con quanto restituito dalla nostra aggregazione, e ci resta solo da "chiudere i buchi", nel senso che gli slot non matchati dalla join saranno NULL per le valorizzazioni...
allora.... sicuramente c'e' un modo "migliore" non basato su una query innestata come ti vado ad illustrare...
ma sono stanco e mi fermo a questo approccio :
-- unione delle funzionalita'
DECLARE @MinYear int;
SELECT @MinYear = MIN(DATEPART(YEAR, DataIns))
FROM dbo.tbl_Arc;
WITH Qrtr(Q) AS (
SELECT 1
UNION ALL
SELECT Q + 1 FROM Qrtr
WHERE Q < 4
),
Yr(Y) AS (
SELECT DATEPART(YEAR, GETDATE())
UNION ALL
SELECT Y - 1
FROM Yr
WHERE Y > @MinYear
),
cteTime AS (
SELECT DISTINCT Yr.Y, Qrtr.Q
FROM Yr, Qrtr
),
-- e questo e' il nostro time set....
--- continuiamo con il running total come prima esemplificato... (OCCHIO CHE LE VIRGOLE CONTINUANO :D)
cteCount AS (
SELECT DATEPART(YEAR, DataIns) AS Y, DATEPART(QUARTER, DataIns) AS Q
, COUNT(*) OVER(ORDER BY DATEPART(YEAR, DataIns), DATEPART(QUARTER, DataIns)) AS C
FROM dbo.tbl_Arc
)
-- a questo punto ho "tutto", e metto tutto insieme, anche se... vedi la colonna FinalCount
SELECT DISTINCT ct.Y, ct.Q
-- questo sara' NULL negli slot NON matchati dal JOIN con i dati
-- , c1.C -- quindi questa colonna NON andra' proiettata
-- quindi, visto che al momento non mi viene in mente niente
-- di meglio, utilizzo una sub query per prendere comunque
-- il MAX di count prima dello slot corrente, visto che l'aggregazione
-- produce il risultato corretto....
, CASE WHEN c1.C IS NULL
THEN (SELECT MAX(c2.C)
FROM cteCount c2
WHERE c2.Y <= ct.Y AND c2.Q <= ct.Q
)
ELSE c1.C
END FinalCount
-- MA C'E' SICURAMENTE UN ALTRO MODO,
-- MA ORA SONO STANCO :D:D:D
FROM cteTime ct
LEFT JOIN cteCount c1 ON c1.Y = ct.Y AND c1.Q = ct.Q
ORDER BY ct.Y, ct.Q;
anche queste sono 88 righe e non le posto
ha senso quello che ho scritto?
poi gli eventuali filtri li aggiungi TU
salutoni romagnoli
--
Andrea