MYSQL: conditional GROUP BY from parameter? - mysql

Im trying to code a procedure that groups by a parameter variable, but at this time, its impossible for me. Its possible to do or i'm writting any mistake?
The finally of this procedure is retrieve a list of values, grouped by time periods Whitout the group by line, it works fine. (granularity its a procedure parameter)
SET g = CASE granularity
WHEN 'week' THEN 'WEEK(r.value_date,1),YEAR(r.value_date)'
WHEN 'month' THEN 'MONTH(r.value_date,1),YEAR(r.value_date)'
ELSE 'r.value_date'
END;
INSERT INTO system_temp_points_data (`method`,`value`,`num_rows`,value_date,ref,units_id,unit_name,`hash`,granularity) SELECT
'sum' AS `method`,
SUM(`value`) AS `value`,
COUNT(`value`) AS num_rows,
UNIX_TIMESTAMP(MIN(value_date)) AS value_date,
GROUP_CONCAT(raw_data_id) AS ref,
r.units_id,
u.unit_name,
aux_hash,
granularity
FROM
raw_data r
JOIN temp_unit_group t ON (r.units_id = t.units_id)
JOIN units u ON (t.units_id = u.units_id)
AND (t.start_date <= r.value_date)
AND (t.end_date >= r.value_date)
WHERE
r.value_date BETWEEN start_date
AND end_date
AND kpis_id = kpi
AND IF(exclude_zeros = 1,`value` <> '0',true)
AND IF(value_aux IS NOT NULL,r.aux = value_aux,true)
GROUP BY g
ORDER BY
r.value_date ASC;

The strings in g are not evaluated when it's used in the query. You need to put that condition in the query itself:
GROUP BY CASE granularity
WHEN 'week' THEN WEEK(r.value_date,1)
WHEN 'month' THEN MONTH(r.value_date)
ELSE r.value_date
END,
CASE granularity
WHEN 'week' THEN YEAR(r.value_date)
WHEN 'month' THEN YEAR(r.value_date)
ELSE 1
END
An alternative is to use a prepared statement, and merge g into it:
SET #sql = CONCAT("INSERT INTO system_temp_points_data (`method`,`value`,`num_rows`,value_date,ref,units_id,unit_name,`hash`,granularity) SELECT
'sum' AS `method`,
SUM(`value`) AS `value`,
COUNT(`value`) AS num_rows,
UNIX_TIMESTAMP(MIN(value_date)) AS value_date,
GROUP_CONCAT(raw_data_id) AS ref,
r.units_id,
u.unit_name,
aux_hash,
granularity
FROM
raw_data r
JOIN temp_unit_group t ON (r.units_id = t.units_id)
JOIN units u ON (t.units_id = u.units_id)
AND (t.start_date <= r.value_date)
AND (t.end_date >= r.value_date)
WHERE
r.value_date BETWEEN start_date
AND end_date
AND kpis_id = kpi
AND IF(exclude_zeros = 1,`value` <> '0',true)
AND IF(value_aux IS NOT NULL,r.aux = value_aux,true)
GROUP BY ", g, "
ORDER BY
r.value_date ASC";
PREPARE #stmt FROM #sql;
EXECUTE #stmt;

Related

Query to select value from another table as a column

