Mysql get value without functions - mysql

I have this mysql query, I have the following query in mysql, I get a table with the maximum value per day of 8, but I would like to have the value of 08:00 and if that value does not exist use 08:15 and if 08:15 does not exist use the of 08:30 but do not use max or min or average, I need that value.
How can I do?
select month(fecha) as mes, day(fecha) as dia, DATE_FORMAT(fecha, '%m-%d') as mesdia1,
DATE_FORMAT(fecha,'%b %d') mesdia,
cast(max(case when fecha between '2022-01-01' and '2022-12-31' then valor end) as decimal(10,4)) as 'a2022',
cast(max(case when fecha between '2021-01-01' and '2021-12-31' then valor end) as decimal(10,4)) as 'a2021',
cast(max(case when fecha between '2020-01-01' and '2020-12-31' then valor end) as decimal(10,4)) as 'a2020',
cast(max(case when fecha between '2019-01-01' and '2019-12-31' then valor end) as decimal(10,4)) as 'a2019',
cast(max(case when fecha between '2018-01-01' and '2018-12-31' then valor end) as decimal(10,4)) as 'a2018',
cast(max(case when fecha between '2017-01-01' and '2017-12-31' then valor end) as decimal(10,4)) as 'a2017',
cast(max(case when fecha between '2016-01-01' and '2016-12-31' then valor end) as decimal(10,4)) as 'a2016'
from datos
where id_estacion=1 and tipo_sensor=3 and year(fecha) in (2022,2021,2020, 2019,2018,2017,2016) and (hora='08:00' or hora='08:15' or hora='08:30' or hora='08:45')
group by id_estacion,month(fecha), day(fecha)
order by month(fecha), day(fecha)
Thanks
I Add the information requested here.
CREATE TABLE datos (
id_estacion smallint(6) DEFAULT NULL,
tipo_sensor smallint(6) DEFAULT NULL,
valor float DEFAULT NULL,
fecha date DEFAULT NULL,
hora time DEFAULT NULL,
id int(11)
NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id) )
DATA
INSERT INTO `datos` VALUES ('1', '3', '1140.83', '2022-01-04', '08:30:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.791', '2022-01-04', '08:45:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.932', '2022-01-05', '08:00:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.6333', '2022-01-05', '08:15:00');
INSERT INTO `datos` VALUES ('1', '3', '1139.9312', '2022-01-05', '08:30:00');
INSERT INTO `datos` VALUES ('1', '3', '1139.132', '2022-01-05', '08:45:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.032', '2022-01-06', '08:15:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.124', '2022-01-06', '08:45:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.132', '2022-01-07', '08:00:00');
INSERT INTO `datos` VALUES ('1', '3', '1140.08', '2022-01-07', '08:15:00');
INSERT INTO `datos` VALUES ('1', '3', '1139.12', '2022-01-07', '08:30:00');
INSERT INTO `datos` VALUES ('1', '3', '1139.675', '2022-01-07', '08:45:00');
INSERT INTO `datos` VALUES ('1', '3', '1139.575', '2022-01-08', '08:45:00');
I need to get the closest value to 08:00

