Ordered Analytical Functions not allowed in GROUP BY Clause - mysql

I have a query joining 2 tables with a SELECT statement for fields in both tables, and an Ordered Analytical Function to calculate the total volume for specific customers.
I'm getting the error "Ordered Analytical Functions not allowed in GROUP BY Clause" when I try to group the fields. I need the GROUP BY because there are other fields that need to be grouped but I also need the SUM() OVER (PARTITION BY()) for other calculations. How can I create the subquery so as to get rid of the error?
The query is something like this:
a.cust_no,
b.cust_name,
a.location,
c.product,
SUM(a.volume),
SUM(a.weight),
SUM(volume) OVER (PARTITION BY cust_no ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS fixed_volume,
SUM(CASE WHEN a_flag = 'Y' THEN volume ELSE 0 END) OVER (PARTITION BY cust_no ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS bias_volume
FROM test_table a
JOIN test_table2 b ON a.cust_no = b.cust_no
JOIN test_table3 c ON a.cust_no = c.cust_no
GROUP BY 1,2,3,4

There's no OLAP function in your GROUP BY clause.
I would expect a 3504 Selected non-aggregate values must be part of the associated group, this should fix it:
select
a.cust_no,
b.cust_name,
a.location,
c.product,
SUM(a.volume),
SUM(a.weight),
--volume is a detail row -> doesn't exist after aggregation
--SUM(a.volume) OVER (PARTITION BY cust_no) AS fixed_volume,
--aggregated volume can be used
SUM(SUM(a.volume)) OVER (PARTITION BY cust_no) AS fixed_volume,
--same logic
SUM(SUM(CASE WHEN a_flag = 'Y' THEN a.volume ELSE 0 END)) OVER (PARTITION BY cust_no ) AS bias_volume
FROM test_table a
JOIN test_table2 b ON a.cust_no = b.cust_no
JOIN test_table3 c ON a.cust_no = c.cust_no
GROUP BY 1,2,3,4

Related

Calculating consecutive occurences in MySQL

I have a quick question in relation to windowing in MySQL
SELECT
Client,
User,
Date,
Flag,
lag(Date) over (partition by Client,User order by Date asc) as last_date,
lag(Flag) over (partition by Client,User order by Date asc) as last_flag,
case when Flag = 1 and last_flag = 1 then 1 else 0 end as consecutive
FROM db.tbl
This query returns something like the below. I am trying to work out the number of consecutive times that the Flag column was 1 for each user most recently, if they had 11110000111 then we should take the final three occurences of 1 to determine that they had a consecutive flag of 3 times.
I need to extract the start and end date for the consecutive flag.
How would I go about doing this, can anyone help me :)
If we use the example of 11110000111 then we should extract only 111 and therefore the 3 most recent dates for that customer. So in the below, we would need to take 10.01.2023 as the first date and 24.01.2023 as the last date. The consecutive count should be 3
Output:
Use aggregation and string functions:
WITH cte AS (
SELECT Client, User,
GROUP_CONCAT(CASE WHEN Flag THEN Date END ORDER BY Date) AS dates,
CHAR_LENGTH(SUBSTRING_INDEX(GROUP_CONCAT(Flag ORDER BY Date SEPARATOR ''), '0', '-1')) AS consecutive
FROM tablename
GROUP BY Client, User
)
SELECT Client, User,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(dates, ',', -consecutive), ',', 1), '') AS first_date,
CASE WHEN consecutive > 0 THEN SUBSTRING_INDEX(dates, ',', -1) END AS last_date,
consecutive
FROM cte;
Another solution with window functions and conditional aggregation:
WITH
cte1 AS (SELECT *, SUM(NOT Flag) OVER (PARTITION BY Client, User ORDER BY Date) AS grp FROM tablename),
cte2 AS (SELECT *, MAX(grp) OVER (PARTITION BY Client, User) AS max_grp FROM cte1)
SELECT Client, User,
MIN(CASE WHEN Flag THEN Date END) AS first_date,
MAX(CASE WHEN Flag THEN Date END) AS last_date,
SUM(Flag) AS consecutive
FROM cte2
WHERE grp = max_grp
GROUP BY Client, User;
See the demo.
Made an attempt to get the result with more simpler queries and here is my approach taking advantage of lastDate and lastFlag column too.
Run here
WITH eTT
AS
( SELECT Client, User, NULLIF(MAX(Date),
(SELECT MAX(Date) FROM tt t2 WHERE t1.Client=t2.Client AND t1.User=t2.User)) as endDate
FROM tt t1 WHERE LastFlag=0 OR LastFlag IS NULL GROUP BY Client, User
)
SELECT Client, User,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE MIN(Date) END) as first_date,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE MAX(Date) END) as last_date,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE COUNT(endDate) END) as consecutive
FROM tt LEFT JOIN eTT USING (Client, User)
WHERE Date >= endDate OR endDate IS null GROUP BY Client, User;
EDIT
The original table doesn't have LastDate and LastFlag columns and were created using OP's initial query.
Since the method used is not apparantly supported but I get an impression that OP somehow manages to do that on their side.
Hence another cte called tt can be added before eTT containing that query.

Mysql Sum over partition by