I have a query to report some data :
select r.m_id,
count(distinct case when r.sal = '1val' then r.login end) as 1val,
count(distinct case when r.sal = '2val' then r.login end) as 2val,
count(distinct case when r.sal = '3val' then r.login end) as 3val,
...
from read_log r
inner join mes m on m.id = r.m_id
where
YEAR(m.send_date) = YEAR(curdate())
group by r.m_id
r.sal value in count(distinct case when r.sal = '1val' then r.login end) as 1val only changes. Finally it shows results in each column for every r.sal. There are currently over 80 diffrent r.sal and its growing.
My question is:
It is possible to take value for r.sal from another table?
Like new table sal with this 1val, 2val, 3val, 4val, 5val etc...?
Maybe loop or something like that:
count(distinct case when r.sal = (select val from sal) then r.login end)
(I know its wrong but maybe it will illustrate it better)
count(distinct case... is great to show data for each r.sal value in the other column but maybe is another way to achieve that...
CREATE TABLE sal_table (sal CHAR(4));
INSERT INTO sal_table VALUES ('1val'), ('2val'), ... ;
CREATE PROCEDURE get_data ()
BEGIN
SELECT CONCAT (
'select r.m_id, ',
GROUP_CONCAT(
CONCAT(
'count(distinct case when r.sal = ''',
sal,
''' then r.login end) as `',
sal,
'`'
)
),
' from read_log r ',
'inner join mes m on m.id = r.m_id ',
'where YEAR(m.send_date) = YEAR(curdate()) ',
'group by r.m_id'
)
INTO #sql
FROM sal_table;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=af55c52aca3280410fba1f3a453aab09
PS. Recommended edition: WHERE m.send_date >= DATE_FORMAT(CURRENT_DATE, '%Y-01-01') AND m.send_date < DATE_FORMAT(CURRENT_DATE + INTERVAL 1 YEAR, '%Y-01-01'). Such condition is sargable rather than your one.

GROUPed or used in an aggregate function error

I want to Group By CUSTOM_DRIVER in the query because I am getting over 1.3 million results and I just need to view them by Custom Driver for the month by store.
The query is returning me
I want something like this
I've tried using Distinct and Group By but I keep getting the error:
Attribute A.STORE_NUM must be GROUPed or used in an aggregate function
How do I get around this? The code I've been using is:
SELECT *
FROM(
SELECT
SE.STORE_NUM,
DM.MONTH_NAME_445 AS MO_445,
--DM.WEEK_START_DT_MON AS WK_BEGIN_MONDAY,
--SE.METRIC_DATE,
substring(SE.ROLLUP_NAME, 39, 20) as CUSTOM_DRIVER,
SE.DT_IMPORTED,
SE.METRIC_VALUE
FROM DNA_PUBLIC.ADMIN.SCHEDULE_EFFECTIVENESS SE
JOIN DNA_PUBLIC.ADMIN.DAY_MAP DM ON SE.METRIC_DATE = DM.DATE_DT
JOIN (
SELECT DISTINCT
METRIC_DATE,
MAX(DATE(DT_IMPORTED)) AS MAX_DT
FROM DNA_PUBLIC.ADMIN.SCHEDULE_EFFECTIVENESS
GROUP BY 1
) MX ON SE.METRIC_DATE = MX.METRIC_DATE
WHERE METRIC_NAME = 'Coverage Effectiveness'
AND SE.ROLLUP_NAME LIKE 'O%'
AND SE.METRIC_DATE between '07/27/2020' and '11/11/2020'
AND CAST(SE.DT_IMPORTED AS DATE) = MAX_DT
--AND CUSTOM_DRIVER = 'Truck/Truck'
AND SE.INDICATOR_NAME = 'Required'
--and STORE_NUM = 1
) AS A
Order by STORE_NUM
A work around was to select the values and group by the order of the selected variables. Thanks for the all the input!
SELECT
STORE_NUM,
MO_445,
CUSTOM_DRIVER,
SUM(METRIC_VALUE) as Tot_Hrs
FROM(
SELECT
SE.STORE_NUM,
DM.MONTH_NAME_445 AS MO_445,
DM.WEEK_START_DT_MON AS WK_BEGIN_MONDAY,
SE.METRIC_DATE,
substring(SE.ROLLUP_NAME, 39, 20) as CUSTOM_DRIVER,
SE.DT_IMPORTED,
SE.METRIC_VALUE
FROM DNA_PUBLIC.ADMIN.SCHEDULE_EFFECTIVENESS SE
JOIN DNA_PUBLIC.ADMIN.DAY_MAP DM ON SE.METRIC_DATE = DM.DATE_DT
JOIN (
SELECT DISTINCT
METRIC_DATE,
MAX(DATE(DT_IMPORTED)) AS MAX_DT
FROM DNA_PUBLIC.ADMIN.SCHEDULE_EFFECTIVENESS
GROUP BY 1
) MX ON SE.METRIC_DATE = MX.METRIC_DATE
WHERE METRIC_NAME = 'Coverage Effectiveness'
AND SE.ROLLUP_NAME LIKE 'O%'
AND SE.METRIC_DATE between '07/27/2020' and '11/11/2020'
AND CAST(SE.DT_IMPORTED AS DATE) = MAX_DT
--AND CUSTOM_DRIVER = 'Truck/Truck'
AND SE.INDICATOR_NAME = 'Required'
and STORE_NUM = 1
) AS A
group by 1,2,3
Order by STORE_NUM

Why integer cast is not working with integer group_concat() list?

I'm stuck at the query where I need to concat IDs of the table. And from that group of IDs, I need to fetch that rows in sub query. But when I try to do so, MySQL consider group_concat() as a string. So that condition becomes false.
select count(*)
from rides r
where r.ride_status = 'cancelled'
and r.id IN (group_concat(rides.id))
*************** Original Query Below **************
-- Daily Earnings for 7 days [Final]
select
group_concat(rides.id) as ids,
group_concat(ride_category.name) as rideType,
group_concat(ride_cars.amount + ride_cars.commission) as rideAmount ,
group_concat(ride_types.name) as carType,
count(*) as numberOfRides,
(
select count(*) from rides r where r.ride_status = 'cancelled' and r.id IN (group_concat(rides.id) )
) as cancelledRides,
(
select count(*) from rides r where r.`ride_status` = 'completed' and r.id IN (group_concat(rides.id))
) as completedRides,
group_concat(ride_cars.status) as status,
sum(ride_cars.commission) + sum(ride_cars.amount) as amount,
date_format(from_unixtime(rides.requested_at/1000 + rides.offset*60), '%Y-%m-%d') as requestedDate,
date_format(from_unixtime(rides.requested_at/1000 + rides.offset*60), '%V') as week
from
ride_cars,
rides,
ride_category,
ride_type_cars,
ride_types
where
ride_cars.user_id = 166
AND (rides.ride_status = 'completed' or. rides.ride_status = 'cancelled')
AND ride_cars.ride_id = rides.id
AND (rides.requested_at >= 1559347200000 AND requested_at < 1561852800000)
AND rides.ride_category = ride_category.id
AND ride_cars.car_model_id = ride_type_cars.car_model_id
AND ride_cars.ride_type_id = ride_types.id
group by
requestedDate;
Any solutions will be appreciated.
Try to replace the sub-query
(select count(*) from rides r where r.ride_status = 'cancelled' and r.id IN (group_concat(rides.id) )) as cancelledRides,
with below to count using SUM and CASE, it will make use of the GROUP BY
SUM(CASE WHEN rides.ride_status = 'cancelled' THEN 1 ELSE 0 END) as cancelledRides
and the same for completedRides
And move to using JOIN instead of implicit joins

how to use user defined variables in where clause

I have this query, and i m trying to use the user defined variable #noVar in my where clause to show only the records with value 'Yes' on that variable.
but when I use Having #noVar = 'Yes' as in the query below, it returns 0 result.
SELECT svcreqdetail.id, svcreqcheckin.stime as checkin, #etime:= time(timestampadd(minute, svcreqdetail.hours*60 , concat(caredate,' ', caretime))) as endtime, svcreqcheckout.stime as checkout, time_to_sec( if(svcreqcheckout.stime > svcreqcheckin.stime,
timediff(svcreqcheckout.stime, svcreqcheckin.stime),
addtime(timediff(svcreqcheckout.stime, svcreqcheckin.stime), '24:00:00.000000')))/3600 AS wrkHrs, svcreqdetail.hours,
svcreqstatus.status, #checkoutvar:= time_to_sec(timediff(svcreqcheckout.stime, #etime))/60 as checkoutvar,#noVar:= if (#checkoutvar <= 15,'Yes', 'No') as noVar, qualif
FROM svcreqdetail
LEFT JOIN svcreqcheckin ON svcreqcheckin.reqid = svcreqdetail.id
LEFT JOIN svcreqcheckout ON svcreqcheckout.reqid = svcreqdetail.id
JOIN svcreqstatus ON svcreqstatus.reqdid = svcreqdetail.id
WHERE (yearweek( caredate ) = yearweek( date_sub( CURRENT_DATE, INTERVAL 1 week ) )
AND svcreqstatus.status != 'Incompleted'
AND svcreqstatus.status != 'Deleted')
having #noVar = 'Yes'
is there anyway i can test against that variable in my where clause. and thank you
I don't think you can use user-defined variables in the HAVING clause like that.
One option would be to put your existing query (without the HAVING clause) into a sub-query and filter the results using a WHERE clause outside the sub-query, like so:
SELECT *
FROM
(
<your existing query goes here>
) AS sub_query
WHERE noVar = 'Yes'

Select Status That Last for More Than 1 Second

I have got a problem looks simple, but I could not find the solution.
So, I have got a table with two cols like this:
Time Status
00:00:00.111 Off
00:00:00.222 On
00:00:00.345 On
00:00:01.555 On
00:00:01.666 Off
00:00:02.222 On
00:00:02.422 On
00:00:02.622 Off
00:00:05.888 Off
00:00:05.999 Off
I want to select all statuses of On which lasted for more than 1 second,
in this example, I want the sequence:
00:00:00.222 On
00:00:00.345 On
00:00:01.555 On
Could you guys give me any clue? Many thanks!
A simple GROUP BY and SUM can not do this on your current dataset, so my idea is to add a helper column:
CREATE TABLE someTable(
`time` DATETIME,
status CHAR(3),
helperCol INT
);
The helperCol is an INT and will be set as follows:
CREATE PROCEDURE setHelperCol()
BEGIN
DECLARE finished,v_helperCol INT;
DECLARE status CHAR(3);
DECLARE ts DATETIME;
DECLARE CURSOR st FOR SELECT `time`,status,helperCol FROM someTable WHERE helperCol IS NOT NULL; -- Handy for re-use: No need to go over all data, so you can save the helperCol as permanent value.
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
SELECT #maxVal:=MAX(helperCol) FROM helperCol;
SET finished=0;
SET helperCol=#maxVal;
IF(!helperCol>0) SET helperCol=1;
OPEN st;
FETCH ts,status,v_helperCol FROM st;
WHILE(finished=0) DO
IF(status='Off') v_helperCol=v_helperCol+1;
UPDATE someTable SET helperCol=v_helperCol WHERE `time`=ts; -- Assuming `time` is unique;
FETCH ts,status,v_helperCol FROM st;
END WHILE;
CLOSE st;
END;
Execute the procedure and the result is:
Time Status helperCol
00:00:00.111 Off 2
00:00:00.222 On 2
00:00:00.345 On 2
00:00:01.555 On 2
00:00:01.666 Off 3
00:00:02.222 On 3
00:00:02.422 On 3
00:00:02.622 Off 4
This can now be grouped and processed:
SELECT MAX(`time`)-MIN(`time`) AS diffTime
FROM someTable
WHERE status='ON'
GROUP BY helperCol
HAVING MAX(`time`)-MIN(`time`)>1;
The result of that is (you need to search for the correct datetime functions to apply in the MAX-MIN part):
1.333
Alternative:
You can also process the MAX-MIN in the stored procedure, but that would not be efficiently repeatable as the helperColumn solution is.
SELECT a.time start
, MIN(c.time) end
, TIMEDIFF(MIN(c.time),a.time) duration
FROM
( SELECT x.*, COUNT(*) rank FROM my_table x JOIN my_table y ON y.time <= x.time GROUP BY time ) a
LEFT
JOIN
( SELECT x.*, COUNT(*) rank FROM my_table x JOIN my_table y ON y.time <= x.time GROUP BY time ) b
ON b.status = a.status
AND b.rank = a.rank - 1
JOIN
( SELECT x.*, COUNT(*) rank FROM my_table x JOIN my_table y ON y.time <= x.time GROUP BY time ) c
ON c.rank >= a.rank
LEFT
JOIN
( SELECT x.*, COUNT(*) rank FROM my_table x JOIN my_table y ON y.time <= x.time GROUP BY time ) d
ON d.status = c.status
AND d.rank = c.rank + 1
WHERE b.rank IS NULL
AND d.rank IS NULL
AND a.status = 1
GROUP
BY a.time
HAVING duration >= 1;
Another, faster, method might be along these lines - unfortunately I don't think the data types and functions in my version of MySQL support fractions of a second, so this is probably a little bit wrong (there may also be a logical error)...
SELECT time
, status
, cumulative
FROM
( SELECT *
, CASE WHEN #prev = status THEN #i:=#i+duration ELSE #i:=0 END cumulative
, #prev:=status
FROM
( SELECT x.*
, TIME_TO_SEC(MIN(y.time))-TIME_TO_SEC(x.time) duration
FROM my_table x
JOIN my_table y
ON y.time > x.time
GROUP
BY x.time
) n
ORDER
BY time
) a
WHERE cumulative >= 1
AND status = 1;