October 2, 2017 at 9:50 am
Hi all,
I'm interested in creating a query that would calculate the balance of customers with an account open on the 1st of each month.
CREATE TABLE dbo.Customers (
CustomerID INT PRIMARY KEY,
AccountTypeDescription VARCHAR(50),
AccountOpenedDate DATETIME,
AccountClosedDate DATETIME
)
It isn't as simple as a running total of accounts opened, as the dates when accounts are closed need to be considered too, which provides the balance of open accounts, by account type. Can this could be done in one succinct query?
Thanks
October 2, 2017 at 10:31 am
piet_dj - Monday, October 2, 2017 9:50 AMHi all,I'm interested in creating a query that would calculate the balance of customers with an account open on the 1st of each month.
CREATE TABLE dbo.Customers (
CustomerID INT PRIMARY KEY,
AccountTypeDescription VARCHAR(50),
AccountOpenedDate DATETIME,
AccountClosedDate DATETIME
)It isn't as simple as a running total of accounts opened, as the dates when accounts are closed need to be considered too, which provides the balance of open accounts, by account type. Can this could be done in one succinct query?
Thanks
This table structure is not the best & should be split into at least two, if not three (if accounts can be opened or closed more than once), tables.
Having said that, what parameters will the query receive? Start and End dates, perhaps?
Does your desired output look like
Date, AccountTypeDescription, Count
?
The absence of evidence is not evidence of absence.
Martin Rees
You can lead a horse to water, but a pencil must be lead.
Stan Laurel
October 2, 2017 at 10:42 am
Yes I agree, the structure is not ideal (I can't change it), however an account does only have one opening and closing date per CustomerID.
In terms of desired output, it would be something like this eventually (once pivoted), displaying the total 'balance' of customer accounts which are remaining open, at each month start:
Some a table containing; Date, Account Type, Count is exactly right.
October 2, 2017 at 10:51 am
piet_dj - Monday, October 2, 2017 10:42 AMYes I agree, the structure is not ideal, however there an account does only have one opening and closing date per CustomerID.In terms of desired output, it would be something like this, displaying the total 'balance' of customer accounts which are remaining open, at each month start:
These numbers look more like counts of the number of accounts than balance figures. Running totals, either COUNT or SUM, can be achieved using the Window functions for SUM and COUNT, using an OVER clause, PARTITION BY Account Type ORDER BY ReportDate ROWS UNBOUNDED PRECEDING.
Steve (aka sgmunson) π π π
Rent Servers for Income (picks and shovels strategy)
October 2, 2017 at 11:05 am
Sorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:
[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
October 2, 2017 at 12:46 pm
piet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Here's a basic query that might lead in the right direction:CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2017-01-04', '2017-01-06'),
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);
WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (
SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (
SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;
DROP TABLE #Customers;
Steve (aka sgmunson) π π π
Rent Servers for Income (picks and shovels strategy)
October 2, 2017 at 8:25 pm
sgmunson - Monday, October 2, 2017 12:46 PMpiet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Here's a basic query that might lead in the right direction:
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2017-01-04', '2017-01-06'),
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;DROP TABLE #Customers;
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2016-01-04', '2017-01-06'), --Changed Start Date here
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
;
WITH ALL_DATES AS
(
--Jeff Moden
Change is inevitable... Change for the better is not.
October 2, 2017 at 8:29 pm
Damned forum software whacked the formatting on half the code. I tried to fix it but it's not buying it. Apologies there.
--Jeff Moden
Change is inevitable... Change for the better is not.
October 2, 2017 at 9:51 pm
piet_dj - Monday, October 2, 2017 10:42 AMYes I agree, the structure is not ideal (I can't change it), however an account does only have one opening and closing date per CustomerID.In terms of desired output, it would be something like this eventually (once pivoted), displaying the total 'balance' of customer accounts which are remaining open, at each month start:
Some a table containing; Date, Account Type, Count is exactly right.
Just to verify... What format are those dates? DD/MM/YYYY? Also, please see the first link in my signature line below for how to post some readily consumable data, which will also clear up the date thing if you do it according to the write up.
--Jeff Moden
Change is inevitable... Change for the better is not.
October 3, 2017 at 12:51 am
Not completely like you requested, but having values for column names makes it hard to retrieve the data for your client software, so i suggest you pick fixed column names instead as in this example:CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', {d '2017-01-01'}, NULL),
(2, 'Standard', {d '2017-02-01'}, NULL),
(3, 'Standard', {d '2017-03-01'}, NULL),
(4, 'Premium', {d '2017-04-01'}, {d '2017-06-01'}),
(5, 'Elite', {d '2017-05-01'}, NULL),
(6, 'Elite', {d '2017-06-01'}, NULL),
(7, 'Elite', {d '2017-07-01'}, NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);
with cteNumbers as (
select row_number() over (order by (select null)) as n
from sys.syscolumns sc
)
select
c.AccountTypeDescription,
sum(case p.n when 6 then 1 else 0 end) as [month - 6],
sum(case p.n when 5 then 1 else 0 end) as [month - 5],
sum(case p.n when 4 then 1 else 0 end) as [month - 4],
sum(case p.n when 3 then 1 else 0 end) as [month - 3],
sum(case p.n when 2 then 1 else 0 end) as [month - 2],
sum(case p.n when 1 then 1 else 0 end) as [last month]
from (
select
n.n,
dateadd(month, -n.n + datediff(month,0, s.dt), 0) as startDate,
dateadd(day, 1, dateadd(month, -n.n + datediff(month,0, s.dt), 0)) as endDate
from (
select /*getdate()*/{d '2017-07-01'}
) s (dt)
cross join cteNumbers n
where n.n <= 6
) p
inner join #customers c on (/*c.AccountOpenedDate is null or */c.AccountOpenedDate <= p.StartDate) and (c.AccountClosedDate is null or c.AccountClosedDate >= p.endDate)
group by
c.AccountTypeDescription
order by
c.AccountTypeDescription desc
Also your table definition allows for AccountOpenedDate to be NULL, so I left in comments a suggestion that would interpret rows having a NULL AccountOpenedDate as an account opened at a date before our time scale.
edit: Just re-read your requirements and you stated that you need the number of accounts open on each 1st of the month. Not during the entire month. So I changed the row with the end date to include only accounts open on the first day of each month.
October 3, 2017 at 1:48 am
piet_dj - Monday, October 2, 2017 11:05 AMIf an account is opened well before the reporting range and closes in or after the reporting range, I'm thinking that it still needs to be included as an active account even if neither date is in the reporting range but straddling it. I modified one row in the data setup and copied your code. Run it and see the anomaly that appears.
Firstly, thanks Steve for your input, I like your CTE solution.I've just stumbled across the issue Jeff has described when running the code, yes the account information from previous periods does need to be considered in order to provide the balance for the reporting period. I guess that hidden requirement complicates things significantly.
Jeff Moden - Monday, October 2, 2017 9:51 PMpiet_dj - Monday, October 2, 2017 10:42 AMYes I agree, the structure is not ideal (I can't change it), however an account does only have one opening and closing date per CustomerID.In terms of desired output, it would be something like this eventually (once pivoted), displaying the total 'balance' of customer accounts which are remaining open, at each month start:
Some a table containing; Date, Account Type, Count is exactly right.
Just to verify... What format are those dates? DD/MM/YYYY? Also, please see the first link in my signature line below for how to post some readily consumable data, which will also clear up the date thing if you do it according to the write up.
The format is DD/MM/YYYY in my image, so it represents each month start date. Apologies, it's been some time since I've visited the forum and my posting skills are rusty.
Pete
October 3, 2017 at 2:05 am
piet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Aren't you overcomplicating this? Can't you simply count the accounts which are open on the first of each month? Like this:SELECT
AccountTypeDescription,
[2016-11-01] = SUM(CASE WHEN WhereDate = '2016-11-01' THEN 1 ELSE 0 END),
[2016-12-01] = SUM(CASE WHEN WhereDate = '2016-12-01' THEN 1 ELSE 0 END),
[2017-01-01] = SUM(CASE WHEN WhereDate = '2017-01-01' THEN 1 ELSE 0 END),
[2017-02-01] = SUM(CASE WHEN WhereDate = '2017-02-01' THEN 1 ELSE 0 END),
[2017-03-01] = SUM(CASE WHEN WhereDate = '2017-03-01' THEN 1 ELSE 0 END),
[2017-04-01] = SUM(CASE WHEN WhereDate = '2017-04-01' THEN 1 ELSE 0 END),
[2017-05-01] = SUM(CASE WHEN WhereDate = '2017-05-01' THEN 1 ELSE 0 END),
[2017-06-01] = SUM(CASE WHEN WhereDate = '2017-06-01' THEN 1 ELSE 0 END),
[2017-07-01] = SUM(CASE WHEN WhereDate = '2017-07-01' THEN 1 ELSE 0 END),
[2017-08-01] = SUM(CASE WHEN WhereDate = '2017-08-01' THEN 1 ELSE 0 END),
[2017-09-01] = SUM(CASE WHEN WhereDate = '2017-09-01' THEN 1 ELSE 0 END),
[2017-10-01] = SUM(CASE WHEN WhereDate = '2017-10-01' THEN 1 ELSE 0 END)
FROM #Customers
CROSS APPLY (
SELECT WhereDate = CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE())-n,0) AS DATE)
FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) v (n)
) x
WHERE AccountOpenedDate <= WhereDate AND (AccountClosedDate IS NULL OR AccountClosedDate > WhereDate)
GROUP BY AccountTypeDescription
For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden
October 3, 2017 at 5:52 am
ChrisM@Work - Tuesday, October 3, 2017 2:05 AMpiet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Aren't you overcomplicating this? Can't you simply count the accounts which are open on the first of each month? Like this:
SELECT
AccountTypeDescription,
[2016-11-01] = SUM(CASE WHEN WhereDate = '2016-11-01' THEN 1 ELSE 0 END),
[2016-12-01] = SUM(CASE WHEN WhereDate = '2016-12-01' THEN 1 ELSE 0 END),
[2017-01-01] = SUM(CASE WHEN WhereDate = '2017-01-01' THEN 1 ELSE 0 END),
[2017-02-01] = SUM(CASE WHEN WhereDate = '2017-02-01' THEN 1 ELSE 0 END),
[2017-03-01] = SUM(CASE WHEN WhereDate = '2017-03-01' THEN 1 ELSE 0 END),
[2017-04-01] = SUM(CASE WHEN WhereDate = '2017-04-01' THEN 1 ELSE 0 END),
[2017-05-01] = SUM(CASE WHEN WhereDate = '2017-05-01' THEN 1 ELSE 0 END),
[2017-06-01] = SUM(CASE WHEN WhereDate = '2017-06-01' THEN 1 ELSE 0 END),
[2017-07-01] = SUM(CASE WHEN WhereDate = '2017-07-01' THEN 1 ELSE 0 END),
[2017-08-01] = SUM(CASE WHEN WhereDate = '2017-08-01' THEN 1 ELSE 0 END),
[2017-09-01] = SUM(CASE WHEN WhereDate = '2017-09-01' THEN 1 ELSE 0 END),
[2017-10-01] = SUM(CASE WHEN WhereDate = '2017-10-01' THEN 1 ELSE 0 END)
FROM #Customers
CROSS APPLY (
SELECT WhereDate = CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE())-n,0) AS DATE)
FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) v (n)
) x
WHERE AccountOpenedDate <= WhereDate AND (AccountClosedDate IS NULL OR AccountClosedDate > WhereDate)
GROUP BY AccountTypeDescription
That's the ticket. Nice and simple. Of course, the next question will end up being how to make it dynamic.
@piet_dj , there's an article on how to make keep it simple, like Chris did, and convert it to being dynamic. It won't be "one succinct query", however. Here's the article...
http://www.sqlservercentral.com/articles/Crosstab/65048/If you decide to make it so that any account with any activity during a particular month is what you really need to count, then please see the following article.
http://www.sqlservercentral.com/articles/T-SQL/105968/
I'd also recommend getting out of the habit of storing NULLs for end dates so that you don't need to use an OR in the WHERE clause. If you store or default a DATETIME column to "9999" (easy to remember) , it will resolve to 9999-01-01, which is far enough into the future to never fall out of favor, establishes a standard, leaves headroom for date calculations, and makes it so that you don't need OR in your WHERE clauses.
You should also get back into the habit of posting readily consumable data with your question, which you still haven't done. π
--Jeff Moden
Change is inevitable... Change for the better is not.
October 3, 2017 at 6:44 am
Jeff Moden - Monday, October 2, 2017 8:25 PMsgmunson - Monday, October 2, 2017 12:46 PMpiet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Here's a basic query that might lead in the right direction:
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2017-01-04', '2017-01-06'),
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;DROP TABLE #Customers;
If an account is opened well before the reporting range and closes in or after the reporting range, I'm thinking that it still needs to be included as an active account even if neither date is in the reporting range but straddling it. I modified one row in the data setup and copied your code. Run it and see the anomaly that appears.
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2016-01-04', '2017-01-06'), --Changed Start Date here
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (
SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;DROP TABLE #Customers;
It was only intended to get the original poster on the path toward a solution. I knew going in that there was lot more to worry about than what my query was going to cover, but the inherent time limits associated with doing this during work hours meant leaving some things at least partially undone, and leaving that to the original poster to fill in the gaps. I also figured you'd likely chime in, and of course, you did. The dates being "euro-style" (so to speak) wasn't something I picked up on right away, but by the time I knew about it, it was already too late to mess with my code. And no biggy on the code formatting... ran into that recently, and did manage to figure out how to stop that from happening. You have to be sure your code isn't being pasted in directly from SSMS, as that actually has formatting in it, and while we can now "get away with it", that doesn't always work to just let this site remove the formatting. Thus I now paste into Notepad and then recopy and paste into the site.
Steve (aka sgmunson) π π π
Rent Servers for Income (picks and shovels strategy)
October 3, 2017 at 7:29 am
sgmunson - Tuesday, October 3, 2017 6:44 AMJeff Moden - Monday, October 2, 2017 8:25 PMsgmunson - Monday, October 2, 2017 12:46 PMpiet_dj - Monday, October 2, 2017 11:05 AMSorry it isn't the account (monetary) balance i'm referring to, it's a balancing figure, calculated by:[Number of Accounts Opened] - [Number of Account Closed] = [Remaining Number of Accounts Open].
This for each month, considering the dates of open/closure.
I could do a running total query for Accounts opened per month, and another for Accounts closed, and then a third to balance the two, but I'm sure there is a better method.
Here's a basic query that might lead in the right direction:
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2017-01-04', '2017-01-06'),
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;DROP TABLE #Customers;
If an account is opened well before the reporting range and closes in or after the reporting range, I'm thinking that it still needs to be included as an active account even if neither date is in the reporting range but straddling it. I modified one row in the data setup and copied your code. Run it and see the anomaly that appears.
CREATE TABLE #Customers (
CustomerID int PRIMARY KEY,
AccountTypeDescription varchar(50),
AccountOpenedDate date,
AccountClosedDate date
);
INSERT INTO #Customers (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate)
SELECT CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate
FROM (
VALUES (1, 'Standard', '2017-01-01', NULL),
(2, 'Standard', '2017-01-02', NULL),
(3, 'Standard', '2017-01-03', NULL),
(4, 'Premium', '2016-01-04', '2017-01-06'), --Changed Start Date here
(5, 'Elite', '2017-01-05', NULL),
(6, 'Elite', '2017-01-06', NULL),
(7, 'Elite', '2017-01-07', NULL)
) AS X (CustomerID, AccountTypeDescription, AccountOpenedDate, AccountClosedDate);WITH ALL_DATES AS (
SELECT TOP (7) DATEADD(day, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, '2017-01-01') AS THE_DATE
FROM sys.all_objects
),
ACCOUNT_TYPES AS (SELECT DISTINCT AccountTypeDescription AS [Account Type]
FROM #Customers
),
RAW_DATA AS (
SELECT X.[Account Type], X.THE_DATE,
SUM(X.AccountsOpened) OVER(PARTITION BY X.[Account Type] ORDER BY X.THE_DATE ROWS UNBOUNDED PRECEDING) AS AccountsOpened
FROM (
SELECT T.[Account Type],
D.THE_DATE,
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountOpenedDate = D.THE_DATE
) -
(
SELECT COUNT(DISTINCT C.CustomerID)
FROM #Customers AS C
WHERE C.AccountTypeDescription = T.[Account Type]
AND C.AccountClosedDate = D.THE_DATE
) AS AccountsOpened
FROM ALL_DATES AS D, ACCOUNT_TYPES AS T
GROUP BY T.[Account Type],
D.THE_DATE
) AS X
)
SELECT [Account Type],
ISNULL([2017-01-01], 0) AS [01/01/2017],
ISNULL([2017-01-02], 0) AS [01/02/2017],
ISNULL([2017-01-03], 0) AS [01/03/2017],
ISNULL([2017-01-04], 0) AS [01/04/2017],
ISNULL([2017-01-05], 0) AS [01/05/2017],
ISNULL([2017-01-06], 0) AS [01/06/2017],
ISNULL([2017-01-07], 0) AS [01/07/2017]
FROM RAW_DATA
PIVOT (MAX(AccountsOpened) FOR THE_DATE IN ([2017-01-01], [2017-01-02], [2017-01-03], [2017-01-04], [2017-01-05], [2017-01-06], [2017-01-07])) AS PVT
ORDER BY [Account Type] DESC;DROP TABLE #Customers;
It was only intended to get the original poster on the path toward a solution. I knew going in that there was lot more to worry about than what my query was going to cover, but the inherent time limits associated with doing this during work hours meant leaving some things at least partially undone, and leaving that to the original poster to fill in the gaps. I also figured you'd likely chime in, and of course, you did. The dates being "euro-style" (so to speak) wasn't something I picked up on right away, but by the time I knew about it, it was already too late to mess with my code. And no biggy on the code formatting... ran into that recently, and did manage to figure out how to stop that from happening. You have to be sure your code isn't being pasted in directly from SSMS, as that actually has formatting in it, and while we can now "get away with it", that doesn't always work to just let this site remove the formatting. Thus I now paste into Notepad and then recopy and paste into the site.
I pasted the code from NotePad. They're never recovered from the "improvements" made when they changed the forum software. There are some great things they've added, like the ability to drag graphics on screen but the code windows still have some major problems.
Heh... absolutely understood on the working-hours thing. That's also why I've been referring folks to articles or just making a suggestion... I've not had the time to post code because I don't have the time to do the testing I normally do before posting... especially when the OP doesn't post readily consumable data. Thank you for helping the OP especially in the area of creating some readily consumable data.
On this given problem, it wasn't real clear even in subsequent posts what the OP was really after especially since the words "Running Balance" instead of "What's currently active" were used. It was a point of confusion. I made a guess and the guess turned out to be correct. If I didn't come across that way, my most sincere apologies. The written word isn't always friendly especially if I'm typing during a coffee depraved stated. π
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 15 posts - 1 through 15 (of 16 total)
You must be logged in to reply to this topic. Login to reply