Select multiple fields from subquery - mysql

I have the next query:
SELECT
a.Date,
(SELECT SUM(Used), SUM(Max) FROM Switch_Statistic b WHERE Date = (SELECT MAX(Date) FROM Switch_Statistic WHERE Switch_ID = b.Switch_ID AND Date <= a.Date))
FROM Switch_Statistic a
GROUP BY Date;
As you see I need to select SUM(Used), SUM(Max) from subquery. With CONCAT is not good solution!
Table schema:
ID --- Switch_ID --- Date --- Max --- Used
Some data:
1 641 2014-10-04 2 16
20 630 2014-10-04 1 7
24 634 2014-10-04 0 8
26 641 2014-10-06 2 16
32 641 2014-10-07 2 16
35 641 2014-10-08 3 16
39 641 2014-10-09 2 16
64 293 2014-10-10 1 22
...
557 38 2014-10-12 3 22
559 293 2014-10-12 1 22
563 294 2014-10-12 6 22
565 641 2014-10-12 2 16
What I need:
Example with CONCAT_WS
mysql> SELECT
a.Date,
(SELECT CONCAT_WS('/', SUM(Used), SUM(Max)) FROM Switch_Statistic b WHERE Date = (SELECT MAX(Date) FROM Switch_Statistic WHERE Switch_ID = b.Switch_ID AND Date <= a.Date)) AS Result
FROM Switch_Statistic a
GROUP BY Date;
+------------+----------+
| Date | Result |
+------------+----------+
| 2014-10-04 | 3/31 |
| 2014-10-06 | 3/31 |
| 2014-10-07 | 3/31 |
| 2014-10-08 | 4/31 |
| 2014-10-09 | 3/31 |
| 2014-10-10 | 249/1587 |
| 2014-10-11 | 354/2147 |
| 2014-10-12 | 360/2185 |
+------------+----------+
8 rows in set (0.26 sec)
Query logic:
1) Select all date's from table
2) SUM - Used and Max for current date, if Switch_ID don't have record for this date, then select the last which exists in table
Link to sqlfiddle - http://sqlfiddle.com/#!2/c3d479

