How to achieve the bucket values in SQL? - sql

I have schedule table like this (table name = testSch)
ID Amount scheduleDate
1 7230.00 2018-07-13
1 7272.00 2018-07-27
1 7314.00 2018-08-10
1 7356.00 2018-08-24
1 7398.00 2018-09-07
1 7441.00 2018-09-21
1 7439.00 2018-10-08
1 7526.00 2018-10-22
1 7570.00 2018-11-05
1 7613.00 2018-11-19
1 5756.00 2018-12-03
I need to sum the Amount field based on specific bucket values as shown below
Principal_7To30_Days
Principal_1To3_Months
Principal_3To6_Months
Principal_6To12_Months
Principal_1To3_Years
by giving an input date
And my input date is 2018-07-09 and below is my query;
;with cteSchedule as (
select *,DATEDIFF(DAY,'20180709',scheduleDate) as datedifference,
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference from testSch)
select ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference <7),0) AS Principal_0To7_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=7 and cteSchedule.datedifference<30),0)
AS Principal_7To30_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=30 and cteSchedule.datedifference<90),0) AS Principal_1To3_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=90 and cteSchedule.datedifference<180),0) AS Principal_3To6_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=180 and cteSchedule.datedifference<365),0) AS Principal_6To12_Months
And below is my output
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 29509.00 35904.00 0.00
But the correct output should be
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 36948.00 28465.00 0.00
So the problem is i'm getting wrong values for Principal_1To3_Months and Principal_3To6_Months, When I asked my client how do they calculate this in their legacy system, they replied that they calculate using +-months by adding number of months and not days. So if today is 2018-07-09 + 3 months we will get 2018-10-09.
So I used the month difference in my cte query as below
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference
And use this in my overall query as below
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.monthdifference>=1 and cteSchedule.monthdifference<=3),0) AS Principal_1To3_Months
But this time also I get the same values as mentioned in my very first output.
Can someone please point out where is my mistake and how to achieve this values as mentioned in correct output

