I´m looking for some help with the following problem.
I need to check, whether all selected entries share a common time interval and if so, what is the time interval.
To visualize the problem:
id openingTime closingTime
1 09:00 18:00
2 11:00 15:00
3 12:00 20:00
4 21:00 23:00
Desired output is to get either an empty result or one result with the overlapping interval.
Examples:
selected id openingTime closingTime
1,2 => 11:00 15:00
1,2,3 => 12:00 15:00
1,3 => 12:00 18:00
1,2,3,4 => empty empty
Having IDs with overlapping intervals, the SQL command is easy:
SELECT MAX(openingTime), MIN(closingTime) FROM table WHERE id IN (ids)
But this SQL query doesn't deal with the cases when one or more entries are not sharing the same interval.
Here is some sample data and DB fiddle to try it out:
CREATE TABLE `mytable` (
`id` int(11) NOT NULL,
`openingtime` time NOT NULL,
`closingtime` time NOT NULL
);
INSERT INTO `mytable` (`id`, `openingtime`, `closingtime`) VALUES
(1, '09:00:00', '18:00:00'),
(2, '11:00:00', '15:00:00'),
(3, '12:00:00', '20:00:00'),
(4, '21:00:00', '23:00:00');
Thank you for your help.
D.
I am thinking exists and aggregation:
select min(openingtime), max(closingtime)
from (
select t.*,
exists (
select 1
from mytable t1
where t1.openingtime > t.closingtime or t1.closingtime < t.openingtime
) flag
from mytable t
) t
having max(flag) = 0
The subquery checks if any other row in the table does not overlap with the current row. Then the outer query aggregates, and uses having to filter out the whole result if any row was flagged.
A common interval is going to start at an opening time and end at the next closing time. So, you can test each starting time, counting the number of overlaps.
select o.time, min(t.closingtime)
from (select distinct time from t) o join
t
on o.time >= t.openingtime and o./time <= t.closingtime
group by o.openingtime
having count(*) = (select count(*) from t);
This return no rows if there are no overlaps. It returns all overlapping periods if there is more than one (which I don't think is possible with one row per id).
Related
I have one table called task
id t_title t_started_on t_due_on
1 Test 1 2018-01-18 01:00 PM 2018-01-20 01:00 PM
2 Test 2 2018-01-25 01:00 PM 2018-01-27 01:00 PM
from here i have to select dates like first row start date is 2018-01-18 01:00 PM (t_started_on) and end date is 2018-01-20 01:00 PM(t_due_on).total is 3 days
2018-01-18
2018-01-19
2018-01-20
same as second row also 3 days
2018-01-25
2018-01-26
2018-01-27
Expected Result
Array
(
[allocatedDate] => 2018-01-18
[allocatedDate] => 2018-01-19
[allocatedDate] => 2018-01-20
[allocatedDate] => 2018-01-25
[allocatedDate] => 2018-01-26
[allocatedDate] => 2018-01-27
)
How to write a select query in above case?
Like I said in your previous question, you should not store your dates as strings, but in date datatype: that will make your data less error prone and your queries simpler. Now you'll have to convert those strings to dates each time you need to do date/time calculations with them.
To generate the dates inside periods, you need a helper table, which can be useful also for many other purposes: a table with one column that has natural numbers starting from 0 up to some large n. You could create it like this:
create table nums (i int);
insert into nums values (0), (1), (2), (3);
insert into nums select i+4 from nums;
insert into nums select i+8 from nums;
insert into nums select i+16 from nums;
insert into nums select i+32 from nums;
insert into nums select i+64 from nums;
insert into nums select i+128 from nums;
insert into nums select i+256 from nums;
You can see how you double the number of records by adding a similar insert statement, but this will already generate 512 records, which would be more than enough for your purposes: it should have the highest number of days that a period can have in your tasks table.
Then you can use this query to get the desired output:
SELECT DISTINCT date_add(d_started_on, interval i day)
FROM (
SELECT date(STR_TO_DATE(t_started_on, '%Y-%m-%d')) as d_started_on,
datediff(
date(STR_TO_DATE(t_due_on, '%Y-%m-%d')),
date(STR_TO_DATE(t_started_on, '%Y-%m-%d'))
) as days
FROM tasks
) as base
INNER JOIN nums ON i <= days
ORDER BY 1
See also SQLfiddle
My goal is it to create a SQL-Query that counts all items in a certain time frame (e.g. 5min)
That's my code so far:
SELECT FROM_UNIXTIME(FLOOR(timestamp_stop/5*60)*(5*60), '%h:%i') AS timekey, timestamp_stop, count(item) AS performance
FROM task
WHERE done = 1
GROUP BY timekey
ORDER BY timestamp_stop ASC
That works great, but doesn't include time frames in which there aren't any records in the database.
I would like to also get these 0-count-ones, up to the current time.
Currently I have no simple/elegant solution in my mind. Any ideas?
Some little post processing in php would also be possible.
As Gordon mentioned, you probably want a secondary table as a basis for ALL 5-minute intervals. I have done similar with a query to self-build using MySQL variables.
select
YourTable.WhateverFields
from
( select
#startTime RangeStart,
#startTime := date_add( #startTime, interval 5 MINUTE ) RangeEnd
from
( select #startTime := '2014-10-20' ) sqlvars,
AnyTableThatHasAsManyDaysYouExpectToReport
limit
12 * numberOfHoursYouNeed * numberOfDaysYouNeed ) DynamicTimeRange
LEFT JOIN YourTable
on YourTable.DateTimeField >= DynamicTimeRange.RangeStart
AND YourTable.DateTimeField < DynamicTimeRange.RangeEnd
So, in this example, the innermost declars a variable "startTime" to Oct 20, 2014 which defaults to 12:00:00. Then the one out from that creates a result set of two columns for a RangeStart and RangeEnd and might look something like...
RangeStart RangeEnd
2014-10-20 00:00 2014-10-20 00:05
2014-10-20 05:00 2014-10-20 00:10
2014-10-20 10:00 2014-10-20 00:15
2014-10-20 05:00 2014-10-20 00:20
2014-10-20 20:00 2014-10-20 00:25
The table reference "AnyTableThatHasAsManyDaysYouExpectToReport" is just that... any table in your database that has at least as many records as you would need to generate your 5-minute intervals for however many hours and days. If you need 1 day worth = 12 records * 5 minutes = 1 hour * 24 hrs = 24*12 = 288 records needed. If you wanted a week, then so be it... multiply that by 7 so my sample just has place-holders to help clarify the intent...
But with the LEFT JOIN, you get all the intervals...
If there are such time frame, but the where clause filters out the records, you can do:
SELECT FROM_UNIXTIME(FLOOR(timestamp_stop/5*60)*(5*60), '%h:%i') AS timekey,
timestamp_stop,
sum(item is not null and done = 1) AS performance
FROM task
GROUP BY timekey
ORDER BY timestamp_stop ASC;
If you still have gaps, then you need to generate a table (or subquery) containing the list of the time frames that you want and use left join.
EDIT:
A subquery is not pleasant. You have to list all the time values. Something like:
SELECT q.timekey, t.timestamp_stop, coalesce(t.performance, 0) as performance
FROM (SELECT '00:00' as timekey UNION ALL
SELECT '00:05' UNION ALL
. . .
) q LEFT JOIN
(SELECT FROM_UNIXTIME(FLOOR(timestamp_stop/5*60)*(5*60), '%h:%i') AS timekey,
timestamp_stop,
COUNT(item) AS performance
FROM task
WHERE done = 1
GROUP BY timekey
) t
ON t.timekey = q.timekey
ORDER BY timestamp_stop ASC;
I have a problem in postgresql.
I have one cohorte (gathering of people) and i would like counting the persons in this cohorte.
Begin date : "2014-09-01", End date : "2014-11-30".
I have 5 persons between 09/01 and 09/22
I have 5 persons between 09/20 and 09/25
I have 5 persons between 09/26 and 10/05
I have 5 persons between 10/01 ans 11/30
I want to have the max of accommodation for each month between the begin date and the end date in SQL (or PHP). Expected max person count:
September(09) => 10
October(10) => 10
November(11) => 5
Find the maximum of simultaneously present persons on a single day for every month in a given period.
I suggest generate_series() to produce the series of days in your period. Then aggregate twice:
First to get a count for each day. A single day can be dealt with plain BETWEEN. Your ranges are obviously meant to be with include borders.
Second to get the maximum per month.
SELECT date_trunc('month', day)::date AS month, max(ct) AS max_ct
FROM (
SELECT g.day, count(*) AS ct
FROM cohorte
,generate_series('2014-09-01'::date -- first of Sept.
,'2014-11-30'::date -- last of Nov.
,'1 day'::interval) g(day)
WHERE g.day BETWEEN t_begin AND t_end
GROUP BY 1
) sub
GROUP BY 1
ORDER BY 1;
Returns:
month | max_ct
-----------+--------
2014-09-01 | 10
2014-10-01 | 10
2014-11-01 | 5
Use to_char() to prettify the month output.
SQL Fiddle .. is down ATM. Here is my test case (that you should have provided):
CREATE TEMP TABLE cohorte (
cohorte_id serial PRIMARY KEY
,person_id int NOT NULL
,t_begin date NOT NULL -- inclusive
,t_end date NOT NULL -- inclusive
);
INSERT INTO cohorte(person_id, t_begin, t_end)
SELECT g, '2014-09-01'::date, '2014-09-22'::date
FROM generate_series (1,5) g
UNION ALL
SELECT g+5, '2014-09-20', '2014-09-25'
FROM generate_series (1,5) g
UNION ALL
SELECT g+10, '2014-09-26', '2014-10-05'
FROM generate_series (1,5) g
UNION ALL
SELECT g+15, '2014-10-01', '2014-11-30'
FROM generate_series (1,5) g;
For more complex checks I'd suggest the OVERLAPS operator:
Find overlapping date ranges in PostgreSQL
For more complex scenarios I'd also consider range types:
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
can't you use window function?
I'd try something like this (I've not tested this code, just exposed my thoughts)
SELECT max(count) FROM (
SELECT count(*) OVER (PARTITION BY ???) as count
FROM contract
WHERE daterange(dateStart, dateEnd, '[]') && daterange('2014-09-01', '2014-10-01', '[)')
) as max
Here, my problem remains that I can't find a way to partition for each day of the interval. Maybe this is a wrong approach, but I would be interested by a solution based on windows.
edit: with this request, you have the max of simultaneous present, but over all the time, not only a given month
with presence as (
SELECT id, generate_series(begin_date, end_date, '1 day'::interval) AS date
FROM test
),
presents as (
SELECT count(*) OVER (PARTITION BY date) AS count
FROM presence
)
SELECT max(count) from presents;
Here we come, I think
Imagine your person table has 3 columns :
id
entrance_date
leaving_date
the request would look like
WITH presents as (
SELECT id,
daterange(entrance_date, leaving_date, '[]') * daterange('2014-09-01', '2014-11-30', '[]') as range
FROM person
WHERE daterange(entrance_date, leaving_date, '[]') && daterange('2014-09-01', '2014-11-30', '[]')
),
present_per_day as (
SELECT id,
generate_series(lower(range), upper(range), '1 day'::interval) AS date
FROM presents
),
count_per_day as (
SELECT count(*) OVER (PARTITION BY date) AS count,
date
FROM present_per_day
),
SELECT max(count) OVER (PARTITION BY date_part('year', date), date_part('month', date)) as max,
date_part('year', date),
date_part('month', date)
FROM count_per_day;
(I have to leave, I hope I'll have time to test it later)
In fact, #erwin solution is much much more easy and efficient than this one.
I have a MySQL database with data recorded every 15 minutes. For simplicity, lets assume there are 2 fields:
DATETIME Created
Double Value
I would like to draw a chart which needs for each hour the opening, min, max, and closing values for an hour. To do this I need to return results from my MySQL query to my PHP to create a JSON. I would like to do this in the MySQL query so that the response is cached.
Here is an example of the problem, given 9 data points trying to get 2 hour groups:
Creation Value
2014-03-25 12:15:00 413.17011
2014-03-25 12:00:00 414
2014-03-25 11:45:00 415
2014-03-25 11:30:00 415
2014-03-25 11:15:00 415.5
2014-03-25 11:00:00 415.5
2014-03-25 10:45:00 416
2014-03-25 10:30:00 416
2014-03-25 10:15:00 415.99
I would need:
Hour 1 (11:15:00 to 12:15:00)
Open: 415.5
Close: 413.17011
High: 415.5
Low: 413.17011
Hour 2 (10:15:00 to 11:15:00)
Open: 415.99
Close: 415.5
High: 416
Low: 415.5
Of course for the full 24 hours this would need repeating, this is just an example.
Any help is really appreciated!
Here is the current MySQL dump for the example (Using MySQL version 2.6.4-pl3):
--
-- Table structure for table `exampleTable`
--
CREATE TABLE `exampleTable` (
`created` datetime NOT NULL,
`value` double NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
--
-- Dumping data for table `exampleTable`
--
INSERT INTO `exampleTable` VALUES ('2014-03-25 12:15:00', 413.17011);
INSERT INTO `exampleTable` VALUES ('2014-03-25 12:00:00', 414);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:45:00', 415);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:30:00', 415);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:15:00', 415.5);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:00:00', 415.5);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:45:00', 416);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:30:00', 416);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:15:00', 415.99);
Get it to work
You might try
SELECT
DATE(created) AS day,
HOUR(created) AS hour,
(
SELECT Value FROM `table` AS b
WHERE DATE(a.created) = DATE(b.created)
AND HOUR(a.created) = HOUR(b.created)
ORDER BY created ASC LIMIT 1
) AS Open,
(
SELECT Value FROM `table` AS b
WHERE DATE(a.created) = DATE(b.created)
AND HOUR(a.created) = HOUR(b.created)
ORDER BY created DESC LIMIT 1
) AS Close,
MIN(value) AS Low,
MAX(value) AS High
FROM `table` AS a
GROUP BY DATE(created), HOUR(created)
this groups all your rows by DATE+HOUR and computes the MIN respectively MAX as Low or High. To find the first and last row for Open and Close, the easiest in SQL syntax is a subselect. It selects all rows which are relevant for the current row, and sorts them ascending or descending. Then selects the first row.
Please consider that this groups only by hour. Instead of
Hour 1 (11:15:00 to 12:15:00)
Hour 2 (10:15:00 to 11:15:00)
this groups like
Hour 1 (11:00:00 to 11:59:00)
Hour 2 (10:00:00 to 10:59:00)
If you want to keep the 15 minutes offset, you may subtract this from your created timestamp (created - INTERVAL 15 MINUTE) at all occurrences of created in the sql query above.
I created a working sqlfiddle for you.
Performance
Just as hint: If you can, you might want to split date and time into two columns (of types date and time). This way you do not need to cast DATE() on created everytime, but can use the new date column instead. You can then add a combined index to this new columns too, which speeds up your query. See this sqlfiddle for an example.
To get your grouping right, you can use
FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600)
where 3600 sets the interval at 1 hour and the - 900 sets the offset at 00:15
Since you need the MIN() and MAX for each of your four values, you'll need to JOIN the main table to itself but grouped by the min or max (based on the column).
finally, you have each sub-query (joined table) calculate the grouping hour above so you can use that to join them. Here's what I cam up with (with slightly different column names and
SELECT openDate,Open,Close,High,Low
FROM (SELECT FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600)
AS
theHour,
myTable.value AS Open,myTable.dateCreated openDate
FROM myTable
JOIN (SELECT value,MIN(dateCreated) AS dateCreated
FROM myTable
GROUP BY FLOOR(( UNIX_TIMESTAMP(dateCreated) - 900 )
/ 3600)
) AS
aggTable
ON aggTable.dateCreated = myTable.dateCreated) AS
openTable
LEFT JOIN (SELECT FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900
) /
3600) AS
theHour
,
myTable.value AS Close,myTable.dateCreated closeDate
FROM myTable
JOIN (SELECT value,MAX(dateCreated) AS dateCreated
FROM myTable
GROUP BY FLOOR(( UNIX_TIMESTAMP(dateCreated) - 900 ) / 3600)
) AS
aggTable
ON aggTable.dateCreated = myTable.dateCreated) AS closeTable
ON openTable.theHour = closeTable.theHour
LEFT JOIN (SELECT
FLOOR((
UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600) AS
theHour,
MAX(
value)
AS High
FROM myTable
GROUP BY theHour) AS highTable
ON closeTable.theHour = highTable.theHour
LEFT JOIN (SELECT
FLOOR((
UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600) AS
theHour,
MIN(
value)
AS Low
FROM myTable
GROUP BY theHour) AS lowTable
ON highTable.theHour = lowTable.theHour
id title start end
1 Doing Coding for this project. 2013-04-02 02:00:00 2013-04-02 04:00:00
2 Doing Coding for this project. 2013-04-02 04:00:00 2013-04-02 06:00:00
3 Doing Coding for this project. 2013-04-02 06:00:00 2013-04-02 06:30:00
I have above MySQL database table record. Now i want to get the total number of hours.
I am developing TimeSheet Management Application and we need to display total working hours with minutes and second of employee. (i.e 04:30:00 according to data i share)
what i have tried?
SELECT HOUR(TIMEDIFF(end,start)) AS 'totalHour' but works only for each row not on all records.
I have also tried TIMESTAMPDIFF.
Is this possible?
EDIT
From the answer i have received from people i have tried every single of them but everytime i just get 4 or 4.5000 but it should return 06:30:00.
The range of HOUR() function is 0 to 23 so it's not correct to use it for total hours in diff.
For single value you could use TIMESTAMPDIFF() like:
SELECT TIMESTAMPDIFF(HOUR, start, end) AS `totalHour` FROM ...
If you want to calculate it for whole project, you have to sum up all time differences and them print it formatted probably with funciton like TIME_FORMAT() which prints hour larger than 24:
If the time value contains an hour part that is greater than 23, the %H and %k hour format specifiers produce a value larger than the usual range of 0..23. The other hour format specifiers produce the hour value modulo 12.
So you can use:
SELECT TIME_FORMAT( SEC_TO_TIME( SUM( TIME_TO_SEC(end) - TIME_TO_SEC(start))), "%H")
AS `totalHour`
FROM ...
GROUP BY sort_of_project_id
If you need seconds/minutes too (as suggested in comment), use either:
time_to_sec( <left side of select>)/3600 which will return value like 4.84 hours
TIME_FORMAT( ..., "%H:%m:%s") which will display 4:38:24
Try this query
SELECT
id,
title,
TIME_FORMAT(SEC_TO_TIME(sum(TIME_TO_SEC(TIMEDIFF( end, start)))), "%h:%i") AS diff
FROM
tbl1
GROUP BY
title
According to the data that you have given answer should be 4:30. Pl cross check in you records.
FIDDLE
Try like this, it will give you the total no of hours:
For Example:
SELECT sum(time_to_sec(timediff(end, start ))/ 3600) AS 'totalHour' from test;
If you run this query for above table you given, it shows the output 4.5 hours.
Hope it will help you.
I already answered in other thread https://stackoverflow.com/questions/44560345/query-is-not-working/44567322#44567322
I just created a temporary table called dataimport
[Table Format][1]
and wrote a query as,
SELECT `EnNo`, work_dt,
SEC_TO_TIME(sum(TIMESTAMPDIFF(SECOND,login,logout))) as time_worked
from (
SELECT `EnNo`, date(`DateTime`) as work_dt, `DateTime` as login
, coalesce(
(SELECT MIN(`DateTime`)
FROM `dataimport` as b
WHERE a.EnNo = b.EnNo
and date(a.`DateTime`) = date(b.`DateTime`)
and b.`DateTime` >= a.`DateTime`
and b.`INOUT` = 'E'
), now()) AS logout
FROM `dataimport` AS a
WHERE a.`INOUT` = 'S'
) as t
GROUP BY `EnNo`, work_dt
Finally got the output as,
[Output][2]
Hope this is what you are lookin on.
[1]: https://i.stack.imgur.com/OEEMe.png
[2]: https://i.stack.imgur.com/g6ivm.png
Change your
SELECT HOUR(TIMEDIFF(end,start)) AS 'totalHour'
to
SELECT sum(TIMESTAMPDIFF(HOUR, end, start)) AS 'totalHour'
SELECT IF(DATE(datetime_end) = DATE(datetime_start), TIMEDIFF(datetime_end,datetime_start), IF(DATEDIFF(datetime_start,datetime_end) > 1, ADDTIME( TIME_FORMAT(CONCAT((DATEDIFF(datetime_start,datetime_end) - 1) * 8,':00:00'), "%H:%i:%s"), ADDTIME( TIMEDIFF(datetime_end,CONCAT(DATE(datetime_end),' 08:00:00')), TIMEDIFF(CONCAT(DATE(datetime_start),' 17:00:00'),datetime_start) ) ) , ADDTIME( TIMEDIFF(datetime_end,CONCAT(DATE(datetime_end),' 08:00:00')), TIMEDIFF(CONCAT(DATE(datetime_start),' 17:00:00'),datetime_start) ) ) ) AS total_working_hrs FROM table