You should be able to do this with just aggregation and no subqueries or joins:
SELECT date, sum(used) as used, sum(max) as max
FROM switch_statistic ss
where ss.date = (select max(date) from Switch_Statistics ss2 where ss2.Switch_id = ss.SwitchId
GROUP BY ss.date;
EDIT:
You seem to want a cumulative sum. In MySQL, this is often best done using variables:
SELECT date, used, max, (#u := #u + used) as cumeused, #m := #m + max) as cumemax
fROM (SELECT date, sum(used) as used, sum(max) as max
FROM switch_statistic ss
GROUP BY ss.date
) ss CROSS JOIN
(SELECT #u := 0, #m := 0) vars
ORDER BY date;

Related

SQL query to find the concurrent sessions based on start and end time

Below is a sample dataset showing TV sessions of each TV set of each household.
Household “111” switch on their TV “1” at 500 and switch it off at 570. However, this has been captured
in the data as 2 separate rows. You will have to write a query to convert this into a single row.
Similar modification needs to be made to all other subsequent occurrences. Please note that a single
valid TV session can be split into more than 2 rows as well (As shown by rows 5-8).
Input :
Table [session]
Household_ID TV_Set_ID Start_time End_time
111 1 500 550
111 1 550 570
111 1 590 620
111 1 650 670
111 2 660 680
111 2 680 700
111 2 700 750
111 2 750 770
112 2 1050 1060
113 1 1060 1080
113 1 1080 1100
113 1 1100 1120
113 1 1500 1520
Expected Output :-
Household_ID TV_Set_ID Start_time End_time
111 1 500 570
111 1 590 620
111 1 650 670
111 2 660 770
112 2 1050 1060
113 1 1060 1120
113 1 1500 1520
I tried to find the lead time and find the difference and calculate the difference between that and the End time and thought I could group by but then that logic wont work since we dont just want the start and end time but even the gaps in the sessions. I'm stuck with the logic. Could someone tell how to proceed further ?
with result as
(
select Household_ID, TV_Set_ID, Start_time, End_time, lead(Start_time)
over (partition by Household_ID, TV_Set_ID order by Household_ID, TV_Set_ID) as lead_start
from session )
select *,lead_start - End_time as diff from result ;
Here is a way to get this done
In the data block i create groups which is defined as any record whose previous end_time doenst match with my start_time and assign a group_number to it if its different, else i keep it same.
After that in the main block i group by this group_number, along with the household_id,tv_set_id to get the results.
with data
as (
select *
,case when lag(end_time) over(partition by household_id,tv_set_id order by end_time)
<> start_time then
sum(1) over(partition by household_id,tv_set_id order by end_time)
else
sum(0) over(partition by household_id,tv_set_id order by end_time)
end as group_number
from t
)
select household_id
,tv_set_id
,min(start_time) as start_time
,max(end_time) as end_time
from data
group by household_id,tv_set_id,group_number
+--------------+-----------+------------+----------+
| household_id | tv_set_id | start_time | end_time |
+--------------+-----------+------------+----------+
| 111 | 1 | 500 | 570 |
| 111 | 1 | 590 | 620 |
| 111 | 1 | 650 | 670 |
| 111 | 2 | 660 | 770 |
| 112 | 2 | 1050 | 1060 |
| 113 | 1 | 1060 | 1120 |
| 113 | 1 | 1500 | 1520 |
+--------------+-----------+------------+----------+
db fiddle link
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ba5ade186ebc3cf693c505d863691670
You could conditionally increment your data as in this case.
You might need to increment your data in case that the same household and TV set is used later. The column sequence created in the cte is used for that reason. Find the indicative answer.
WITH cte AS (
select t.*,
sum(flag) over (partition by household_id, tv_set_id order by household_id, tv_set_id, start_time) as sequence
from (select t.*,
case when start_time = LAG(end_time,1) OVER (PARTITION BY household_id, tv_set_id ORDER BY household_id, tv_set_id, start_time)
then 0
else 1
end as flag
from t
)t)
SELECT household_id, tv_set_id, MIN(start_time), MAX(end_time)
FROM cte
GROUP BY household_id, tv_set_id, sequence

Getting last values for week in mysql

I would like know how to get the last value for each week.
Let's say I have the next values
-- Table 1 --
day value
2018-03-12 32
2018-02-14 42
2018-03-16 62
2018-03-19 82
2018-03-20 92
2018-03-21 102
2018-03-27 112
2018-03-28 122
2018-03-29 132
How can I get the next values which are the last values for each week. Assuming the week start on Monday.
Day Value
2018-03-16 62
2018-03-21 102
2018-03-29 132
I have everything settled here SQL Fiddle
You can get the week number of day then get the max value per week number.
select t1.*
from table1 t1
join (
select week(day) as wknum,
max(day) as day
from table1
group by week(day)
) t2
on t1.day=t2.day
Result:
day value
2018-03-16 62
2018-03-21 102
2018-03-29 132
You can group by YEARWEEK()
create table tbl (day date, value int);
✓
insert into tbl values
('2018-03-12', 32),
('2018-02-14', 42),
('2018-03-16', 62),
('2018-03-19', 82),
('2018-03-20', 92),
('2018-03-21', 102),
('2018-03-27', 112),
('2018-03-28', 122),
('2018-03-29', 132);
✓
select day, yearweek(day) from tbl;
day | yearweek(day)
:--------- | ------------:
2018-03-12 | 201810
2018-02-14 | 201806
2018-03-16 | 201810
2018-03-19 | 201811
2018-03-20 | 201811
2018-03-21 | 201811
2018-03-27 | 201812
2018-03-28 | 201812
2018-03-29 | 201812
select day, value
from tbl
join (select max(day) mday
from tbl
group by yearweek(day)) t
on day = mday
day | value
:--------- | ----:
2018-02-14 | 42
2018-03-16 | 62
2018-03-21 | 102
2018-03-29 | 132
dbfiddle here
This solution uses window functions and picks the latest date within the week.
https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html
I use SQL Server, but I believe this is the MySQL equivalent:
with cte AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY WEEKOFYEAR([day]) ORDER BY DAYOFWEEK([day]) DESC) AS counter_
from #table1
)
SELECT [day], [value]
FROM cte
WHERE counter_ = 1
Here's how you would do it in SQL Server - Use mysql equivalent
select b.day, b.value from (
select datepart(ww,day) a, max(day) b
from yourtable
group by datepart(ww,day))a
join yourtable b on a.a=datepart(ww,b.day) and a.b=b.day
Try this FIDDLE:
= Order by the closest to the end of every week
= Group by week
SELECT day, value
FROM (SELECT * FROM table1 ORDER BY DATEDIFF(day + INTERVAL 6 - weekday(day) DAY, day) ASC) t
GROUP BY week(day);

MYSQL delete all records except first and last for a given date

This gives me all the records for a particular switch and port. I would like to delete all except the first and last one for each day.
/mysql -sN --user=root --password=notmypassword -e
"SELECT * FROM collector.fibre
WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND (time > '2016-05-31 00:00:00' AND time < '2016-05-31 23:59:59') "
I don't know how much time will it take to delete all this records but i think
minutes of works just wait for it :
delete fb from collector.fibre fb
INNER JOIN
(
SELECT
distinct
f.id -- extract only the ids
FROM
collector.fibre f
INNER JOIN (
-- create a special table day min_time max_time
select
date(time) as 'day',
min(time) as 'min_time',
max(time) as 'max_time'
from
collector.fibre
Where
-- must be the same set of records
-- because we look for a dates of this switch
fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
group by date(time)
) fd on date(f.time) = fd.day -- join the two table to have a special table see example down
WHERE
fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
-- extract record witch the time is not
-- the min or the max of that date
AND (f.time <> fd.min_time and f.time <> fd.max_time)
) recs on fb.id = recs.id -- join the table and the result of query by the id now you will remove the records that exist in the result of the query
by "first and last" I will assume you mean earliest and latest, so:
Select criteria in the given date and then deselect the minimum date and maximum date values.
It is assumed your time is stored in a DATETIME column.
DELETE FROM collector.fibre
WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND (TIME(time) !=
(
SELECT MAX(TIME(time))
FROM `collector`.`fibre` WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND DATE(time) = '2016-05-30'
)
AND
TIME(time) != (
SELECT MIN(TIME(time))
FROM `collector`.`fibre` WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND DATE(time) = '2016-05-30'
)
)
AND DATE(time) = '2016-05-30'
This is probably poorly written and does unfortunately use criteria repetition for the subqueries but the concept works as:
SubSelect by criteria and Not rows where the time column is MAX or MIN time values fitting said criteria
Reference : https://stackoverflow.com/a/7836444/3536236
So you can then input each day and remove non-earliest and non-latest values. If you want to cycle through the whole table in one fell swoop I would figure you want to create a Procedure but I don't know enough to help you much here. However your title says MYSQL delete all records except first and last for a given date which implies you will manually run it one date at a time as needed.
Example:
Example Set:
fabric_switch_name | datatype | port | time
-------------------------------------------------------------------
switch-1 | TxElements | 2 | 2016-05-31 15:00:00
switch-1 | TxElements | 1 | 2016-05-30 22:10:00
switch-2 | TxElements | 0 | 2016-05-30 15:00:00
switch-1 | TxElements | 0 | 2016-05-29 10:20:10
switch-1 | TxElements | 0 | 2016-05-29 05:50:00
switch-1 | TxElements | 0 | 2016-05-29 19:50:00
switch-1 | TxElements | 5 | 2016-05-29 21:50:00
switch-1 | TxElements | 0 | 2016-05-29 20:11:40
Result Set:
fabric_switch_name | datatype | port | time
-------------------------------------------------------------------
switch-1 | TxElements | 2 | 2016-05-31 15:00:00
switch-1 | TxElements | 1 | 2016-05-30 22:10:00
switch-1 | TxElements | 0 | 2016-05-29 05:50:00
switch-2 | TxElements | 0 | 2016-05-30 15:00:00
switch-1 | TxElements | 5 | 2016-05-29 21:50:00
switch-1 | TxElements | 0 | 2016-05-29 20:11:40
this query will extract all the record that should be deleted :
SELECT
*
FROM
collector.fibre f
INNER JOIN (
-- create a special table day min_time max_time
select
date(time) as 'day',
min(time) as 'min_time',
max(time) as 'max_time'
from
collector.fibre
Where fabric_switch_name = 'switch-1'
group by date(time)
) fd on date(f.time) = fd.day -- join the two table to have a special table see example down
WHERE
fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
-- extract record witch the time is not
-- the min or the max of that date
AND (f.time <> fd.min_time and f.time <> fd.max_time)
let suppose this is your date
1 2016-05-31 23:00:00
2 2016-05-31 01:00:00
3 2016-05-31 00:00:00
4 2016-05-30 10:00:12
5 2016-05-30 01:00:00
6 2016-05-30 00:05:00
the result of join :
1 2016-05-31 23:00:00 2016-05-31 2016-05-31 00:00:00 2016-05-31 23:00:00 -- this will not be selected in the result
2 2016-05-31 01:00:00 2016-05-31 2016-05-31 00:00:00 2016-05-31 23:00:00 -- this will be selected because it's not min or max of that day
3 2016-05-31 00:00:00 2016-05-31 2016-05-31 00:00:00 2016-05-31 23:00:00
4 2016-05-30 10:00:00 2016-05-30 2016-05-30 00:05:00 2016-05-30 10:00:12
5 2016-05-30 01:00:00 2016-05-30 2016-05-30 00:05:00 2016-05-30 10:00:12
6 2016-05-30 00:00:00 2016-05-30 2016-05-30 00:05:00 2016-05-30 10:00:12
Give this a try:
DELETE
FROM collector.fibre
WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND time BETWEEN '2016-05-31 00:00:00' AND '2016-05-31 23:59:59'
AND time NOT IN (
SELECT MIN(time), MAX(time)
FROM collector.fibre
WHERE fabric_switch_name = 'switch-1'
AND port = '0'
AND datatype = 'TxElements'
AND time BETWEEN '2016-05-31 00:00:00' AND '2016-05-31 23:59:59'
)

Get the latest record for each employee older than a given date

I have a status history table that also includes future dated records.
Example: employee_jobs
id | employee_id | division_id | department_id | job_id | effective_date
1 100 1 1 1 2015-01-01
2 100 1 1 2 2016-01-01
3 100 1 2 4 2017-01-01
4 200 1 3 5 2016-01-01
5 300 1 3 6 2015-01-01
6 300 1 3 7 2016-05-25
I need a preforming SQL that will show a given employee_id's current live record when given a date: Example Date = 2016-08-15
The result set should be:
id | employee_id | division_id | department_id | job_id | effective_date
2 100 1 1 2 2016-01-01
4 200 1 3 5 2016-01-01
6 300 1 3 7 2016-05-25
I guess you want records for each employee having the latest effective_date with a constraint
(effective_date must be less than or equal to a given date)
SELECT
*
FROM
(
SELECT
*,
IF(#sameEmployee = employee_id, #rn := #rn + 1,
IF(#sameEmployee := employee_id, #rn := 1, #rn := 1)
) AS row_number
FROM employee_jobs
CROSS JOIN (SELECT #sameEmployee := 0, #rn := 1) var
WHERE effective_date <= '2016-08-15'
ORDER BY employee_id, effective_date DESC
) AS t
WHERE t.row_number = 1
ORDER BY t.employee_id

Consecutive dates Sequence by criteria

I have a table that holds tasks like the following example:
id user_id project_id done_at duration type
1 13 15 2014-08-11 03:00:00 1
2 13 15 2014-08-11 04:00:00 1
10 13 15 2014-08-12 04:00:00 1
3 13 15 2014-08-12 08:00:00 2
5 13 13 2014-08-13 08:00:00 1
7 13 15 2014-08-14 04:00:00 1
8 13 15 2014-08-18 08:00:00 1
9 13 15 2014-08-19 08:00:00 2
How can i get start/end sequences of consecutive done_at column by user, project and type like this:
user_id project_id type start_done_at end_done_at duration
13 15 1 2014-08-11 2014-08-12 11:00:00
13 15 2 2014-08-12 2014-08-12 08:00:00
13 13 1 2014-08-13 2014-08-14 12:00:00
13 15 1 2014-08-18 2014-08-18 08:00:00
13 15 2 2014-08-19 2014-08-19 08:00:00
I already tried following this example: http://www.artfulsoftware.com/infotree/qrytip.php?id=76 however i didnt make to get the duration correctly.
My current query is this:
SELECT task.user_id as user_id, task.done_at as start_done_at, MIN(task2.done_at) as end_done_at, task.project_id as project_id, task.timeoff_type_id as timeoff_type, SEC_TO_TIME(SUM(TIME_TO_SEC(task.duration))) as duration
FROM tasks task
LEFT JOIN tasks task1 ON task.done_at = task1.done_at + 1
AND task.user_id = task1.user_id
AND task.project_id = task1.project_id
AND task.timeoff_type_id = task1.timeoff_type_id
LEFT JOIN tasks task2 ON task.done_at <= task2.done_at
AND task.user_id = task2.user_id
AND task.project_id = task2.project_id
AND task.timeoff_type_id = task2.timeoff_type_id
LEFT JOIN tasks task3 ON task2.done_at = task3.done_at -1
AND task2.user_id = task3.user_id
AND task2.project_id = task3.project_id
AND task2.timeoff_type_id = task3.timeoff_type_id
WHERE task1.done_at IS NULL
AND task2.done_at IS NOT NULL
AND task3.done_at IS NULL
GROUP BY task.user_id, task.project_id, task.timeoff_type_id, task.done_at
select
user_id, project_id,
min(done_at) as start_done_at,
max(done_at) as end_done_at,
sec_to_time(sum(time_to_sec(duration))) as duration
from (
select
t.*,
#gn := if(timestampdiff(day, #prev_date, done_at) between 0 and 1 and #prev_type = type and #prev_project = project_id, #gn, #gn + 1) as group_number,
#prev_date := done_at,
#prev_type := type,
#prev_project := project_id
from
t
, (select #prev_project := null, #prev_date := null, #prev_type := null, #gn := 0) var_init
order by project_id, done_at, duration
) sq
group by group_number
see it working live in an sqlfiddle