SQL count row equal to column - mysql

Here is my table:
+--------+---------------------+
| roomNo | date |
+--------+---------------------+
| 1 | 2017-05-17 16:05:00 |
| 1 | 2017-05-17 15:05:00 |
| 2 | 2019-05-20 12:30:00 |
| 2 | 2019-05-15 10:30:00 |
| 2 | 2019-05-14 08:00:00 |
+--------+---------------------+
I want to get the day where the room is used at least once and which day(s) had the most operations in it and how many times, in the current year. I don't know how to compare the dates.
The expected result would be something like :
+--------+------------+------------+
| roomNo | date | operations |
+--------+------------+------------+
| 2 | 2019-05-20 | 3 |
+--------+------------+------------+

We can use MySQL DATE function to lop off times from DATETIME and TIMESTAMP columns. Or we could use MySQL DATE_FORMAT function, to return just year, month day.
We can use an aggregate function like COUNT or SUM in a query with GROUP BY to get counts by room and day.
If "current year" means from Jan 1 thru Dec 31, we can use expression to derive date values of '2019-01-01' and '2020-01-01', and do a comparison of the date column to those values in the WHERE clause.
As a start, consider this:
SELECT t.roomno
, DATE(t.date) AS date_
, COUNT(*) AS cnt_
FROM mytable t
WHERE t.date >= DATE_FORMAT(NOW(),'%Y-01-01') + INTERVAL 0 YEAR
AND t.date < DATE_FORMAT(NOW(),'%Y-01-01') + INTERVAL 1 YEAR
GROUP
BY t.roomno
, DATE(t.date)
ORDER
BY t.roomno
, cnt_ DESC
If the goal is to just return one of the rooms that has the highest number of uses, we could use a LIMIT clause, and order by the highest count to lowest,
ORDER
BY cnt_ DESC
, t.roomno
LIMIT 1
If the results are more complex than that, we can omit the LIMIT clause, and use the result from that query as an inline view in an outer query.
With MySQL 8.0, we can use common table expression (CTE) and window/analytic functions, to get more elaborate results.

Related

How to group entries by month (from a date field) when `only_full_group_by` is on?

Example table:
+---------+---------------------+
| payment | date |
+---------+---------------------+
| 20 | 2021-02-03 12:00:00 |
| 40 | 2021-02-02 12:00:00 |
| 30 | 2021-01-01 12:00:00 |
| 60 | 2020-02-02 12:00:00 |
+---------+---------------------+
Desired output would sum up payments from each month.
+----------+---------+
| total | month |
+----------+---------+
| 60 | 2021-02 |
| 30 | 2021-01 |
| 60 | 2020-02 |
+----------+---------+
I have some older code that successfully outputs this result by grouping them by a formatted date, so it uses the DATE_FORMAT(date, '%Y-%m') as the grouping condition.
It looks something like this:
SELECT SUM(`payment`) AS total, DATE_FORMAT(`date`, '%Y-%m') AS month
FROM `my_table`
GROUP BY DATE_FORMAT(`date`, '%Y-%m')
ORDER BY `date` DESC
But when only_full_group_by is on, this no longer works, complaining that:
SELECT list is not in GROUP BY clause and contains nonaggregated column 'date' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
Doing GROUP BY MONTH(date), YEAR(date) produces the same result.
While grouping by date only would execute, but would result in an incorrect output as it wouldn't sum up the two entries from 2021-02 together.
Is this something that only_full_group_by shouldn't even allow in the first place? Or if it should, then how would I write this statement correctly to continue outputting the correct result?
Hmmm . . . The problem is the date in the ORDER BY. A simple workaround is:
SELECT SUM(`payment`) AS total, DATE_FORMAT(`date`, '%Y-%m') AS month
FROM `my_table`
GROUP BY month
ORDER BY month DESC;
I used month in the GROUP BY as well, because MySQL conveniently allows that syntax.
Or, if you prefer:
ORDER BY MIN(`date`)
The bare date is not available for the ORDER BY because it is not in the result set generated by the GROUP BY.

how to calculate of sum of multiple conditional values in mysql