Hi I am doing MySQL and using 'Sum over (partition by )'
I want to see the values are adding up by following lines like below
but my result is like just
I'm using the following query:
select dea.location, sum(cast(vac.new_vaccinations as signed)) over (partition by dea.location order by dea.location)
From pr.CovidDeaths_csv dea
join pr.CovidVaccinations_csv vac
on dea.location = vac.location
and dea.date = vac.date
where dea.continent is not null
order by 2;
Does anyone know about this problem?
You're missing the frame specification for window functions in MySQL. It allows you to apply a cumulative sum instead of a static sum:
select dea.location,
sum(cast(vac.new_vaccinations as signed))
over(partition by dea.location
order by dea.location ROWS UNBOUNDED PRECEDING)
From pr.CovidDeaths_csv dea
join pr.CovidVaccinations_csv vac
on dea.location = vac.location
and dea.date = vac.date
where dea.continent is not null
order by 2;
As you've not shared your data from all your tables, I cannot replicate your case, but you can see an analogous pattern on sample data here.

I need to get last created eligible rider ids and pinged rider ids accordeing to a orderId using a sql query

I need to get my data set as this table
I am trying to get eligible set like this, need to group_concat pinged set also
x.id IN (SELECT MAX(x.id) FROM x WHERE ping rider id IS NULL GROUP BY orderId)
You can assign a group based on the cumulative number of non-null values in eligible_riders. Then aggregate and take the last value:
select og.*
from (select order_id, grp, max(eligible_riders) as eligible_riders,
group_concat(rider_id) as riders,
row_number() over (partition by order_id order by min(id) desc) as seqnum
from (select t.*,
sum(eligible_riders <> '') over (partition by order_id order by id) as grp
from t
) t
group by order_id, grp
) og
where seqnum = 1;
Hmmm . . . You could also do this with a correlated subquery, which might look a bit simpler:
select order_id, max(eligible_riders) as eligible_riders,
group_concat(rider_id) as riders
from t
where t.id >= (select max(t2.id)
from t t2
where t2.order_id = t.order_id and
t2.eligible_riders <> ''
)
group by order_id;
For performance, you want an index on (order_id, eligible_riders).

Select latest record with date and time column with group by clause in mysql

I have this table from which I have to select the latest row on the basis of date and time column for each checkpost
I have tried the following queries but not returning the latest data for each checkpost.
SELECT checkpost_id,current_rate,date,time FROM revisionrates
WHERE date IN (SELECT max(date) FROM revisionrates GROUP BY checkpost_id)
The expected output is
You can use window functions:
select rr.*
from (select rr.*,
row_number() over (partition by checkpost_id order by date desc, time desc) as seqnum
from revisionrates rr
) rr
where seqnum = 1;
This requires MySQL 8.0. In earlier versions of MySQL, this is a bit trickier, but one method uses tuples
select rr.*
from rr
where (date, time) in (select rr2.date, rr2.time
from revisionrates rr2
where rr2.checkpoint_id = rr.checkpoint_id
order by rr2.date desc, rr2.time desc
limit 1
);

Sum Values Till the Next Appearance of a Condition

Thank you for coming in.
I have a table like this:
And here is what I want to do: Segregated by id, I want to sum up the Val based on the condition.
For example, for id=1, I want the sum of Val till condition A firstly appear, then another sum of Val between the first A and the second A, and sum of Val between the second and the third A... The sum of Val when condition = B follows the same logic, but should not be influenced by A. Finally, each sum of Val only sums the same id.
How should I do this? I tried group by and partition by, but unable to obtain an ideal result. The ideal output would be like the Sum column in the picture.
Thank you very much.
Assuming that there is a column that specifies the ordering, then you can do what you want. SQL tables represent unordered sets. There is no ordering unless a column specifies the ordering.
You seem to want to define groups for As and Bs. You can do this using window functions. This is a little strange, because you want different groupings -- a case expression can handle that. Here is the idea:
select t.*,
(case when condition = 'A'
then sum(val) over (partition by id, grp_a order by <ordering col>)
when condition = 'B'
then sum(val) over (partition by id order by <ordering col>)
end) as calculation
from (select t.*,
sum(case when condition = 'A' then 1 else 0 end) over (partition by id order by <ordering col> desc) as grp_a
from t
) t;
You are looking for something like this:
WITH somatory as (SELECT id, cond, v,
SUM(v) OVER (PARTITION BY id ORDER BY id ROWS UNBOUNDED PRECEDING) as sumV
from foo),
conditional_somatory as (
select id, cond, v, sumV,
case when cond = 'A' then sumV end as somatoryA,
case when cond = 'B' then sumV end as somatoryB
from somatory
),
last_somatories as (
select id, cond, v, sumV,
SomatoryA,
max(coalesce(somatoryA,0)) over (partition by id order by id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) as lastSomatoryA,
SomatoryB,
max(coalesce(somatoryB,0)) over (partition by id order by id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) as lastSomatoryB
from conditional_somatory)
select id, cond, v,
case when cond = 'A' then sumV - lastSomatoryA
when cond = 'B' then sumV - lastSomatoryB
end as somatory
from last_somatories
See working fiddle.
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2b2d23b1421aa8fb45aa82ed8bad8b32