cumulative totals based on condition - sql-server-2008

I am trying to get the cumulative totals based on some criteria. Below is a dummy sample data set. I would like to get the cumulative time based on the Indicator as well as ID. When the Indicator is continuously 1 for the same ID, I would like to get the sum of all the Duration. If it becomes 0 then I would like to restart.
ID Duration Indicator Cumm_duration
1 30 0 30
1 30 1 60
1 30 0 30
1 30 0 30
1 30 1 60
1 30 0 30
1 30 0 30
1 30 0 30
1 30 0 30
1 30 0 30
1 30 0 30
1 30 0 30
1 30 1 60
1 30 1 90
2 30 1 30
2 30 0 30
2 30 0 30
2 30 0 30
2 30 1 60
2 30 0 30
2 30 1 60
2 30 0 30
2 30 0 30
2 30 0 30
2 30 1 60
2 30 1 90
2 30 0 30

Data:
DECLARE #t TABLE
(
ID INT ,
Duration INT ,
INDICATOR INT
)
INSERT INTO #t
VALUES ( 1, 30, 0 ),
( 1, 30, 1 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 1 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 0 ),
( 1, 30, 1 ),
( 1, 30, 1 ),
( 2, 30, 1 ),
( 2, 30, 0 ),
( 2, 30, 0 ),
( 2, 30, 0 ),
( 2, 30, 1 ),
( 2, 30, 0 ),
( 2, 30, 1 ),
( 2, 30, 0 ),
( 2, 30, 0 ),
( 2, 30, 0 ),
( 2, 30, 1 ),
( 2, 30, 1 ),
( 2, 30, 0 );
Solution1(bruteforce):
with indexed
as ( select * ,
row_number() over ( order by ( select null ) ) rn
from #t
),
recursion
as ( select * ,
duration as cumulative
from indexed
where rn = 1
union all
select t.* ,
case when t.indicator = 1 and t.id = r.id
then r.cumulative + t.duration
else t.duration
end
from indexed t
join recursion r on r.rn + 1 = t.rn
)
select * from recursion
option ( maxrecursion 0 )
Solution2:
with indexed as (select *, row_number() over (order by (select null)) rn from #t)
select *,
(select sum(duration) from indexed i2
where i1.id = i2.id and i2.rn <= i1.rn and
i2.rn >= isnull((select top 1 i3.rn from indexed i3
where i3.indicator = 0 and i3.rn <= i1.rn
order by i3.rn desc), 0))
from indexed i1

Related

Mysql select query count until reach the condition with condition

I have lists of users with his points and game id. I need to find the rank of the specified user based on the game order by the max(lb_point).
I have already done the query for getting the rank based on individual game as follows.
select count(*) AS user_rank
from (
select distinct user_id
from leader_board
where lb_point >= (select max( lb_point )
from leader_board
where user_id = 1
and game_id = 2 )
and game_id = 2
) t
But i need to find the rank based on the overall game. Example i have 3 different games (1,2,3). By passing the user_id, i need to find his overall rank among all three games. Can you please help me on this?
lb_id user_id game_id lb_point
------------------------------------------------
1 1 2 670
2 1 1 200
3 1 2 650
4 1 1 400
5 3 2 700
6 4 2 450
7 2 1 550
8 2 1 100
9 1 1 200
10 2 1 100
11 1 1 200
12 2 1 100
13 1 1 200
14 2 1 100
15 1 1 200
16 2 1 100
17 1 1 200
18 2 1 100
19 1 1 200
20 2 1 100
21 1 1 200
22 2 1 800
use sandbox;
/*create table t (lb_id int, user_id int, game_id int, lb_point int);
truncate table t;
insert into t values
(1 , 1, 2, 670),
(2 , 1, 1, 200),
(3 , 1, 2, 650),
(4 , 1, 1, 400),
(5 , 3, 2, 700),
(6 , 4, 2, 450),
(7 , 2, 1, 550),
(8 , 2, 1, 100),
(9 , 1, 1, 200),
(10, 2, 1, 100),
(11, 1, 1, 200),
(12, 2, 1, 100),
(13, 1, 1, 200),
(14, 2, 1, 100),
(15, 1, 1, 200),
(16, 2, 1, 100),
(17, 1, 1, 200),
(18, 2, 1, 100),
(19, 1, 1, 200),
(20, 2, 1, 100),
(21, 1, 1, 200),
(22, 2, 1, 800);
*/
select t.*
from
(
select s.*,#rn:=#rn+1 as rank
from
(
select user_id, sum(lb_point) points
from t
where lb_id = (select t1.lb_id from t t1 where t1.user_id = t.user_id and t1.game_id = t.game_id order by t1.lb_point desc limit 1)
group by user_id
order by points desc
) s
,(select #rn:=0) rn
) t
where t.user_id = 1
The innermost query grabs the highest score per game per user and sums it.
The next query assigns a rank based on the aggregated score per user.
The outermost query selects the user.

SQL Query to get the total sales per seller by month

I try this:
SELECT
profile.user_id,
profile.name,
total_month.total as month10
FROM profile
LEFT OUTER JOIN (SELECT
order.seller_id,
COUNT(*) AS total
FROM order
WHERE MONTH(order.data_hora) = 10
GROUP BY order.seller_id) AS total_month
ON total_month.seller_id= profile.user_id;
The result was this:
-------------------------
|user_id| name |month10|
-------------------------
| 5 |user1 | 73 |
| 1 |user2 | 1 |
-------------------------
But I need more months like this:
-------------------------------------------------
| user_id | name | month10 | month11 | month12 |
-------------------------------------------------
| 5 | user1 | 73 | 52 | 65 |
| 1 | user2 | 67 | 56 | 78 |
-------------------------------------------------
How could I do this without creating a function?
You can extend your query to do what you want. Just be more flexible in the subquery:
SELECT p.user_id, p.name,
tm.month10, tm.month11, tm.month12
FROM profile p LEFT OUTER JOIN
(SELECT o.seller_id,
sum(o.data_hora = 10) as month10,
sum(o.data_hora = 11) as month11,
sum(o.data_hora = 12) as month12
FROM order o
WHERE MONTH(order.data_hora) in (10, 11, 12)
GROUP BY order.seller_id
) tm
ON tm.seller_id = p.user_id;
Well as I said in the comments supporting the #Schalk comment, in order to get this working as you want you will need a function to get a DYNAMIC PIVOT TABLE or TRANSPOSE ROWS TO COLUMNS google it if you prefer.
For your solution I've created a query that gives you all months/values like this:
user_id name jan feb mar apr may jun jul aug sep oct nov dec
1 a 0 0 1 10 0 0 1 2 7 2 3 0
2 b 1 0 0 0 2 0 0 0 3 1 1 0
If it fits to your problem this is your query:
select
profile.user_id,
profile.name,
sum(if( MONTH(orderr.data_hora) = 1, 1, 0 )) as Jan,
sum(if( MONTH(orderr.data_hora) = 2, 1, 0 )) as Feb,
sum(if( MONTH(orderr.data_hora) = 3, 1, 0 )) as Mar,
sum(if( MONTH(orderr.data_hora) = 4, 1, 0 )) as Apr,
sum(if( MONTH(orderr.data_hora) = 5, 1, 0 )) as May,
sum(if( MONTH(orderr.data_hora) = 6, 1, 0 )) as Jun,
sum(if( MONTH(orderr.data_hora) = 7, 1, 0 )) as Jul,
sum(if( MONTH(orderr.data_hora) = 8, 1, 0 )) as Aug,
sum(if( MONTH(orderr.data_hora) = 9, 1, 0 )) as Sep,
sum(if( MONTH(orderr.data_hora) = 10, 1, 0 )) as Oct,
sum(if( MONTH(orderr.data_hora) = 11, 1, 0 )) as Nov,
sum(if( MONTH(orderr.data_hora) = 12, 1, 0 )) as Dece
from
profile left join orderr
on profile.user_id = orderr.seller_id
group by profile.user_id,
profile.name
I've created a fiddle to it (but the data_hora column I created as integer to make it quickly to do it, it is for understanding).
http://sqlfiddle.com/#!2/4a1a2e/5
It was perfect!
The field type 'data_hora' is datetime, so I made a small change.
SELECT p.user_id,
p.name,
tm.month10,
tm.month11,
tm.month12,
(tm.month10+tm.month11+tm.month12) AS final_total
FROM profile p
LEFT OUTER JOIN
(SELECT o.seller_id,
sum(month(o.data_hora) = 10) AS month10,
sum(month(o.data_hora) = 11) AS month11,
sum(month(o.data_hora) = 12) AS month12
FROM order o
WHERE MONTH(ORDER.data_hora) IN (10, 11, 12)
GROUP BY ORDER.seller_id ) tm
ON tm.seller_id = p.user_id
ORDER BY final_total DESC;
How could I optimize the field "final_total"?

SQL Select Query Help. Maximum sum of consequtive four rows.

We have a traffic counter that counts cars in each lane (two inbound and two outbound) in 15 minute increments.
There is a peak period which is defined as 7:00am to 9:00am. Within this peak period we want to know the PeakHourIn and PeakHourOut and PeakHourSum.
The PeakHourIn is the highest consecutive 4x15 minute total (1 hour) for lne1in + lne4in
The PeakHourOut is the highest consecutive 4x15 minute total (1 hour) for lne2out + lne3out
The PeakHourSum is the highest consecutive 4x15 minute total (1 hour) for all lanes.
Date Time lne1in lne2out lne3out lne4in
09-18-2012 5:45 AM 2 0 0 0
09-18-2012 6:00 AM 1 0 0 1
09-18-2012 6:15 AM 2 1 0 0
09-18-2012 6:30 AM 2 1 0 0
09-18-2012 6:45 AM 6 1 2 1
09-18-2012 7:00 AM 9 1 0 3
09-18-2012 7:15 AM 81 12 22 15
09-18-2012 7:30 AM 144 31 63 56
09-18-2012 7:45 AM 84 30 62 42
09-18-2012 8:00 AM 7 1 0 3
09-18-2012 8:15 AM 11 2 3 3
09-18-2012 8:30 AM 12 3 7 1
09-18-2012 8:45 AM 16 4 8 0
09-18-2012 9:00 AM 5 2 5 0
09-18-2012 9:15 AM 10 1 4 0
Results should look like:
PeakHourIn 434
PeakHourOut 221
PeakHourSum 655
Any help would be greatly appreciated.
If you used a native temporal data type to store the date/time, you could group multiple self-joins:
SELECT MAX(lne1in + lne4in ) AS PeakHourIn,
MAX(lne2out + lne3out) AS PeakHourOut,
MAX(lne1in + lne2out + lne3out + lne4in) AS PeakHourSum
FROM (
SELECT t1.lne1in + t2.lne1in + t3.lne1in + t4.lne1in AS lne1in,
t1.lne2out + t2.lne2out + t3.lne2out + t4.lne2out AS lne2out,
t1.lne3out + t2.lne3out + t3.lne3out + t4.lne3out AS lne3out,
t1.lne4in + t2.lne4in + t3.lne4in + t4.lne4in AS lne4in
FROM my_table t1
JOIN my_table t2 ON t2.DateTime = t1.DateTime + INTERVAL 15 MINUTE
JOIN my_table t3 ON t3.DateTime = t2.DateTime + INTERVAL 15 MINUTE
JOIN my_table t4 ON t4.DateTime = t3.DateTime + INTERVAL 15 MINUTE
WHERE TIME(t1.DateTime) BETWEEN '07:00:00' AND '08:00:00'
GROUP BY t1.DateTime
) t
EDIT
Here's a solution in MySQL: http://sqlfiddle.com/#!2/ff0fb/9
create table TrafficData
(
StartTime timestamp
,Lane int
,CarCount int
);
create table LaneData
(
Lane int
, Direction bit
);
insert LaneData
select 1, 0
union select 2, 1
union select 3, 1
union select 4, 0;
insert TrafficData
select dt, lane
, case lane
when 1 then l1
when 2 then l2
when 3 then l3
when 4 then l4
else null
end
from
(
select '2012-09-18 05:45' dt, 2 l1, 0 l2, 0 l3, 0 l4
union all select '2012-09-18 06:00', 1, 0, 0, 1
union all select '2012-09-18 06:15', 2, 1, 0, 0
union all select '2012-09-18 06:30', 2, 1, 0, 0
union all select '2012-09-18 06:45', 6, 1, 2, 1
union all select '2012-09-18 07:00', 9, 1, 0, 3
union all select '2012-09-18 07:15', 81, 12, 22, 15
union all select '2012-09-18 07:30', 144, 31, 63, 56
union all select '2012-09-18 07:45', 84, 30, 62, 42
union all select '2012-09-18 08:00', 7, 1, 0, 3
union all select '2012-09-18 08:15', 11, 2, 3, 3
union all select '2012-09-18 08:30', 12, 3, 7, 1
union all select '2012-09-18 08:45', 16, 4, 8, 0
union all select '2012-09-18 09:00', 5, 2, 5, 0
union all select '2012-09-18 09:15', 10, 1, 4, 0
) as originalTable
cross join LaneData;
select Lane, max(SumCarCount) as MaxSumCarCount
from
(
select a.Lane, SUM(b.CarCount) as SumCarCount
from TrafficData a
inner join TrafficData b
on b.Lane = a.Lane
and b.StartTime between a.StartTime and DATE_ADD(DATE_ADD(a.starttime, interval 1 hour), interval -1 second)
where time(a.StartTime) between '07:00' and '08:15'
group by a.Lane, a.StartTime
) x
group by Lane
order by Lane;
select Direction, max(SumCarCount) as MaxSumCarCount
from
(
select al.Direction, SUM(b.CarCount) SumCarCount
from TrafficData a
inner join LaneData al
on al.Lane = a.Lane
inner join TrafficData b
on b.StartTime between a.StartTime and DATE_ADD(DATE_ADD(a.starttime, interval 1 hour), interval -1 second)
inner join LaneData bl
on bl.Lane = b.Lane
and bl.Direction = al.Direction
where time(a.StartTime) between '07:00' and '08:15'
group by al.Direction, a.StartTime
) x
group by Direction
order by Direction;
ORIGINAL
Here's how I'd go about it in SQL Server:
--I'd change your table structure to be like this - that way you can easily add new lanes without rewriting the whole system
declare #trafficData table
(
StartTime DateTime
,Lane int
,CarCount int
)
--here's where you store additional info about the lanes (e.g. what direction they go in)
declare #laneData table
(
Lane int
, Direction bit --0 in, 1 out
)
--populate the tables with sample data
insert #laneData
select 1, 0
union select 2, 1
union select 3, 1
union select 4, 0
insert #trafficData
select dt, lane
, case lane
when 1 then l1
when 2 then l2
when 3 then l3
when 4 then l4
else null --should never happen
end
from
(
select '2012-09-18 5:45 AM' dt, 2 l1, 0 l2, 0 l3, 0 l4
union all select '2012-09-18 6:00 AM', 1, 0, 0, 1
union all select '2012-09-18 6:15 AM', 2, 1, 0, 0
union all select '2012-09-18 6:30 AM', 2, 1, 0, 0
union all select '2012-09-18 6:45 AM', 6, 1, 2, 1
union all select '2012-09-18 7:00 AM', 9, 1, 0, 3
union all select '2012-09-18 7:15 AM', 81, 12, 22, 15
union all select '2012-09-18 7:30 AM', 144, 31, 63, 56
union all select '2012-09-18 7:45 AM', 84, 30, 62, 42
union all select '2012-09-18 8:00 AM', 7, 1, 0, 3
union all select '2012-09-18 8:15 AM', 11, 2, 3, 3
union all select '2012-09-18 8:30 AM', 12, 3, 7, 1
union all select '2012-09-18 8:45 AM', 16, 4, 8, 0
union all select '2012-09-18 9:00 AM', 5, 2, 5, 0
union all select '2012-09-18 9:15 AM', 10, 1, 4, 0
) originalTable
cross join #laneData
--peak for each individual lane
select *
from
(
select a.Lane, a.StartTime, SUM(b.CarCount) SumCarCount
, ROW_NUMBER() over (partition by a.lane order by SUM(b.CarCount) desc) r
from #trafficData a
inner join #trafficData b
on b.Lane = a.Lane
and b.StartTime between a.StartTime and DATEADD(second,-1,DATEADD(hour,1,a.starttime))
group by a.Lane, a.StartTime
) x
where r = 1
order by Lane
--peak for lane direction
select *
from
(
select al.Direction, a.StartTime, SUM(b.CarCount) SumCarCount
, ROW_NUMBER() over (partition by al.Direction order by SUM(b.CarCount) desc) r
from #trafficData a
inner join #laneData al
on al.Lane = a.Lane
inner join #trafficData b
on b.StartTime between a.StartTime and DATEADD(second,-1,DATEADD(hour,1,a.starttime))
inner join #laneData bl
on bl.Lane = b.Lane
and bl.Direction = al.Direction
group by al.Direction, a.StartTime
) x
where r = 1
order by Direction

Creating summary report from multiple fields from a set of data

I have a task to write an sql to summarize the following data:
select
payment,
status,
interest,
principal
from payment
payment status interest principal
1 A 100 0
2 r 0 500
3 o 0 400
4 d 0 100
5 A 0 200
6 A 200 0
7 A 300 0
8 A 0 300
a payment relating to interest would be where interest is not 0
a payment relating to principal would be where a principal is not 0
the data would need to be split into type
The result I am looking for is similar to this:
Interest total count principal total count
Status - A 3 1
Other 0 4
total of all payments 3 5
I have been using group function but have not been able to group beyond the interest amount. Any suggestions would be greatly appreciated.
I would suggest to use GROUP BY + WITH ROLLUP for this. Something like:
SELECT
IF(`status` = 'A', 'Status A', 'Others') as `-`,
SUM(`interest` > 0) as `Interest total count`,
SUM(`principal` > 0) as `principal total count`
FROM
(
SELECT 1 as `payment`, 'A' as `status`, 100 as `interest`, 0 as `principal`
UNION ALL
SELECT 2, 'r', 0, 500
UNION ALL
SELECT 3, 'o', 0, 400
UNION ALL
SELECT 4, 'd', 0, 100
UNION ALL
SELECT 5, 'A', 0, 200
UNION ALL
SELECT 6, 'A', 200, 0
UNION ALL
SELECT 7, 'A', 300, 0
UNION ALL
SELECT 8, 'A', 0, 300
) as `sub`
GROUP BY 1 WITH ROLLUP;
Result:
- Interest total count principal total count
Others 0 3
Status A 3 2
3 5
Query:
SELECT
IF(`status` = 'A', 'Status A', 'Others') as `-`,
SUM(`interest` > 0) as `Interest total count`,
SUM(`principal` > 0) as `principal total count`
FROM
`payment`
GROUP BY 1 WITH ROLLUP;

Calculation of Percentage for Rows within particular range

I have a List of students name and Marks Stored in a table like below.
CREATE TABLE StudentsList(StudentId INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(50),
Marks INT);
INSERT INTO StudentsList(Name, Marks)
VALUES('Student A', 20),
('Student B', 45),
('Student C', 90),
('Student D', 81),
('Student E', 50),
('Student F', 10),
('Student G', 85),
('Student H', 41),
('Student I', 66),
('Student J', 65),
('Student K', 05),
('Student L', 20),
('Student M', 19),
('Student N', 80),
('Student O', 90),
('Student P', 91),
('Student Q', 10),
('Student R', 29);
I want to Group the no of students based on the mark range and percentage they contribute in whole.
MarkRange NoOfStudents Percentage
0 - 20 4 22.22
20 - 50 5 27.77
50 - 70 3 16.66
70 - 90 3 16.66
90 3 16.66
I Tried the Below Query and Brought the Result for Students between 0 -20
SELECT COUNT(*) , COUNT(*)/(T.total)* 100
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks < 20
How can I do this by using single Query
Try this:
SELECT CONCAT(A.minRange, ' - ', A.maxRange) MarkRange, COUNT(sl.Name) NoOfStudents,
(SELECT COUNT(sl.Name) / COUNT(*) * 100 FROM StudentsList) Percentage
FROM studentslist sl
INNER JOIN (SELECT 1 id, 0 minRange, 20 maxRange
UNION
SELECT 2 id, 20 minRange, 50 maxRange
UNION
SELECT 3 id, 50 minRange, 70 maxRange
UNION
SELECT 4 id, 70 minRange, 90 maxRange
UNION
SELECT 5 id, 90 minRange, 100 maxRange
) AS A ON sl.Marks >= A.minRange AND sl.Marks < A.maxRange
GROUP BY A.id;
SELECT COUNT(*) , COUNT(*)/(T.total)* 100, '0 - 20' AS Range
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks < 20
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100, '20 - 50' AS Range
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 20 and Marks < 50
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100, '50 - 70' AS Range
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 50 and Marks < 70
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100, '70 - 90' AS Range
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 70 and Marks < 90
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100, '90 Above' AS Range
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 90
I found an Answer Using Union.
If there is some other solution kindly suggest me
Try this:
SELECT COUNT(*) as 'number of students', COUNT(*)/(T.total)* 100 as 'Percentage', '0 - 20' AS 'Range'
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks < 20
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100 as 'Percentage', '20 - 50' AS 'Range'
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks >= 20 and Marks < 50
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100 as 'Percentage', '50 - 70' AS 'Range'
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks >= 70 and Marks < 90
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100 as 'Percentage', '70 - 90' AS 'Range'
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks >= 70 and Marks < 90
UNION
SELECT COUNT(*) , COUNT(*)/(T.total)* 100 as 'Percentage', 'Above 90' AS 'Range'
FROM StudentsList,
(SELECT COUNT(*) AS total
FROM StudentsList) AS T
WHERE Marks >= 0 and Marks >= 90
order by 3
output:
NUMBER OF STUDENTS PERCENTAGE RANGE
4 22.2222 0 - 20
5 27.7778 20 - 50
3 16.6667 50 - 70
3 16.6667 70 - 90
3 16.6667 Above 90
You can see how it runs here