I'm trying to get sums of conditional values and group by minute.
I succeeded with the following query but results takes some time and wonder is there any efficient way?
SELECT x.query, x.value, x.time FROM (
SELECT id, query, SUM(VALUE) AS value, time FROM `modbuslogs` WHERE query IN ("sensor1","sensor2") AND time LIKE '2020-12-04%' GROUP BY HOUR(time), MINUTE(TIME)
UNION
SELECT id, query, SUM(VALUE) AS value, time FROM `modbuslogs` WHERE query IN ("sensor3","sensor4") AND time LIKE '2020-12-04%' GROUP BY HOUR(time), MINUTE(TIME)
) x
GROUP BY x.query, HOUR(time), MINUTE(TIME)
ORDER BY x.id
Table structure:
+--------+-------+-----------------+
| query | value | time |
+--------+-------+-----------------+
|sensor1 | 2 |2012-02-10 00:00 |
|sensor2 | 2 |2012-02-10 00:00 |
|sensor3 | 3 |2012-02-10 00:00 |
|sensor4 | 3 |2012-02-10 00:00 |
|sensor1 | 2 |2012-02-10 00:01 |
|sensor2 | 3 |2012-02-10 00:01 |
|sensor3 | 3 |2012-02-10 00:01 |
|sensor4 | 2 |2012-02-10 00:01 |
+--------+-------+-----------------+
Obtained and expected Output:
+--------+-------+-----------------+
| query | value | time |
+--------+-------+-----------------+
|sensor1 | 4 |2012-02-10 00:00 |
|sensor3 | 6 |2012-02-10 00:00 |
|sensor1 | 5 |2012-02-10 00:01 |
|sensor3 | 5 |2012-02-10 00:01 |
+--------+-------+-----------------+
There is no point for the union subquery in the first place. This is equivalent to your query:
select id, query, sum(value) as value, min(time) as time
from modbuslogs
where
query in ('sensor1', 'sensor2', 'sensor3', 'sensor4')
and time >= '2020-12-04'
and time < '2020-12-05'
group by query, hour(time), minute(time)
This gives one row per query and per minute, with the sum of value. Not that we need an aggregate function around time, so the select clause is consistent with the group by clause. Also, the where clause uses date filtering rather than string matching.
On the other hand, if you want the two groups of sensors in two different columns, then use conditional aggrgation:
select id, min(time) as time,
sum(case when query in ('sensor1', 'sensor2') then value else 0 end) as value_1_2,
sum(case when query in ('sensor3', 'sensor4') then value else 0 end) as value_3_4
from modbuslogs
where
query in ('sensor1', 'sensor2', 'sensor3', 'sensor4')
and time >= '2020-12-04'
and time < '2020-12-05'
group by hour(time), minute(time)

Using multiple group_concats and group by on the same field in MySQL

I am trying to write a query that tracks the occurrences over a one-month span, with each day of the month being a column of the result. When trying to use group_concat for each day of the month, only the date of the first occurrence is returned. I've included the schemas and data of the 2 tables that I'm working with below, as well as the result that I expect to see and the SQL query that I've tried.
Note: I've simplified the data and schema as much as possible to get to the root of the question.
Tracking
tracking_id | date
_______________________________________
1 | 2017-05-01
2 | 2017-05-02
TrackingStatus
trackingstatus_id | tracking_id | time
___________________________________________
1 | 1 | 09:00
2 | 2 | 10:00
3 | 2 | 14:00
Desired Result
Month | Day1 | Day2
5 | 09:00 | 10:00,14:00
I tried the following query
SELECT
MONTH(date),
if( date = '2017-05-01', group_concat(timeadministered), NULL ),
if( date = '2017-05-02', group_concat(timeadministered), NULL )
FROM trackingstatus ts
JOIN tracking t ON t.tracking_id = ts.tracking_id
GROUP BY MONTH(date)
But am getting the following result
Month | Day1 | Day2
5 | 09:00,10:00,14:00 |
I know that when I use the group by on the date, only the first date value is recognized by the row. Is there a better way to go about this then using group by and group_concat?
You probably want an aggregate function around the expressions in the SELECT list. But wrapping another aggregate around GROUP_CONCAT doesn't make a lot of sense, since the expression in the GROUP BY clause means you will only get back one row for each month.
Maybe you are wanting something like this:
SELECT MONTH(t.date)
, GROUP_CONCAT(IF(t.date = '2017-05-01',t.timeadministered,NULL)) AS Day01
, GROUP_CONCAT(IF(t.date = '2017-05-02',t.timeadministered,NULL)) AS Day02
FROM ...
GROUP BY MONTH(t.date)