I think you are over-thinking it. It sounds like you want the minimum time of a given event on a per-day basis.
To simplify, we can just ask for the minimum time on a per-day basis from the raw data. Since you are asking for all years from 2016 to current, I am just asking for the date > '2016-01-01'. No need to be explicit of each individual year. Also, while getting the data, your sample only included those 4 time slots, but I think you dont really care what time it as as long as it was the earliest one of the given day.
That said, my inner query is just pre-gathering the data, pre-grouped by date (month/day, but leaving it as an entire date field), but also grabbing the year of the data for the outer level to simplify use. Also, since the where clause is explicitly only getting data for id_estacion = 1, no need to group by it as they will ALL be that value and thus redundant.
select
month(fecha) rptMonth,
day(fecha) rptDay,
case when d1.FechaYear = 2022 then d1.MaxValor end a2022,
case when d1.FechaYear = 2021 then d1.MaxValor end a2021,
case when d1.FechaYear = 2020 then d1.MaxValor end a2020,
case when d1.FechaYear = 2019 then d1.MaxValor end a2019,
case when d1.FechaYear = 2018 then d1.MaxValor end a2018,
case when d1.FechaYear = 2017 then d1.MaxValor end a2017,
case when d1.FechaYear = 2016 then d1.MaxValor end a2016
from
( select
fecha,
max( year( fecha )) as FechaYear,
min( hora ) MaxHora,
max( valor ) MaxValor
from
datos
where
id_estacion = 1
and tipo_sensor = 3
and fecha > '2016-01-01'
and hora >= '08:00'
group by
fecha ) d1
order by
fecha
Now, this is first pass, but possibly CLOSE to what you want and I will clarify too.
By having the hour column being at or greater than 8am, it will ignore any records with a time such as 2:30am, 5:45am, 7:45am, etc. If you want a cut-off time, such as before 9:00am, then you could easily add "and hora <= '09:00'" to the where clause.
Now, for your "valor" value, you were looking for the maximum value within any respective year. But I am not sure if that is what you mean when you were doing the MAX( case when per year to get the VALOR )... So, if on a given day you have an 8am entry with a valor of 12.35 and an 8:45am entry with 18.85, which valor do you want. Do you want the 18:85 even though it was later in the day? OR, do you want the result line to show the 8am slot that had a valor of 12.35 valor. If the first scenario showing the 18.85, then the query should work for you.
Now, you stated you did not want to use min(), max(), avg(), and dont know why. If you are restricting your time period down, then you are just getting the lowest one that qualified for the time. Similar for the max() of the valor. Since these times and valor amounts are grouped on the PER-DAY, you should be good to just apply which group they fall into in the outer portion of the query. No need to ask for the fecha between two dates. If the year of it is the given one, all done.
Since the inner query is already grouping on a per-day basis, it will only return a single row per day, so the outer query can get the month() and day() context as final output.
Your original group by was by the month and day. Dont know if that was intended or not. If so, then your data would have possibly been returned with
Jan 1 2022
Jan 1 2021
Jan 1 2020
...
Jan 1 2016
Jan 2 2022
Jan 2 2021
...
Jan 2 2016
etc.
If that IS what you intended, then yes, change your group by to the month(), day(), year() respectively, otherwise you can just order by the fecha in ascending or descending order for natural calendar sequential date output.

Related

Find the user who increased their hours streamed from the previous calendar month

