I'm trying to perform a number of MySQL queries and to write the results as a single line to a different database and table.
So this works
INSERT INTO bridgedb.stats (longestcall, totalmins, totalconfs)
SELECT
(SELECT MAX(duration) AS longestcall FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT SUM(duration) AS totalmins FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT COUNT(*) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY);
and this works
INSERT INTO bridgedb.stats (date, peakchan)
SELECT
`calldate`,
MAX(concurrent)+1 AS peakcount
FROM (
SELECT
DATE(a.calldate) as calldate,
COUNT(b.uniqueid) AS concurrent
FROM asteriskcdr.cdr AS a, asteriskcdr.cdr AS b
WHERE
a.calldate >= NOW() - INTERVAL 4 DAY
AND (
(a.calldate<=b.calldate AND (UNIX_TIMESTAMP(a.calldate)+a.duration)>=UNIX_TIMESTAMP(b.calldate))
OR (b.calldate<=a.calldate AND (UNIX_TIMESTAMP(b.calldate)+b.duration)>=UNIX_TIMESTAMP(a.calldate))
)
AND a.uniqueid>b.uniqueid
GROUP BY a.uniqueid
) AS baseview
GROUP BY calldate;
But this doesn't work
INSERT INTO bridgedb.stats (date, peakchan, longestcall, totalmins, totalconfs)
SELECT
(SELECT
`calldate`,
MAX(concurrent)+1 AS peakcount
FROM (
SELECT
DATE(a.calldate) as calldate,
COUNT(b.uniqueid) AS concurrent
FROM asteriskcdr.cdr AS a, asteriskcdr.cdr AS b
WHERE
a.calldate >= NOW() - INTERVAL 1 DAY
AND (
(a.calldate<=b.calldate AND (UNIX_TIMESTAMP(a.calldate)+a.duration)>=UNIX_TIMESTAMP(b.calldate))
OR (b.calldate<=a.calldate AND (UNIX_TIMESTAMP(b.calldate)+b.duration)>=UNIX_TIMESTAMP(a.calldate))
)
AND a.uniqueid>b.uniqueid
GROUP BY a.uniqueid
) AS baseview
GROUP BY calldate),
(SELECT MAX(duration) AS longestcall FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT SUM(duration) AS totalmins FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT COUNT(*) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY);
I get an error #1241 - Operand should contain 1 column(s)
Does anyone know how I can make this work for all the queries?
Try using session variables or abstract it away into a stored procedure, something like -
First:
SELECT
#calldate:=`calldate`,
#peakchan:=MAX(concurrent)+1
FROM (
...
) AS baseview
GROUP BY calldate
Second:
SELECT #longestcall:=MAX(duration) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY
Third:
SELECT #totalmins:=SUM(duration) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY
Fourth:
SELECT #totalconfs:=COUNT(*) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY;
And finally
INSERT INTO bridgedb.stats (date, peakchan, longestcall, totalmins, totalconfs)
VALUES (#calldate, #peakchan, #longestcall, #totalmins, #totalconfs)
BTW: It is not a good idea to call a column date.
EDIT
Ofcourse you need to make sure, those queries run in the same session to use session variables. This is the default when run with a single DB connection in PHP
EDIT 2
To handle periods without activity, you can
either prefix this with SELECT #calldate:=DATE(NOW() - INTERVAL 1 DAY), #peakchan:=0, #longestcall:=0, #totalmins:=0, #totalconfs:=0; (record with defaults created)
or stop after the first query, if #calldate is NULL (no record created)
EDIT 3
This works for me:
DELIMITER //
CREATE PROCEDURE ReportYesterday()
BEGIN
DECLARE calldate DATE;
DECLARE peakchan, longestcall, totalmins, totalconfs INT;
SELECT
#calldate:=`calldate`,
#peakchan:=MAX(concurrent)+1
FROM (
SELECT
DATE(a.calldate) as calldate,
COUNT(b.uniqueid) AS concurrent
FROM asteriskcdr.cdr AS a, asteriskcdr.cdr AS b
WHERE
a.calldate >= NOW() - INTERVAL 1 DAY
AND (
(a.calldate<=b.calldate AND (UNIX_TIMESTAMP(a.calldate)+a.duration)>=UNIX_TIMESTAMP(b.calldate))
OR (b.calldate<=a.calldate AND (UNIX_TIMESTAMP(b.calldate)+b.duration)>=UNIX_TIMESTAMP(a.calldate))
)
AND a.uniqueid>b.uniqueid
GROUP BY a.uniqueid) AS baseview
GROUP BY calldate
;
-- EDIT 4 IS THE FOLLOWING LINE
IF #calldate IS NOT NULL THEN
SELECT #longestcall:=MAX(duration) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY
;
SELECT #totalmins:=SUM(duration) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY
;
SELECT #totalconfs:=COUNT(*) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY
;
INSERT INTO bridgedb.stats (date, peakchan, longestcall, totalmins, totalconfs)
VALUES (#calldate, #peakchan, #longestcall, #totalmins, #totalconfs)
;
END IF;
END
;
//
DELIMITER ;
followed by
CALL ReportYesterday();
You must build a (valid) SELECT query which returns all rows. Your former SELECT was not valid. This one should work:
INSERT INTO bridgedb.stats (date, peakchan, longestcall, totalmins, totalconfs)
SELECT
`calldate`,
MAX(concurrent)+1 AS peakcount,
(SELECT MAX(duration) AS longestcall FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT SUM(duration) AS totalmins FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY),
(SELECT COUNT(*) FROM bridgedb.log WHERE `start` >= NOW() - INTERVAL 1 DAY)
FROM (
SELECT
DATE(a.calldate) as calldate,
COUNT(b.uniqueid) AS concurrent
FROM asteriskcdr.cdr AS a, asteriskcdr.cdr AS b
WHERE
a.calldate >= NOW() - INTERVAL 1 DAY
AND (
(a.calldate<=b.calldate AND (UNIX_TIMESTAMP(a.calldate)+a.duration)>=UNIX_TIMESTAMP(b.calldate))
OR (b.calldate<=a.calldate AND (UNIX_TIMESTAMP(b.calldate)+b.duration)>=UNIX_TIMESTAMP(a.calldate))
)
AND a.uniqueid>b.uniqueid
GROUP BY a.uniqueid
) AS baseview
GROUP BY calldate;
Related
I am using MySQL 5.7 and I need to do queries from a table like
order_id fee created_time
111 10 2020-11-16
222 90 2020-11-01
333 300 2000-10-22
The results should be the total income of last 1 day(yesterday) and last 30 days, like
date_range revenue
1 10
30 400
The column date_range is the last X day before now and I can do this use 'union all':
SELECT 1 AS date_range, SUM(fee) FROM test
WHERE created_time >= SUBDATE(CURRENT_DATE, 1) AND created_time < CURRENT_DATE
UNION ALL
SELECT 30 AS date_range, SUM(fee) FROM test
WHERE created_time >= SUBDATE(CURRENT_DATE, 30) AND created_time < CURRENT_DATE
The queries are quite similar and is it possible to combine them into ONE query instead of using union all?
CREATE TABLE:
CREATE TABLE test (
order_id INT,
fee INT,
created_time DATETIME
)
INSERT VALUES:
INSERT INTO test VALUES (111,10,'2020-11-16'),(222,90,'2020-11-01'),(333,300,'2020-10-22')
SELECT date_range, SUM(fee)
FROM test
CROSS JOIN (SELECT 1 date_range UNION ALL SELECT 30) date_ranges
WHERE created_time >= CURRENT_DATE - INTERVAL date_range DAY
AND created_time < CURRENT_DATE
GROUP BY date_range
UPDATE
You may improve the performance additionally while creating date-generated subquery with interval borders, not interval lengths:
SELECT DATEDIFF(CURRENT_DATE, date_range) date_range, SUM(fee)
FROM test
CROSS JOIN (SELECT CURRENT_DATE - INTERVAL 1 DAY date_range
UNION ALL
SELECT CURRENT_DATE - INTERVAL 30 DAY) date_ranges
WHERE created_time >= date_range
AND created_time < CURRENT_DATE
GROUP BY date_range
DEMO
You can try using case when:
https://dev.mysql.com/doc/refman/5.7/en/case.html
select date_range, sum(fee)
from (
select
case
when created_time between subdate(current_date, 1) and current_date then 1
when created_time between subdate(current_date, 30) and current_date then 30
end case date_range,
fee
from test) t
where date_range is not null
group by date_range
Is it possible to make the year and month part of the date dynamic (based on current year and month) instead of hard coded?
SELECT SUM(`amount`)
FROM employees
WHERE (`date` between "2018-08-26" and "2018-09-26") AND `status` = 'Pending';
In the above example, if the current month is 9 then the query should be 08-26 and 09-26, if the current month is 10 then 09-26 and 10-26 and so on.
If you want to query data for last month, it's enough to use such query:
SELECT * FROM my_table
WHERE some_datetime_column > DATE_ADD(NOW(), INTERVAL -1 MONTH);
Also, if you want to get current month/year, try this:
SELECT MONTH(NOW()), YEAR(NOW())
Demo
UPDATE
Try this:
--here you specify the day of the month you want
SELECT #days := 26 - DAY(NOW());
SELECT #startDate := DATE_ADD(CAST(NOW() AS DATE), INTERVAL #days DAY),
#endDate := DATE_ADD(DATE_ADD(CAST(NOW() AS DATE), INTERVAL #days DAY), INTERVAL -1 MONTH)
Another demo
Then you can use it like:
SELECT * FROM t
WHERE date_column BETWEEN #startDate AND #endDate
Here is how you can build the desired dates:
SELECT
CURRENT_DATE,
STR_TO_DATE(CONCAT_WS('-', YEAR(CURRENT_DATE), MONTH(CURRENT_DATE), 26), '%Y-%m-%d'),
STR_TO_DATE(CONCAT_WS('-', YEAR(CURRENT_DATE), MONTH(CURRENT_DATE), 26), '%Y-%m-%d') - INTERVAL 1 MONTH
-- 2018-09-25 | 2018-09-26 | 2018-08-26
And use the above in your query:
SELECT SUM(`amount`)
FROM employees
WHERE `date` BETWEEN
STR_TO_DATE(CONCAT_WS('-', YEAR(CURRENT_DATE), MONTH(CURRENT_DATE), 26), '%Y-%m-%d') - INTERVAL 1 MONTH AND
STR_TO_DATE(CONCAT_WS('-', YEAR(CURRENT_DATE), MONTH(CURRENT_DATE), 26), '%Y-%m-%d')
AND `status` = 'Pending';
Try below with now() and last month (DATE_SUB(NOW(), INTERVAL 1 MONTH)):
SELECT SUM(`amount`) FROM employees
WHERE (`date` between DATE_SUB(NOW(), INTERVAL 1 MONTH) and now())
AND `status` = 'Pending';
To Get Current Month use
MONTH(NOW())
To Get Current Year use
YEAR(NOW())
But you don't need these
Use this-
SELECT SUM(`amount`) FROM employees
WHERE (`date` between DATE_ADD(CURDATE(), INTERVAL -1 MONTH) and CURDATE())
AND `status` = 'Pending';
Use CURDATE() to get the current date.
To get same date with previous month use:
DATE_ADD(CURDATE(), INTERVAL -1 MONTH)
As of now on 25/9/2018
SELECT CURDATE()
Output:
2018-09-25
For previous month
SELECT DATE_ADD(CURDATE(), INTERVAL -1 MONTH)
Output:
2018-08-25
Instead of raw query you can use procedure, where it will dynamically create date based on your provided date.
DELIMITER $$
-- Call Get_Sum (15) -- Dynamically you pass the date
DROP PROCEDURE IF EXISTS Get_Sum$$
DELIMITER ;
DELIMITER $$
--
-- Create procedure "Get_Sum"
CREATE DEFINER = 'root'#'localhost'
PROCEDURE Get_Sum(IN Day SMALLINT)
BEGIN
Declare current_month datetime;
Declare previous_month datetime;
Select STR_TO_DATE(CONCAT(year(now()),'-',month(NOW()),'-',Day), '%Y-%m-%d') INTO current_month;
Select DATE_SUB(current_month, INTERVAL 1 MONTH) into previous_month;
SELECT SUM(amount) FROM employees
WHERE (date between previous_month and current_month ) AND status = 'Pending';
END
$$
DELIMITER ;
i use this script
SELECT ti_556, TIME_FORMAT(`waktu_be5b`, '%H:%i')
FROM be5b
WHERE waktu_be5b >= DATE_FORMAT(NOW(),'%Y-%m-%d 23:00:00') + INTERVAL -1 DAY
AND waktu_be5b < DATE_FORMAT(NOW(),'%Y-%m-%d 01:00:00')
limit 1
how to return null if no data match with this condition?
i try using COALESCE
SELECT coalesce(ti_556,null), TIME_FORMAT(`waktu_be5b`, '%H:%i')
FROM be5b WHERE waktu_be5b >= DATE_FORMAT(NOW(),'%Y-%m-%d 23:00:00') + INTERVAL -1 DAY
AND waktu_be5b < DATE_FORMAT(NOW(),'%Y-%m-%d 01:00:00')
limit 1
and the output
MySQL returned an empty result set (i.e. zero rows). ( Query took 0.0006 sec )
I'm not really up on mysql syntax so this may not work, but it should be close:
CREATE PROCEDURE myProc()
BEGIN
SELECT ti_556, TIME_FORMAT(`waktu_be5b`, '%H:%i')
INTO temp_table
FROM be5b
WHERE waktu_be5b >= DATE_FORMAT(NOW(),'%Y-%m-%d 23:00:00') + INTERVAL -1 DAY
AND waktu_be5b < DATE_FORMAT(NOW(),'%Y-%m-%d 01:00:00') LIMIT 1;
SELECT FOUND_ROWS() INTO num_rows;
IF(num_rows = 0) THEN
INSERT INTO temp_table VALUES (NULL,NULL);
END IF;
SELECT * FROM temp_table;
END
You'll probably want to pass in your own args as well.
You need a union that is mutual exclusive:
SELECT ti_556, TIME_FORMAT(`waktu_be5b`, '%H:%i')
FROM be5b
WHERE waktu_be5b >= DATE_FORMAT(NOW(),'%Y-%m-%d 23:00:00') + INTERVAL -1 DAY
AND waktu_be5b < DATE_FORMAT(NOW(),'%Y-%m-%d 01:00:00') limit 1
UNION
SELECT NULL, NULL
FROM (SELECT 1)
WHERE NOT EXISTS (
SELECT *
FROM be5b
WHERE waktu_be5b >= DATE_FORMAT(NOW(),'%Y-%m-%d 23:00:00') + INTERVAL -1 DAY
AND waktu_be5b < DATE_FORMAT(NOW(),'%Y-%m-%d 01:00:00')
)
Only one side of the union can return data
.
i have this kind of query:
SELECT COUNT( * )
FROM `reportinc`
WHERE `Data/ora apertura` >= '21/01/13 00:00:00'
AND `Data/ora apertura` <= '21/01/13 18:00:00'
I have to repeat this query for 30 days past from today.
How automate it?
Instead of daytime: 21/01/13 i have to insert something like TODAY -1, TODAY -2 etc etc but my own specified timestamp.
How to?
SELECT TO_DAYS(NOW()) - TO_DAYS(your_table_column_name) <= 30;
Or use DATEDIFF
mysql> SELECT DATEDIFF('1997-12-31 23:59:59','1997-12-30');
-> 1
You could use BETWEEN
SELECT COUNT( * )
FROM `reportinc`
WHERE `Data/ora apertura`
BETWEEN
CURRENT_DATE - INTERVAL 2 DAY
AND
CURRENT_DATE - INTERVAL 1 DAY + '18:00:00'
Something like this:
WHERE `Data/ora apertura` >= CURRENT_DATE - INTERVAL 2 DAY
AND `Data/ora apertura` <= CURRENT_DATE - INTERVAL 1 DAY
+ INTERVAL '18:00:00' HOUR_SECOND
Suppose I have a table that contain information on streaming media connections. In this table, I have a start time and end time for when the connection was initiated and then later closed.
Table: logs
id (INT, PK, AUTO_INCREMENT)
StartTime (DATETIME)
EndTime (DATETIME)
I want to be able to run a query that will add up the total time connections were established for a day. This is obvious for connections within a day:
SELECT
SUM(
TIME_TO_SEC(
TIMEDIFF(`EndTime`, `StartTime`)
)
)
WHERE (`StartTime` BETWEEN '2010-01-01' AND '2010-01-02);
However, suppose a StartTime begins one day, say around 11:00PM, and EndTime is some time the next day, maybe 3:00AM. In these situations, I want to allocate only the amount of time that occurred during the day, to that day. So, 1 hour would go towards the first day, and 3 hours would go to the next.
SUM(
TIME_TO_SEC(
TIMEDIFF(
IF(`EndTime`>DATE_ADD('2010-01-01', INTERVAL 1 DAY), DATE_ADD('2010-01-01', INTERVAL 1 DAY), `EndTime`),
IF(`StartTime`<'2010-01-01', '2010-01-01', `StartTime`)
)
)/60/60
)
The thinking with this is that if the EndTime is more than the end of the day, then we'll just use the end of the day instead. If the StartTime is less than the beginning of the day, then we'll just use the beginning of the day instead.
So, I then need to wrap this all up into something that will generate a table that looks like this:
date, total
2010-01-01, 0
2010-01-02, 1.53
2010-01-03, 5.33
I thought this query would work:
SELECT
`date`,
SUM(
TIME_TO_SEC(
TIMEDIFF(
IF(`EndTime`>DATE_ADD(`date`, INTERVAL 1 DAY), DATE_ADD(`date`, INTERVAL 1 DAY), `EndTime`),
IF(`StartTime`<`date`, `date`, `StartTime`)
)
)/60/60
) AS `total_hours`
FROM
(SELECT * FROM `logs` WHERE `StartTime` BETWEEN '2010-08-01' AND '2010-08-31') AS logs_small,
(SELECT DATE_ADD("2010-08-01", INTERVAL `number` DAY) AS `date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS `dates`
GROUP BY `date`;
Note the numbers table referenced is a table with just one column, number, with a series of integers, 0, 1, 2, 3, etc. I am using it here to generate a series of dates, which works fine.
The problem with this query is that I get inaccurate data. Specifically, rows in the logs table that have an EndDate that goes into the next day don't get any time counted in that next day. For example, if I had a row that started 2010-08-01 23:00:00 and ended 2010-08-02 01:00:00, then the resulting row for 2010-08-02 would add up to 0.
Is there a better way to do this? Ideally, I'd like to get 0 instead of null on days that don't have any records that match up to them as well.
Edit: To clarify, I want to turn this:
id, StartTime, EndTime
0, 2000-01-01 01:00:00, 2000-01-01 04:00:00
1, 2000-01-01 23:00:00, 2000-01-02 05:00:00
2, 2000-01-02 00:00:00, 2000-01-04 01:00:00
... into this:
date, total_hours
2000-01-01, 4
2000-01-02, 29
2000-01-03, 24
2000-01-04, 1
2000-01-05, 0
Solution
Thanks to jim31415 for coming up with the solution! I translated his answer over to the functions usable in MySQL and came up with this:
SELECT `d`.`Date`,
SUM(COALESCE(
(CASE WHEN t.StartTime >= d.Date AND t.EndTime < DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(t.EndTime, t.StartTime))
WHEN t.StartTime < d.Date AND t.EndTime <= DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(t.EndTime,d.Date))
WHEN t.StartTime >= d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(DATE_ADD(d.Date, INTERVAL 1 DAY),t.StartTime))
WHEN t.StartTime < d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY) THEN 24*60*60
END), 0)
)/60/60 ConnectionTime
FROM (SELECT DATE_ADD('2011-03-01', INTERVAL `number` DAY) AS `Date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS d
LEFT JOIN `logs` t ON (t.StartTime >= d.Date AND t.StartTime < DATE_ADD(d.Date, INTERVAL 1 DAY))
OR (t.EndTime >= d.Date AND t.EndTime < DATE_ADD(d.Date, INTERVAL 1 DAY))
OR (t.StartTime < d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY))
GROUP BY d.Date
ORDER BY d.Date;
I should also note that the null values for EndTime weren't applicable in my situation, as I am reading from old log files in my application. If you need them though, Jim's post has them outlined quite well.
This is in MS SQL, but I think the logic applies and can be translated into MySQL.
I wasn't sure how you wanted to handle EndTime that are null, so I commented that out.
select d.Date,
sum(coalesce(
(case when t.StartTime >= d.Date and t.EndTime < dateadd(day,1,d.Date) then datediff(minute,t.StartTime,t.EndTime)
when t.StartTime < d.Date and t.EndTime <= dateadd(day,1,d.Date) then datediff(minute,d.Date,t.EndTime)
when t.StartTime >= d.Date and t.EndTime > dateadd(day,1,d.Date) then datediff(minute,t.StartTime,dateadd(day,1,d.Date))
when t.StartTime < d.Date and t.EndTime > dateadd(day,1,d.Date) then 24*60
--when t.StartTime >= d.Date and t.EndTime is null then datediff(minute,t.StartTime,getdate())
--when t.StartTime < d.Date and t.EndTime is null then datediff(minute,d.Date,getdate())
end), 0)
) ConnectionTime
from (select Date=dateadd(day, num, '2011-03-01') from #NUMBERS where num between 0 and 30) d
left join Logs t on (t.StartTime >= d.Date and t.StartTime < dateadd(day,1,d.Date))
or (t.EndTime >= d.Date and t.EndTime < dateadd(day,1,d.Date))
or (t.StartTime < d.Date and t.EndTime > dateadd(day,1,d.Date))
group by d.Date
order by d.Date
Use a union to make it easier for yourself
SELECT
`date`,
SUM(
TIME_TO_SEC(TIMEDIFF(`EndTime`,`StartTime`))/60/60
) AS `total_hours`
FROM
(SELECT id, starttime, if (endtime > date then date else endtime) FROM `logs` WHERE `StartTime` >= date AND `StartTime` < date
union all
SELECT id, date, endtime FROM `logs` WHERE `enddate` >= date AND `enddate` < date and !(`StartTime` >= date AND `StartTime` < date)
union all
SELECT id, date, date_add(date, 1) FROM `logs` WHERE `enddate` > date AND `startdate` < date
) as datedetails inner join
(SELECT DATE_ADD("2010-08-01", INTERVAL `number` DAY) AS `date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS `dates`
GROUP BY `date`;
Hope, I understood your question correctly
Edit: Forgot case when there is a multiday request that starts before the day asked for, and ended after
Use this
select startTime,duration as duration,time,TIME_TO_SEC(TIMEDIFF(time,startTime)) as diff from <idling> limit 25;
select startTime,duration DIV 60 as duration,time,TIMESTAMPDIFF(MINUTE,startTime,time) as diff from <idling> limit 25;