MySQL does not return right data for <= operator

I have the following MySQL query:
select date(JoinTime), count(UniqueCallID) from Call2 where JoinTime >= '2016-03-10' and JoinTime <= '2016-03-15' group by day(JoinTime);
and the result I get is:
| date(JoinTime) | count(distinct UniqueCallID) |
+----------------+------------------------------+
| 2016-03-10 | 20 |
| 2016-03-11 | 29 |
| 2016-03-12 | 2 |
| 2016-03-13 | 5 |
| 2016-03-14 | 12 |
As you can see it has not returned data for 2016-03-15, even though I clearly set the >= operator as I wanted to include 2016-03-15.
I've also tried with BETWEEN like this:
select date(JoinTime), count(UniqueCallID) from Call2 where JoinTime between '2016-03-10' and '2016-03-15' group by day(JoinTime);
and the result was the same.
Why does this happen? And what can I do to fix it?
As you are only comparing to dates, you need to CAST JoinTime to a date first
select date(JoinTime), count(UniqueCallID)
from Call2 where date(JoinTime) between '2016-03-10' and '2016-03-15'
group by day(JoinTime);
Also, as there might be several months relevant, you should group by year, month and day:
select date(JoinTime), count(UniqueCallID)
from Call2 where date(JoinTime) between '2016-03-10' and '2016-03-15'
group by year(JoinTime), month(JoinTime), day(JoinTime);
The way you are using it, an entry like
2016-03-15 12:21:32
will be checked against
2016-03-15 00:00:00
so it will not be included as it doesn't match the <= condition.
If JoinTime is of type DateTime and you don't provide a 'time' part, MySql assumes that your time is midnight ('2016-03-10 00:00:00'). So you are checking from '2016-03-10 00:00:00' to '2016-03-15 00:00:00' which gives your result.
Try a day later, or cast to date how mmm provided.

Counting appointments for each day using MYSQL