I wouldn't use DATEDIFF to calculation day or month difference days, because there is some month have 31 days, others month have 30 days.
Therefore, the calculated difference days are not accurate.
I would use DATEADD instead of DATEDIFF to do the condition.
;with cteSchedule as (
select *,'20180709' compareDay
from testSch
)
SELECT Sum(CASE
WHEN t.scheduleDate < DATEADD(day, 7, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_0To7_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(day, 7, compareDay) AND t.scheduleDate < DATEADD(day, 30, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_7To30_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,1,compareDay) AND t.scheduleDate < DATEADD(month,3,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_1To3_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,3,compareDay) AND t.scheduleDate < DATEADD(month,6,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_3To6_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,6,compareDay) AND t.scheduleDate < DATEADD(month,12,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_6To12_Months
from cteSchedule t
SQLFiddle
[Results]:
| Principal_0To7_Days | Principal_7To30_Days | Principal_1To3_Months | Principal_3To6_Months | Principal_6To12_Months |
|---------------------|----------------------|-----------------------|-----------------------|------------------------|
| 7230 | 7272 | 36948 | 28465 | 0 |
Note
You can use CASE WHEN with SUM
Aggregate function instead of select subquery, the performance will be better.

Related

Sum Based on Date

I currently have this code that I want to sum every quantity based on the year. I have written a code that I thought would sum all the charges in 2016 and 2017, but it isn't running correctly.
I added the two different types of partition by statements to test and see if either would work and they don't. When I take them out, the Annual column just shows me the quantity for that specific receipt.
Here is my current code:
SELECT
ReceiptNumber
,Quantity
,Date
,sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER (PARTITION BY Date)
as Annual2016
,sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER (PARTITION BY ReceiptNumber)
as Annual2017
FROM Table1
GROUP BY ReceiptNumber, Quantity, Date
I would like my data to look like this
ReceiptNumber Quantity Date Annual2016 Annual2017
1 5 2016-01-05 17 13
2 11 2017-04-03 17 13
3 12 2016-11-11 17 13
4 2 2017-09-09 17 13
Here is a sample of some of the data I am pulling from:
ReceiptNumber Quantity Date
1 5 2016-01-05
2 11 2017-04-03
3 12 2016-11-11
4 2 2017-09-09
5 96 2015-07-08
6 15 2016-12-12
7 24 2016-04-19
8 31 2017-01-02
9 10 2017-0404
10 18 2015-10-10
11 56 2017-06-02
Try something like this
Select
..
sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2016
sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER ()as Annual2017
..
Where Date >= '2016-01-01' and Date < '2018-01-01'
If you want it printed only once at the top then you should run it in a separate query like:
SELECT YEAR(Date) y, sum(Quantity) s FROM Table1 GROUP BY YEAR(Date)
and then do the main query like this:
SELECT * FROM table1
Easy, peasey ... ;-)
Your original question could also be answered with:
SELECT *,
(SELECT SUM(Quantity) FROM Table1 WHERE YEAR(Date)=2016 ) Annual2016,
(SELECT SUM(Quantity) FROM Table1 WHERE YEAR(Date)=2017 ) Annual2017
FROM table1
You need some conditional aggreation over a Window Aggregate. Simply remove both PARTITION BY as you're already filtering the year in the CASE:
SELECT
ReceiptNumber
,Quantity
,Date
,sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2016
,sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2017
FROM Table1
You probably don't need the final GROUP BY ReceiptNumber, Quantity, Date

SQL - How to count records for each status in one line per day?

I have a table Sales
Sales
--------
id
FormUpdated
TrackingStatus
There are several status e.g. Complete, Incomplete, SaveforLater, ViewRates etc.
I want to have my results in this form for the last 8 days(including today).
Expected Result:
Date Part of FormUpdated, Day of Week, Counts of ViewRates, Counts of Sales(complete), Counts of SaveForLater
--------------------------------------
2015-05-19 Tuesday 3 1 21
2015-05-18 Monday 12 5 10
2015-05-17 Sunday 6 1 8
2015-05-16 Saturday 5 3 7
2015-05-15 Friday 67 5 32
2015-05-14 Thursday 17 0 5
2015-05-13 Wednesday 22 0 9
2015-05-12 Tuesday 19 2 6
Here is my sql query:
select datename(dw, FormUpdated), count(ID), TrackingStatus
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= GetDate() - 8
group by datename(dw, FormUpdated), TrackingStatus
order by datename(dw, FormUpdated) desc
I do not know how to make the next step.
Update
I forgot to mention, I only need the Date part of the FormUpdated, not all parts.
You can use SUM(CASE WHEN TrackingStatus = 'SomeTrackingStatus' THEN 1 ELSE 0 END)) to get the status count for each tracking status in individual column. Something like this. SQL Fiddle
select
CONVERT(DATE,FormUpdated) FormUpdated,
DATENAME(dw, CONVERT(DATE,FormUpdated)),
SUM(CASE WHEN TrackingStatus = 'ViewRates' THEN 1 ELSE 0 END) c_ViewRates,
SUM(CASE WHEN TrackingStatus = 'Complete' THEN 1 ELSE 0 END) c_Complete,
SUM(CASE WHEN TrackingStatus = 'SaveforLater' THEN 1 ELSE 0 END) c_SaveforLater
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= DATEADD(D,-8,GetDate())
group by CONVERT(DATE,FormUpdated)
order by CONVERT(DATE,FormUpdated) desc
You can also use a PIVOT to achieve this result - you'll just need to complete the list of TrackingStatus names in both the SELECT and the FOR, and no GROUP BY required:
WITH DatesOnly AS
(
SELECT Id, CAST(FormUpdated AS DATE) AS DateOnly, DATENAME(dw, FormUpdated) AS DayOfWeek, TrackingStatus
FROM Sales
)
SELECT DateOnly, DayOfWeek,
-- List of Pivoted Columns
[Complete],[Incomplete], [ViewRates], [SaveforLater]
FROM DatesOnly
PIVOT
(
COUNT(Id)
-- List of Pivoted columns
FOR TrackingStatus IN([Complete],[Incomplete], [ViewRates], [SaveforLater])
) pvt
WHERE DateOnly <= GETDATE() AND DateOnly >= GetDate() - 8
ORDER BY DateOnly DESC
SqlFiddle
Also, I think your ORDER BY is wrong - it should just be the Date, not day of week.

SQL query to fetch data based on date range

i want to fetch data for below scenario
input: (Let say today is: 1-Mar-2015)
LicenseNo LicenseEndDate LicenseType Amount
1 1-Apr-2015 AB 100
2 5-Apr-2015 AB 150
3 7-Apr-2015 BC 200
4 10-July-2015 AB 120
5 10-july-2015 BC 140
Expected O/P
AB BC
Between 0-3 months 250 200
Between 3-6 months 120 140
this may increase
SELECT 'Between 0-3 months',
SUM(Case when l.LicenseType='AB' then l.Amount End),
SUM(Case when l.LicenseType='BC' then l.Amount End)
FROM licence l
WHERE l.LicenceEndDate BETWEEN #inputDate AND DATEADD (month , 3 , #inputDate)
UNION
SELECT 'Between 3-6 months',
SUM(Case when l.LicenseType='AB' then l.Amount End),
SUM(Case when l.LicenseType='BC' then l.Amount End)
FROM licence l
WHERE l.LicenceEndDate BETWEEN DATEADD (month , 3 , #inputDate) AND DATEADD (month , 6 , #inputDate)
Union of two queries for the two interval.
Or you can create a temporary table based on your input date like this
| ID | DESCRIPTION | DATA_MIN | DATA_MAX |
| 1 | Between 0-3 months | #input | #input + 3|
| 2 | Between 3-6 months | #input +3| #input + 6|
And use that for your join
Having the derived table in this solution just makes grouping easier in the outer select, it saves us from repeating ourselves too much. The SUM(case...end) structure is one way of pivoting our results, you could look at the pivot operator but I think it is overkill for this scenario. I also added a few other cases even though your supplied data doesn't use them because I figure they are likely. you can always add a where clause if you are looking at specific groups and this is facilitated by the derived table as well.
I have used GETDATE() but you can substitute a date variable for that if it suits better.
declare #t as table
(
LicenseNo int,
LicenseEndDate datetime,
LicenseType varchar(2),
Amount numeric(10,2)
)
insert into #t
values
(1,'1-Apr-2015','AB',100),
(2,'5-Apr-2015','AB',150),
(3,'7-Apr-2015','BC',200),
(4,'10-July-2015','AB',120),
(5,'10-july-2015','BC',140)
declare #comparison_date as datetime = getdate()
select
case ExpGrp
when 0 then 'Expired'
when 1 then 'Expires today'
when 2 then 'Expires in 0-3 months'
when 3 then 'Expires in 3-6 months'
when 4 then 'Not due to expire'
else 'Something went wrong'
end as Descrip,
sum(case when LicenseType = 'AB'
then Amount
else 0
end) as AB,
sum(case when LicenseType = 'BC'
then Amount
else 0
end) as BC
from
(select *,
case
when LicenseEndDate < #comparison_date
then 0
when LicenseEndDate = #comparison_date
then 1
when LicenseEndDate > #comparison_date and LicenseEndDate <= dateadd(MONTH,3,#comparison_date)
then 2
when LicenseEndDate > dateadd(MONTH,3,#comparison_date) and LicenseEndDate <= dateadd(MONTH,6,#comparison_date)
then 3
else 4
end as ExpGrp
from #t) t
group by t.ExpGrp

How to convert Dynamic 7 day rows into columns with t-sql

Background Info
I have a large table 400M+ rows that changes daily (one days data drops out an a new days data drops in) The table is partitioned on a 'day' field so there are 31 paritions.
Each row in the table has data similar to this:
ID, Postcode, DeliveryPoint, Quantity, Day, Month
1 SN1 1BG A1 6 29 1
2 SN1 1BG A1 1 28 1
3 SN1 1BG A2 2 27 1
4 SN1 1BG A1 3 28 1
5 SN2 1AQ B1 1 29 12
6 SN1 1BG A1 2 26 12
I need to pull out 7 days of data in the format:
Postcode, Deliverypoint, 7dayAverage, Day1,day2,Day3,Day4,Day5,Day6,Day7
SN1 1BG A1 2 0 1 2 1 3 4 0
I can easily extract the data for the 7 day period but need to create a columnar version as shown above.
I have something like this:
select postcode,deliverypoint,
sum (case day when 23 then quantity else 0 end) as day1,
sum (case day when 24 then quantity else 0 end) as day2,
sum(case day when 25 then quantity else 0 end) as day3,
sum(case day when 26 then quantity else 0 end) as day4,
sum(case day when 27 then quantity else 0 end) as day5,
sum(case day when 28 then quantity else 0 end) as day6,
sum(case day when 29 then quantity else 0 end) as day7,
sum(quantity)*1.0/#daysinweek as wkavg
into #allweekdp
from maintable dp with (nolock)
where day in (select day from #days)
group by postcode,deliverypoint
where #days has the day numbers in the 7 day period.
But as you can see, I've hard-coded the day numbers into the query, I want to get them out of my temporary table #days but can't see a way of doing it (an array would be perfect here)
Or a I going about this in completely the wrong way ?
Kind Regards
Steve
If I understand correctly, what I would do is:
Convert the day and month columns into datetime values,
Get the first day of the week and day of the weekday (1-7) for each date, and
Pivot the data and group by the first day of the week
see here: sqlfiddle
As utexaspunk suggested, Pivot might be the way to go. I've never been comfortable with pivot and have preferred to pivot it manually so I control how everything looks, so I'm using a similar solution to how you did your script to solve the issue. No idea how the performance between my way and utexaspunk's will compare.
Declare #Min_Day Integer = Select MIN(day) as Min_Day From #days;
With Day_Coding_CTE as (
Select Distinct day
, day - #Min_Day + 1 as Day_Label
From #days
)
, Non_Columnar_CTE as (
Select dp.postcode
, dp.deliverypoint
, d.day
, c.Day_Label
, SUM(quantity) as Quantity
From maintable dp with (nolock)
Left Outer Join #days d
on dp.day = d.day --It also seems like you'll need more criteria here, but you'll have to figure out what those should be
Left Outer Join Day_Coding_CTE c
on d.day = c.day
)
Select postcode
, deliverypoint
, SUM(Case
When Day_Label = 1
Then Quantity
Else 0
End) as Day1
, SUM(Case
When Day_Label = 2
Then Quantity
Else 0
End) as Day2
, SUM(Case
When Day_Label = 3
Then Quantity
Else 0
End) as Day3
, SUM(Case
When Day_Label = 4
Then Quantity
Else 0
End) as Day4
, SUM(Case
When Day_Label = 5
Then Quantity
Else 0
End) as Day5
, SUM(Case
When Day_Label = 6
Then Quantity
Else 0
End) as Day6
, SUM(Case
When Day_Label = 7
Then Quantity
Else 0
End) as Day7
, SUM(Quantity)/#daysinweek as wkavg
From Non_Columnar_CTE
Group by postcode
deliverypoint

Combine data from a table to one row T-SQL

I have a table in #SQL server 2008 that has transaction data. The table looks like this. I would like to have this in a sql statement.
TransactionId|TransactionDate|TransactionType|Amount|Balance|UserId
The transaction type can be one of four types, Deposit, Withdrawals, Profit and Stake. I give an example how it can look like in the transaction table. The balance is the Sum of amount column.
TransactionId|TransactionDate|TransactionType|Amount|Balance|UserId
1| 2013-03-25| Deposit| 150| 150| 1
2| 2013-03-27| Stake| -20| 130| 1
3| 2013-03-28| Profit | 1500| 1630| 1
4 | 2013-03-29| Withdrawals| -700| 930| 1
5| 2013-03-29| Stake | -230 | 700 | 1
6| 2013-04-04| Stake| -150 | 550| 1
7| 2013-04-06| Stake | -150 | 400| 1
What I want now is to get a select statement that gives me all data grouped by week. The result should look like this.
Week|Deposit|Withdrawals|Stake|Profit|Balance|Year
13 | 150| -700 | -250 | 1500 | 700 | 2013
14 | 0 | 0 | -300| 0 | 400 | 2013
I have also problem with the weeks... I live in Europe an my first day in a week is monday. I have a solution for that but around the end of a year I get sometimes week 54 but there are only 52 weeks in a year...
I hope someone can help me out.
This is what I have so far.
SELECT transactionid,
transactiondate,
transactiontype,
amount,
(SELECT Sum(amount)
FROM transactions AS trans_
WHERE trans_.transactiondate <= trans.transactiondate
AND userid = 1) AS Balance,
userid,
Datepart(week, transactiondate) AS Week,
Datepart(year, transactiondate) AS Year
FROM transactions trans
WHERE userid = 1
ORDER BY transactiondate DESC,
transactionid DESC
Here's sample data and my query on sql-fiddle: http://www.sqlfiddle.com/#!3/79d65/92/0
In order to transform the data from the rows into columns, you will want to use the PIVOT function.
You did not specify what balance value you want to return but based on the final result, it looks like you want the final balance to be the value associated with the last transaction date for each day. If that is not correct, then please clarify what the logic should be.
In order to get the result you will want to use the DATEPART and YEAR functions. These will allow grouping by both the week and year values.
The following query should get the result that you want:
select week,
coalesce(Deposit, 0) Deposit,
coalesce(Withdrawals, 0) Withdrawals,
coalesce(Stake, 0) Stake,
coalesce(Profit, 0) Profit,
Balance,
Year
from
(
select datepart(week, t1.transactiondate) week,
t1.transactiontype,
t2.balance,
t1.amount,
year(t1.transactiondate) year
from transactions t1
cross apply
(
select top 1 balance
from transactions t2
where datepart(week, t1.transactiondate) = datepart(week, t2.transactiondate)
and year(t1.transactiondate) = year(t2.transactiondate)
and t1.userid = t2.userid
order by TransactionId desc
) t2
) d
pivot
(
sum(amount)
for transactiontype in (Deposit, Withdrawals, Stake, Profit)
) piv;
See SQL Fiddle with Demo. The result is:
| WEEK | DEPOSIT | WITHDRAWALS | STAKE | PROFIT | BALANCE | YEAR |
------------------------------------------------------------------
| 13 | 150 | -700 | -250 | 1500 | 700 | 2013 |
| 14 | 0 | 0 | -300 | 0 | 400 | 2013 |
As a side note, you stated that your start of the week is Monday, you might have to use the DATEFIRST function to set the first day of the week.
Another option, without using PIVOT, but rather with few CASEs
WITH CTE AS
(
SELECT
TransactionId
,TransactionDate
,DATEPART(WEEK, TransactionDate) AS Week
,CASE WHEN TransactionType='Deposit' THEN Amount ELSE 0 END AS Deposit
,CASE WHEN TransactionType='Stake' THEN Amount ELSE 0 END AS Stake
,CASE WHEN TransactionType='Profit' THEN Amount ELSE 0 END AS Profit
,CASE WHEN TransactionType='Withdrawals' THEN Amount ELSE 0 END AS Withdrawals
,Balance
,DATEPART(YEAR, TransactionDate) AS Year
FROM dbo.Transactions
)
SELECT
Week, SUM(Deposit) AS Deposit, SUM(Withdrawals) AS Withdrawals, SUM(Stake) AS Stake, SUM(Profit) AS Profit,
(SELECT Balance FROM CTE i WHERE i.TransactionID = MAX(o.TransactionID)) AS BAlance, Year
FROM CTE o
GROUP BY Week, Year
SQLFiddle Demo
http://www.sqlfiddle.com/#!3/79d65/89
;WITH cte AS
(
SELECT datepart(ww, transactiondate) wk,
sum(CASE WHEN TransactionType = 'Deposit' THEN Amount ELSE 0 END) AS D,
sum(CASE WHEN TransactionType = 'Withdrawals' THEN Amount ELSE 0 END) AS W,
sum(CASE WHEN TransactionType = 'Profit' THEN Amount ELSE 0 END) AS P,
sum(CASE WHEN TransactionType = 'Stake' THEN Amount ELSE 0 END) AS S,
sum(
CASE WHEN TransactionType = 'Deposit' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Withdrawals' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Profit' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Stake' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Balance' THEN Amount ELSE 0 END) AS wkTotal
FROM transactions
GROUP BY datepart(ww, transactiondate)),
cte1 AS
(
SELECT *, row_number() over (ORDER BY wk) AS rowNum
FROM cte)
SELECT wk, d, w, p, s, wktotal
+ coalesce((SELECT top 1 wktotal FROM cte1 x WHERE x.rownum < m.rownum ), 0) AS RunningBalance
FROM cte1 m