This table is a "heartbeat" tracking event where one row is genereated each minute for each streamer while that streamer is live. If a streamer is live for 60 minutes, 60 rows would be generated in this table
Create Table minute_streamed
(
time_minute datetime ,
username varchar(50) ,
category varchar(50) ,
concurrent_viewers int
)
Insert into minute_streamed values ('2020-03-18 12:00:00', 'alex','Fornite',125) ;
Insert into minute_streamed values ('2020-03-18 12:01:00', 'alex','Fornite',130) ;
Insert into minute_streamed values ('2020-03-19 15:30:00', 'jamie','Just Chatting',13) ;
Insert into minute_streamed values ('2020-03-19 15:31:00', 'jamie','Food & Drink',15) ;
Insert into minute_streamed values ('2020-03-20 10:30:00', 'rick','Call of Duty: Black Ops',150) ;
Insert into minute_streamed values ('2020-03-20 10:31:00', 'rick','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'rick','Fornite',120) ;
Insert into minute_streamed values ('2020-04-20 10:31:00', 'rick','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'rick','Fornite',120) ;
Insert into minute_streamed values ('2020-04-20 10:31:00', 'jamie','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'jamie','Fornite',120) ;
Insert into minute_streamed values ('2020-04-18 12:00:00', 'alex','Fornite',125) ;
Insert into minute_streamed values ('2020-04-18 12:01:00', 'alex','Fornite',130) ;
Insert into minute_streamed values ('2020-06-18 14:00:00', 'alex','Fornite',120) ;
Alex has two entries in March. That means he streamed for 2 minutes. So, his hourly streamed for March will be 2/60.
I am trying to write a query: For each calendar month, output the list of streamers, who increased their hours streamed from the previous calendar month
For example, Alex has two entries for March, two entries for April, and one entry for June. So he streamed 2 minutes in March (because he has two entries), 2 minutes in April and 1 minute in June. I want to compare his last month, which is June and the previous calendar month. In this case, the previous calendar month is May, which Alex did not stream. So I need to say that he did not stream in May and he streamed in June. So, he increased his streaming compare to the previous calendar month
This my code below, but I want to compare the current streaming hour with the previous calendar month. Can you please help modify my query?
select
*
from(
select
*
,lag(total_monthly_hours,1) over(partition by username order by year,month) as prev_month
from(
select
username
,year(time_minute) as year
,month(time_minute) as month
,count(*)/60 as total_monthly_hours
from minute_streamed
group by year(time_minute), month(time_minute), username
order by month(time_minute) desc ) as temp ) as temp2
where total_monthly_hours > prev_month
If I understand correctly, you want to compare both the previous value and the previous time period. One way to do this is to calculate a "number of months" by multiplying the year by 12 and adding in the month number. Then you can see if the lag() is getting the value from the previous row:
select uym.*
from (select uym.*,
lag(total_monthly_hours) over (partition by username order by year, month) as prev_total_monthly_hours,
lag(month_cnt) over (partition by username order by year, month) as prev_month_cnt
from (select username,
year(time_minute) as year,
month(time_minute) as month,
year(time_minute) * 12 + month(time_minute) as month_cnt,
count(*)/60 as total_monthly_hours
from minute_streamed
group by year(time_minute), month(time_minute), username, month_cnt
) uym
) uym
where prev_month_cnt is null or
prev_month_cnt <> month_cnt - 1 or
(prev_month_cnt = month_cnt - 1 and prev_total_monthly_hours < total_monthly_hours);
Here is a db<>fiddle.

How to get the amount of extra hours registered for each day reported in MySQL stored procedure

Having an attendance report which displays the entrance, lunch time exit, lunch time entrance and final exit for each user, I need to display the extra hours worked counting only the time after 20:30 and limited to 21:30.
i.e.
if a worker reports its exit around 20:10 it shouldn't count as extra time.
if a worker reports its exit around 21:00 it should count as extra time.
if a worker reports its exit after 21:30 it should only count as extra time until 21:30.
Having the following table:
CREATE TABLE `tblassistance` (
`Entrance` datetime NOT NULL,
`LunchExit` datetime DEFAULT NULL,
`LunchEntrance` datetime DEFAULT NULL,
`DayExit` datetime DEFAULT NULL,
`UserID` int(11) NOT NULL);
INSERT INTO tblassistance(Entrance, LunchExit, LunchEntrance, DayExit, UserID) VALUES ('2019-01-05 14:30:00','2019-01-05 15:30:00','2019-01-05 16:30:00','2019-01-05 21:30:00', '1');
INSERT INTO tblassistance(Entrance, LunchExit, LunchEntrance, DayExit, UserID) VALUES ('2019-01-05 14:30:00','2019-01-05 15:30:00','2019-01-05 16:30:00','2019-01-05 21:36:00', '2');
INSERT INTO tblassistance(Entrance, LunchExit, LunchEntrance, DayExit, UserID) VALUES ('2019-01-05 14:30:00','2019-01-05 15:30:00','2019-01-05 16:30:00','2019-01-05 21:00:00', '3');
INSERT INTO tblassistance(Entrance, LunchExit, LunchEntrance, DayExit, UserID) VALUES ('2019-01-05 14:30:00','2019-01-05 15:30:00','2019-01-05 16:30:00','2019-01-05 20:10:00', '4');
The report needs to display the extra time for each user counting from 20:30 of each day limited to 21:30.
So far I've managed to (try to) sketch the following query:
(SELECT TIMEDIFF(DATE(tblassistance.DayExit) = CURDATE() && tblassistance.DayExit> (SELECT CONCAT(CURDATE(), ' 21:30:00'))) GROUP BY tblassistance.userID, tblassistance.entrance
The original report is in a stored procedure format, which only receives an initial and final date, and also reports the number of days and other parameters unrelated to the present issue. If it's needed in order to solve the present issue (getting the TIMEDIFF() amount of extra time reported for a date) I can give further details.
Try this query:
SELECT
UserID,
GREATEST(
"00:00:00",
TIMEDIFF(
LEAST( DayExit, DATE_FORMAT( DayExit, "%Y-%m-%d 21:30:00" ) ) ,
DATE_FORMAT( DayExit, "%Y-%m-%d 20:30:00" )
)
)
FROM `tblassistance`

Ingnoring particular record while execution in mysql

I have a query where I am getting rows like blow.
date Week Total
'2018-01-10' '00', '104072'
'2018-01-10', '48', '2'
'2018-01-10', '00', '61'
'2018-01-10', '48', '3'
Here every time i am execution the query I am getting the week 00 like above
Now I want to ignore that particular record '00' while executing the query.
Please guide me how to do this.
Thanks in advance.
If that's a VARCHAR column:
SELECT * FROM whatever WHERE Week != '00'
It's generally better to store numbers as INT for a variety of reasons.

How to sort a list of people by birth and death dates when data are incomplete

I have a list of people who may or may not have a birth date and/or a death date. I want to be able to sort them meaningfully - a subjective term - by birth date.
BUT - if they don't have a birth date but they to have a death date, I want to have them collated into the list proximal to other people who died then.
I recognize that this is not a discrete operation - there is ambiguity about where someone should go when their birth date is missing. But I'm looking for something that is a good approximation, most of the time.
Here's an example list of what I'd like:
Alice 1800 1830
Bob 1805 1845
Carol 1847
Don 1820 1846
Esther 1825 1860
In this example, I'd be happy with Carol appearing either before or after Don - that's the ambiguity I'm prepared to accept. The important outcome is that Carol is sorted in the list relative to her death date as a death date, not sorting the death dates in with the birth dates.
What doesn't work is if I coalesce or otherwise map birth and death dates together. For example, ORDER BY birth_date, death_date would put Carol after Esther, which is way out of place by my thinking.
I think you're going to have to calculate an average age people end up living (for those having both birth and death dates). And either subtract them from death date or add them to birth date for people who don't have the other one.
Doing this in one query may not be efficient, and perhaps ugly because mysql doesn't have windowing functions. You may be better of precalculating the average living age beforehand. But let's try to do it in one query anyway:
SELECT name, birth_date, death_date
FROM people
ORDER BY COALESCE(
birth_date,
DATE_SUB(death_date, INTERVAL (
SELECT AVG(DATEDIFF(death_date, birth_date))
FROM people
WHERE birth_date IS NOT NULL AND death_date IS NOT NULL
) DAY)
)
N.B.: I've tried with a larger dataset, and it is not working completely as I'd expect.
Try with this query (it needs an id primary key column):
SELECT * FROM people p
ORDER BY (
CASE WHEN birth IS NOT NULL THEN (
SELECT ord FROM (
SELECT id, #rnum := #rnum + 1 AS ord
FROM people, (SELECT #rnum := 0) r1
ORDER BY (CASE WHEN birth IS NOT NULL THEN 0 ELSE 1 END), birth, death
) o1
WHERE id = p.id
) ELSE (
SELECT ord FROM (
SELECT id, #rnum := #rnum + 1 AS ord
FROM people, (SELECT #rnum := 0) r2
ORDER BY (CASE WHEN death IS NOT NULL THEN 0 ELSE 1 END), death, birth
) o2
WHERE id = p.id
)
END)
;
What I've done is, basically, to sort the dataset two times, once by birth date and then by death date. Then I've used these two sorted lists to assign the final order to the original dataset, picking the place from the birth-sorted list at first, and using the place from the death-sorted list when a row has no birth date.
Here's a few problems with that query:
I didn't run it against lots of datasets, so I can't really guarantee it will work with any dataset;
I didn't check its performance, so it could be quite slow on large datasets.
This is the table I've used to write it, tested with MySQL 5.6.21 (I can't understand why, but SQL Fiddle is rejecting my scripts with a Create script error, so I can't provide you with a live example).
Table creation:
CREATE TABLE `people` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`birth` INT(11) NULL DEFAULT NULL,
`death` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);
Data (I actually slightly changed yours):
INSERT INTO `people` (`name`, `birth`, `death`) VALUES ('Alice', 1800, NULL);
INSERT INTO `people` (`name`, `birth`, `death`) VALUES ('Bob', 1805, 1845);
INSERT INTO `people` (`name`, `birth`, `death`) VALUES ('Carol', NULL, 1847);
INSERT INTO `people` (`name`, `birth`, `death`) VALUES ('Don', 1820, 1846);
INSERT INTO `people` (`name`, `birth`, `death`) VALUES ('Esther', 1815, 1860);
you can use a subquery to pick a suitable birthdate for sorting purposes
and then a union to join with the records with a birthdate
for example:
select d1.name, null as birthdate, d1.deathdate, max(d2.birthdate) sort from
d as d1, d as d2
where d1.birthdate is null and d2.deathdate <=d1.deathdate
group by d1.name, d1.deathdate
union all
select name, birthdate, deathdate, birthdate from d
where birthdate is not null
order by 4
http://sqlfiddle.com/#!9/2d91c/1
Not sure if this will work, but worth a try (I can't test this on MySQL) so trying to guess:
order by case birth_date when null then death_date else birth_date end case

Order by day_of_week in MySQL

How can I order the mysql result by varchar column that contains day of week name?
Note that MONDAY should goes first, not SUNDAY.
Either redesign the column as suggested by Williham Totland, or do some string parsing to get a date representation.
If the column only contains the day of week, then you could do this:
ORDER BY FIELD(<fieldname>, 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY');
Why not this?
ORDER BY (
CASE DAYOFWEEK(dateField)
WHEN 1 THEN 7 ELSE DAYOFWEEK(dateField)
END
)
I believe this orders Monday to Sunday...
I'm thinking that short of redesigning the column to use an enum instead, there's not a lot to be done for it, apart from sorting the results after you've gotten them out.
Edit: A dirty hack is of course to add another table with id:weekday pairs and using joins or select in selects to fake an enum.
... ORDER BY date_format(order_date, '%w') = 0, date_format(order_date, '%w') ;
This looks messy but still works and seems more generic:
select day,
case day
when 'monday' then 1
when 'tuesday' then 2
when 'wednesday' then 3
when 'thursday' then 4
when 'friday' then 5
when 'saturday' then 6
when 'sunday' then 7
end as day_nr from test order by day_nr;
Using if is even more generic and messier:
select id, day,
if(day = 'monday',1,
if(day = 'tuesday',2,
if(day = 'wednesday',3,
if(day = 'thursday',4,
if(day = 'friday',5,
if(day = 'saturday',6,7)
)
)
)
)
) as day_nr from test order by day_nr;
You can also hide the details of conversion from name to int in stored procedure.
I realise that this is an old thread, but as it comes to the top of google for certain search times I will use it to share my approach.
I wanted the same result as the original question, but in addition I wanted the ordering of the results starting from the current day of the week and then progressing through the rest of the days.
I created a separate table, in which the days were listed over a fortnight, so that no matter which day you started from you could run through a sequence of 7 days.
CREATE TABLE IF NOT EXISTS `Weekdays` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
INSERT INTO `Weekdays` (`id`, `name`) VALUES
(1, 'Monday'),
(2, 'Tuesday'),
(3, 'Wednesday'),
(4, 'Thursday'),
(5, 'Friday'),
(6, 'Saturday'),
(7, 'Sunday'),
(8, 'Monday'),
(9, 'Tuesday'),
(10, 'Wednesday'),
(11, 'Thursday'),
(12, 'Friday'),
(13, 'Saturday'),
(14, 'Sunday');
I then ran the query with a variable that determined the start point in sequence and used a join to get the order number for the days. For example to start the listing at Wednesday, I do the following:
SELECT #startnum := MIN(id) FROM Weekdays WHERE name='Wednesday';
SELECT * FROM Events INNER JOIN ( SELECT id as weekdaynum, name as dayname FROM Weekdays WHERE id>(#startnum-1) AND id<(#startnum+7) ) AS s2 ON s2.dayname=Events.day ORDER BY weekdaynum;
I hope this helps someone who stumbles onto this post.
Found another way, your can reverse order bye week
ORDER BY date_format(date_name, '%w') DESC;
Another way would be to create another table with those days and an int to order them by, join that table when searching, and order by it. Of course, joining on a varchar is not recommended.
Table DaysOfWeek
id | day
--------------------
1 | Monday
2 | Tuesday
3 | Wednesday
4 | Thursday
5 | Friday
6 | Saturday
SELECT * FROM WhateverTable
LEFT JOIN DaysOFWeek on DaysOFWeek.day = WhateverTable.dayColumn
ORDER BY DaysOfWeek.id
(Apologies if that's not correct; I've been stuck with SQL server recently)
Again, this is NOT recommended, but if you cannot alter the data you've already got... This will also work if there are non-standard values in the dayColumn field.
Found another way that works for me:
SELECT LAST_NAME, HIRE_DATE, TO_CHAR(HIRE_DATE, 'fmDAY') as 'Day' FROM EMPLOYEES
ORDER BY TO_CHAR(HIRE_DATE, 'd');
Hope it helps
In my case, since the days can be registered in several languages, to get the correct order I do like this according to Glen Solsberry:
....
....
ORDER BY
FIELD(<fieldname>, 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'),
FIELD(<fieldname>, 'LUNDI', 'MARDI', 'MERCREDI', 'JEUDI', 'VENDREDI', 'SAMEDI', 'DIMANCHE'),
FIELD(<fieldname>, 'LUNES', 'MARTES', 'MIERCOLES', 'JUEVES', 'VIERNES', 'SABADO', 'DOMINGO'),
FIELD(<fieldname>, 'MONTAGE', 'DIENSTAG', 'MITTWOCH', 'DENNERSTAG', 'FREITAG', 'SAMSTAG', 'SONNTAG')
;
Do not forget that, <fieldname> is the name of the date column in question in your case.
I saw that ...WHEN 1 THEN 7... was posted but it should be WHEN 1 THEN 8.
So...
ORDER BY (
CASE DATEPART(DW, yourdatefield)
WHEN 1 THEN 8 ELSE DATEPART(DW, yourdatefield)
END
)
Otherwise Sunday may come before Saturday because both Sunday and Saturday would equal 7. By setting Sunday to 8, it ensures it comes after Saturday.
If you try this, it should work:
SELECT ename, TO_CHAR(hiredate, 'fmDay') as "Day"
FROM my_table
ORDER BY MOD(TO_CHAR(hiredate, 'D') + 5, 7)