I'm in trouble with a mysql statement counting appointments for one day within a given time period. I've got a calendar table including starting and finishing column (type = DateTime). The following statement should count all appointments for November including overall appointments:
SELECT
COUNT('APPOINTMENTS') AS Count,
DATE(c.StartingDate) AS Datum
FROM t_calendar c
WHERE
c.GUID = 'blalblabla' AND
((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY DATE(c.StartingDate)
HAVING Count > 1
But how to include appointments that starts before a StartingDate and ends on the StartingDate?
e.g.
StartingDate = 2012-11-14 17:00:00, EndingDate = 2012-11-15 08:00:00
StartingDate = 2012-11-15 09:00:00, EndingDate = 2012-11-15 10:00:00
StartingDate = 2012-11-15 11:00:00, EndingDate = 2012-11-15 12:00:00
My statement returns a count of 2 for 15th of November. But that's wrong because the first appointment is missing. How to include these appointments? What I am missing, UNION SELECT, JOIN, sub selection?
A possible solution?
SELECT
c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count,
DATE(c1.StartingDate) AS Datum,
COUNT(DISTINCT c2.ANYFIELD)
FROM
t_calendar c1
LEFT JOIN
t_calendar c2
ON
c2.ResourceGUID = c1.ResourceGUID AND
(DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND
(DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE
((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY
c1.ResourceGUID,
DATE(c1.StartingDate)
First: Consolidate range checking
First of all your two range where conditions can be replaced by a single one. And it also seems that you're only counting appointments that either completely overlap target date range or are completely contained within. Partially overlapping ones aren't included. Hence your question about appointments that end right on the range starting date.
To make where clause easily understandable I'll simplify it by using:
two variables to define target range:
rangeStart (in your case 1st Nov 2012)
rangeEnd (I'll rather assume to 1st Dec 2012 00:00:00.00000)
won't be converting datetime to dates only (using date function) the way that you did, but you can easily do that.
With these in mind your where clause can be greatly simplified and covers all appointments for given range:
...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...
This will search for all appointments that fall in target range and will cover all these appointment cases:
start end
target range |==============|
partial front |---------|
partial back |---------|
total overlap |---------------------|
total containment |-----|
Partial front/back may also barely touch your target range (what you've been after).
Second: Resolving the problem
Why you're missing the first record? Simply because of your having clause that only collects those groups that have more than 1 appointment starting on a given day: 15th Nov has two, but 14th has only one and is therefore excluded because Count = 1 and is not > 1.
To answer your second question what am I missing is: you're not missing anything, actually you have too much in your statement and needs to simplified.
Try this statement instead that should return exactly what you're after:
select count(c.GUID) as Count,
date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
(c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
(c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)
I used str_to_date function to make string to date conversion more safe.
I'm not really sure why you included having in your statement, because it's not really needed. Unless your actual statement is more complex and you only included part that's most relevant. In that case you'll likely have to change it to:
having Count > 0
Getting appointment count per day in any given date range
There are likely other ways as well but the most common way would be using a numbers or ?calendar* table that gives you the ability to break a range into individual points - days. They you have to join your appointments to this numbers table and provide results.
I've created a SQLFiddle that does the trick. Here's what it does...
Suppose you have numbers table Num with numbers from 0 to x. And appointments table Cal with your records. Following script created these two tables and populates some data. Numbers are only up to 100 which is enough for 3 months worth of data.
-- appointments
create table Cal (
Id int not null auto_increment primary key,
StartDate datetime not null,
EndDate datetime not null
);
-- create appointments
insert Cal (StartDate, EndDate)
values
('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
('2012-12-01 05:00:00', '2012-12-10 12:00:00');
-- numbers table
create table Nums (
Id int not null primary key
);
-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
from (select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as a,
(select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as b
Now what you have to do now is
Select a range of days which you do by selecting numbers from Num table and convert them to dates.
Then join your appointments to those dates so that those appointments that fall on particular day are joined to that particular day
Then just group all these appointments per each day and get results
Here's the code that does this:
-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;
-- start and end target date range
set #s := str_to_date('2012-11-01', '%Y-%m-%d');
set #e := str_to_date('2012-12-01', '%Y-%m-%d');
-- get appointment count per day within target range of days
select adddate(#s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
left join Cal c
on ((date(c.StartDate) <= adddate(#s, n.Id)) and (date(c.EndDate) >= adddate(#s, n.Id)))
where adddate(#s, n.Id) < #e
group by Day;
And this is the result of this rather simple select statement:
| DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 | 2 |
| 2012-11-02 | 0 |
| 2012-11-03 | 0 |
| 2012-11-04 | 0 |
| 2012-11-05 | 0 |
| 2012-11-06 | 0 |
| 2012-11-07 | 0 |
| 2012-11-08 | 0 |
| 2012-11-09 | 0 |
| 2012-11-10 | 0 |
| 2012-11-11 | 0 |
| 2012-11-12 | 0 |
| 2012-11-13 | 0 |
| 2012-11-14 | 0 |
| 2012-11-15 | 1 |
| 2012-11-16 | 1 |
| 2012-11-17 | 0 |
| 2012-11-18 | 0 |
| 2012-11-19 | 0 |
| 2012-11-20 | 1 |
| 2012-11-21 | 1 |
| 2012-11-22 | 1 |
| 2012-11-23 | 1 |
| 2012-11-24 | 1 |
| 2012-11-25 | 1 |
| 2012-11-26 | 1 |
| 2012-11-27 | 1 |
| 2012-11-28 | 1 |
| 2012-11-29 | 1 |
| 2012-11-30 | 2 |