interval by 4 using sql - Mysql - mysql

I've a table and i want that data is interval by 4 or, when i'm using modulo the record is not that i expected, PFB `
SELECT (DATE_FORMAT(subscribed_from, '%Y-%m')) AS date_ FROM subscription
WHERE operator = 'tim'
AND DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN '2013-01-01' AND '2014-12-31'
GROUP BY (DATE_FORMAT(subscribed_from, '%Y-%m'));
it will show record like this
2013-01
2013-02
2013-03
2013-04
2013-05
2013-06
2013-07
2013-08
2013-09
i want take only data interval by 4, this below is record that i expected.
2013-01
2013-05
2013-09
2014-02
and also for interval by 2, this below record is that i expected
2013-01
2013-03
2013-05
2013-07
2013-09
if i using modulo % 2 it will start from 2013-01 and jump by 2, but the problem if the where range i want to start from 2013-02, 02 it self not showing on the result. so if the where clause the month start from 2 it will given the interval such as 2,4,6,8,10,12

SELECT date_, SUM(the_metric_you_want_to_aggregate)
FROM (
SELECT 4*FLOOR(
(DATE_FORMAT(subscribed_from, '%Y%m') - 201301)
/4) AS date_,
the_metric_you_want_to_aggregate
FROM subscription
WHERE operator = 'tim'
AND subscribed_from BETWEEN 20130101000000 AND 201412315959
) AS ilv
GROUP BY date_
(where 201301 is the year/month start of the range you are selecting by - assuming that is the reference for the 4-month aggregation)
Note that enclosing column references in functions (...DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN...) prevents the use of indexes.

You have to use variables. Here is sample for interval by 4.
SET #row_number:=0;
SELECT date_ from (
SELECT (DATE_FORMAT(subscribed_from, '%Y-%m')) AS date_,#row_number:=#row_number+1 FROM subscription
WHERE operator = 'tim' AND DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN '2013-01-01' AND '2014-12-31'
GROUP BY (DATE_FORMAT(subscribed_from, '%Y-%m'))
) as tbl where #row_number % 4=0;

let says i'm using this method to generate the intevals, but i want the start number is from my input, let says it start from 4 and if the condition put %4 should be the output is 4, 8 ,12 ....
enter code here
SET #row:=0;
SELECT *
FROM (
SELECT
#row := #row +1 AS rownum
FROM (
SELECT #row) r, subscription
) ranked
WHERE rownum %4 = 1

Related

Is there a clever way to the do the following?

Is ther any clear way of doing the following in one SQL (PL) code? :)
What I have in the tables is the following:
CONTACT_TBL (HEADER)
CONTRACT_ID
BEGIN_DATE
END_DATE
TOT_AMOUNT
123
13-MAY-16
12-MAY-34
100
456
13-JAN-14
12-DEC-25
300
789
13-SEP-14
12-OCT-34
700
CONTRACT_ACTIVTY (DETAIL)
CONTRACT_ID
SEQNUM
END_DATE
AMOUNT
COMMENTS
123
1
12-MAY-19
25
25 - Initial Expiration
123
2
12-MAY-24
25
25
123
3
12-MAY-29
25
25
123
4
12-MAY-34
25
25 - End of Contract
What we need is to populate for each row:
BEGIN_DATE
For SEQNUM #1 - BEGIN_DATE is always CONTACT_TBL.BEGIN_DATE (in this case 13-MAY-15, a 3 year span)
For SEQNUM #2 to N - BEGIN_DATE is 1 day more than previous row's END_DATE (in this case 12-MAY-19 + 1 = 13-MAY-19, all 5 year spans)
Mark the row 'Active' if the SYSDATE is between BEGIN_DATE and END_DATE (in this case it's Row #2)
CONTRACT_ID
SEQNUM
BEGIN_DATE
END_DATE
AMOUNT
STATUS
123
1
13-MAY-16
12-MAY-19
25
123
2
13-MAY-19
12-MAY-24
25
Active
123
3
13-MAY-24
12-MAY-29
25
123
4
13-MAY-29
12-MAY-34
25
N.B. This answer assumes the db is Oracle
This can be done by using LAG to find the date from the previous row.
LAG can accept 3 parameters, the first of which is the column whose value you want to find from the previous row, the second is how many rows to go back each time (default is 1), and the third is what to display if there is no previous row found.
In your case, you want to default to the begin_date from the contact_tbl if there is no previous row to the first row in the contract_activity table, so you need to join the two tables as well, like so:
WITH contact_tbl AS (SELECT 123 contract_id, to_date('13/05/2016', 'dd/mm/yyyy') begin_date, to_date('12/05/2034', 'dd/mm/yyyy') end_date, 100 tot_amount FROM dual UNION ALL
SELECT 456 contract_id, to_date('13/05/2016', 'dd/mm/yyyy') begin_date, to_date('12/05/2034', 'dd/mm/yyyy') end_date, 100 tot_amount FROM dual UNION ALL
SELECT 789 contract_id, to_date('13/05/2016', 'dd/mm/yyyy') begin_date, to_date('12/05/2034', 'dd/mm/yyyy') end_date, 100 tot_amount FROM dual),
contract_activity AS (SELECT 123 contract_id, 1 seqnum, to_date('12/05/2019', 'dd/mm/yyyy') end_date, 25 amount, '25 - Initial Expiration' comments FROM dual UNION ALL
SELECT 123 contract_id, 2 seqnum, to_date('12/05/2024', 'dd/mm/yyyy') end_date, 25 amount, '25' comments FROM dual UNION ALL
SELECT 123 contract_id, 3 seqnum, to_date('12/05/2029', 'dd/mm/yyyy') end_date, 25 amount, '25' comments FROM dual UNION ALL
SELECT 123 contract_id, 4 seqnum, to_date('12/05/2034', 'dd/mm/yyyy') end_date, 25 amount, '25 - End of Contract' comments FROM dual),
-- end of creating subqueries to mimic your tables
contract_details AS (SELECT ca.contract_id,
ca.seqnum,
LAG(ca.end_date + 1, 1, c.begin_date) OVER (PARTITION BY ca.contract_id ORDER BY seqnum) begin_date,
ca.end_date,
ca.amount
FROM contact_tbl c
INNER JOIN contract_activity ca ON c.contract_id = ca.contract_id)
SELECT contract_id,
seqnum,
begin_date,
end_date,
amount,
CASE WHEN TRUNC(SYSDATE) BETWEEN begin_date AND end_date THEN 'Active' END status
FROM contract_details;
CONTRACT_ID SEQNUM BEGIN_DATE END_DATE AMOUNT STATUS
----------- ---------- ----------- ----------- ---------- ------
123 1 13/05/2016 12/05/2019 25
123 2 13/05/2019 12/05/2024 25 Active
123 3 13/05/2024 12/05/2029 25
123 4 13/05/2029 12/05/2034 25
Your status column is a simple case expression, but since it refers to the calculated begin_date, rather than repeating the calculation, I did the begin_date calculation in one subquery, and then referenced that in the outer query.
Note how I used TRUNC(sysdate) instead of just sysdate in the case expression for the status - DATEs in Oracle have a time component (which defaults to midnight if you don't specify the time), so if you ran the query any time between 12/05/2024 00:00:01 and 12/05/2024 23:59:59, your query wouldn't return a status of Active for the second row if you used sysdate. You need to explicitly truncate sysdate to midnight in order for the comparison to work.
Hope below snippet serve the purpose.
DECLARE
CURSOR cur_contracts IS
SELECT
*
FROM
CONTACT_TBL
ORDER BY
CONTRACT_ID;
CURSOR cur_contracts_act(contact_id NUMBER) IS
SELECT
*
FROM
CONTRACT_ACTIVTY
ORDER BY
CONTRACT_ID,
SEQNUM;
l_ca_begin_date DATE;
l_ca_status VARCHAR2(10);
l_prev_rec_end_date DATE;
BEGIN
FOR i IN cur_contracts
LOOP
contract_activity_seq := 0;
FOR j IN cur_contracts_act (i.CONTRACT_ID)
LOOP
contract_activity_seq := contract_activity_seq + 1;
IF (contract_activity_seq = 1) THEN
l_ca_begin_date := i.begin_date;
ELSE
l_ca_begin_date := l_prev_rec_end_date + 1;
END IF;
IF SYSDATE BETWEEN l_ca_begin_date AND j.end_date THEN
l_ca_status := 'Active';
ELSE
l_ca_status := NULL;
END IF;
l_prev_rec_end_date := j.end_date;
UPDATE CONTRACT_ACTIVTY
SET BEGIN_DATE = l_ca_begin_date,
status = l_ca_status
WHERE CURRENT OF cur_contracts_act;
END LOOP;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN raise_application_error(
-20001,
'An error was encountered - ' || SQLCODE || ' -ERROR- ' || SQLERRM
);
END;
/

Amount of time for giving the date

I'm trying to get a report in MySQL for consecutive days based on activity recorded. I have the start date&time and end date&time of a given status. My goal is to receive a report in a form:
Status|Date|Sum of activity
The problem that I've encountered is that some activities start i.e. 2019-12-12 18:21:12 and ends the next day 2019-12-13 03:21:12. Is there a way to let's say split the result for one date until 23:59:59 and add the rest of time to the following day? So far I have a code below, but it just sums the timestampdiff.
USE db;
SELECT
table1.status,
left(table1.start_time, 7) ' Date',
sec_to_time(
sum(
timestampdiff(
second,
table1.start_time,
(
case when table1.end_time is null then now() else table1.end_time end
)
)
)
) 'Sum of activity'
FROM
table1
GROUP by 1,2
Update : Let me clarify a bit my question. I have some activities that take for example 36 hours, starting on 2019-12-20 and ending on 2019-12-22. I need a composed monthly report with each day in the month selected from start_time, so for the example described above (36h over 3 days) I would like to get:
Activity1|2019-12-20|3h
Activity1|2019-12-21|24h
Activity1|2019-12-22|9h
Update2: Thank you for the 2nd update,but the proposed code works only for the first record in the dataset (for more records the time is not summed up) and doesn't take into account the activity type. I will provide more data maybe it will help:
Activity start_time end_time
1048 2019-12-27 06:42:51 2019-12-27 07:11:42
1048 2019-12-29 07:07:11 2019-12-29 07:08:59
1048 2019-12-29 07:09:19 2019-12-29 07:21:10
2066 2019-12-25 07:08:00 2019-12-25 19:01:17
2066 2019-12-25 19:01:17 2019-12-26 06:55:15
2066 2019-12-26 06:55:15 2019-12-26 18:20:51
You can use date() function :
select status, date(start_time) as date, count(*) as "Sum of activities"
from table1
group by status, date(start_time);
Demo
Update (depending on your comment): Try to use
select status, date(start_time) as date,
sec_to_time(sum(timestampdiff(second,
start_time,
(case
when end_time is null then
now()
else
end_time
end))))
as "Sum of activities"
from table1
group by status, date(start_time);
Update2 : To accomplish the last mentioned duty, need to generate rows firstly :
select date1,
extract( hour from
sec_to_time(
sum(case
when date1 = date(start_time) then
timestampdiff(second,start_time,date2)
when date1 = date(end_time) then
timestampdiff(second,date1,end_time)
else
timestampdiff(second,date1,date2)
end
)) ) as "Time Difference as hour"
from
(
select #cr := #cr + 1 as rn,
date_sub(date(end_time), interval date(end_time)-date(start_time) - #cr + 1 day) as date1,
date_sub(date(end_time), interval date(end_time)-date(start_time) - #cr day) as date2,
start_time, end_time
from information_schema.tables c1
cross join ( select #cr := 0 ) r
cross join table1 t
where #cr < date(end_time)- date(start_time)+1
) q
group by date1;
Demo 2
removing extract( hour from ) part you can get the whole difference upto second precision.

SQL query to extract the most recent part and anything that is 30 days older than that date.

I am very new to SQL and I need to write a query that selects data for a specific part. However, It should select only the part that is the most recent(given by date) and anything that is only 30 days prior to it. Please consider the table below:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
2 BBB 6/15/2015
3 AAA 6/11/2015
4 AAA 1/1/2008
I need a query that gives me:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
3 AAA 6/11/2015
I have tried:
select * from ( select * from sales_table where Part_NAME = 'AAA') where DATE BETWEEN (max(DATE) and (max(DATE)-30))
I have read some articles saying that I cannot use WHERE and functions like max() together and advised me to use group by or having but it didn't work for me as well. Thank you.
IF you want data from the last 30 days of the current day, you can do :
SELECT *
FROM sales_table
WHERE
[DATE] >= DATEADD(DAY, -30,GETDATE())
AND [DATE] <= GETDATE()
AND Part_NAME = 'AAA'
IF you want data from the last 30 days from the last date of sale of each Part_NAME (this will take the max recorded date of sale for each Part_NAME and get the last 30 days records of each one of them.)
SELECT *
FROM (
SELECT *,
MAX([DATE]) OVER(PARTITION BY Part_NAME ORDER BY PartID) AS RecentDate
FROM sales_table
) D
WHERE
[DATE] >= DATEADD(DAY, -30, RecentDate)
AND [DATE] <= RecentDate
AND Part_NAME = 'AAA'
You can accomplish by using datediff and getdate() and a subquery.
SELECT * FROM (
SELECT *,DATEDIFF(DD,[DATE],GETDATE()) AS DAYSBETWEEN FROM sales_table
) AS X
WHERE DAYSBETWEEN <= 30
If you want data from the last 30 days, it would be:
select st.*
from sales_table st join
(select top (1) st2.*
from sales_table st2
order by st2.date desc
) st2
on st2.part_name = st.part_name and
st.date >= dateadd(day, -30, cast(getdate() as date));

MySQL Select maximum concurrent calls in a time period

I have a table storing all the calls that pass through the system.
The columns I am interested in are just calldate and callend which are the start and end times of a call.
I want to select how many concurrent calls were in progress at the same time.
I have a query which returns all the over lapping calls but this doesn't quite give me what I want.
SELECT SEC_TO_TIME(FLOOR((TIME_TO_SEC(t.calldate)+300)/600)*600) AS `TIME`,
MAX(t.overlaps)
FROM(
SELECT x.id,
x.calldate,
COUNT(y.id) overlaps
FROM cdr x
LEFT
JOIN cdr y
ON y.id <> x.id
AND y.calldate < x.callend
AND y.callend > x.calldate
AND y.calldate BETWEEN '2015-05-01 00:00:00' AND '2015-05-01 23:59:59'
WHERE
x.calldate BETWEEN '2015-05-01 00:00:00' AND '2015-05-01 23:59:59'
GROUP BY
x.id
) t
GROUP BY `TIME`;
This is returning me some of the right things but because its returning all the overlapping calls it may actually overlap with calls that finished before that time so some of the numbers are too high.
Call 08:00 08:05 08:10 08:15 08:20 08:25
1 |-----------------|
2 |-----------------|
3 |--------------|
4 |--------------------------------------|
5 |--------------|
6 |------------------------------------|
1 2 3 4 5 4 3 4 3 2 1 0
So for example that would return 5 because its looking at the call id rather than the dates, I'm just not sure how to change it get it to use the dates properly.
I'm probably missing something simple but it's been a long day.
EDIT
Using edTarik's answer this works:
SELECT y.`Interval`,
#lines := CAST((#lines + y.t) AS INT) as TotayQuantity
FROM
(SELECT #lines := 0) init,
(
SELECT
SEC_TO_TIME(FLOOR((TIME_TO_SEC(x.cd)+300)/600)*600) as `Interval`,
SUM(x.t) as t
FROM
(
SELECT
calldate as cd,
1 as t
FROM cdr c
WHERE
calldate BETWEEN '2015-05-04 00:00:00' AND '2015-05-04 23:59:59'
UNION ALL
SELECT
callend as cd,
-1 as t
FROM cdr c
WHERE
callend BETWEEN '2015-05-04 00:00:00' AND '2015-05-04 23:59:59'
ORDER BY cd
) x
GROUP BY `Interval`
) y
You need to do the following:
Select calldate, 1 From cdr
Union
Select callend, -1 from cdr
Order by calldate
You then select the cumulative sum and then find the maximum. See How to get running sum of a column in sql server

Find max of continuous streak and the current streak from datetime

I have the following data of a particular user -
Table temp -
time_stamp
2015-07-19 10:52:00
2015-07-18 10:49:00
2015-07-12 10:43:00
2015-06-08 12:32:00
2015-06-07 11:33:00
2015-06-06 10:05:00
2015-06-05 04:17:00
2015-04-14 04:11:00
2014-04-02 23:19:00
So the output for the query should be -
Maximum streak = 4, Current streak = 2
Max streak = 4 because of these -
2015-06-08 12:32:00
2015-06-07 11:33:00
2015-06-06 10:05:00
2015-06-05 04:17:00
And current streak is 2 because of these (Assuming today's date is 2015-07-19)-
2015-07-19 10:52:00
2015-07-18 10:49:00
EDIT: I want a simple SQL query for MYSQL
For MAX streak(streak) you can use this, I have use the same query to calculate max streak. This may help you
SELECT *
FROM (
SELECT t.*, IF(#prev + INTERVAL 1 DAY = t.d, #c := #c + 1, #c := 1) AS streak, #prev := t.d
FROM (
SELECT date AS d, COUNT(*) AS n
FROM table_name
group by date
) AS t
INNER JOIN (SELECT #prev := NULL, #c := 1) AS vars
) AS t
ORDER BY streak DESC LIMIT 1;
A general approach with the gaps and islands queries is to tag each row with its rank in the data and with its rank in the full list of dates. The clusters will all have the same difference.
Caveats: I don't know if this query will be efficient. I don't remember if MySQL allows for scalar subqueries. I didn't look up the way to calculate a day interval in MySQL.
select user_id, max(time_stamp), count(*)
from (
select
t.user_id, t.time_stamp,
(
select count(*)
from T as t2
where t2.user_id = t.user_id and t2.time_stamp <= t.time_stamp
) as rnk,
number of days from t.time_stamp to current_date as days
from T as t
) as data
group by usr_id, days - rnk