MySQL select in select query - mysql

can somebody help me solving and explaining how i can get the following done? :
In my MySQL query i want to select all entries, where the forwarded_to_cordroom value is 0, and in the next row i want to have all where the value is 1, basically i could create 2 identical queries, where the only difference would be the WHERE clause (where forwarded_to_cordroom = 1 , where forwarded_to_cordroom = 0) , and i thought about doing this in one query, but getting the following error with what ive tried:
SELECT
COUNT(DISTINCT o.order_id) as count,
(SELECT o.forwarded_to_cordroom WHERE o.forwarded_to_cordroom = 1)
FROM
`orders_articles` o
LEFT JOIN orders oo ON
o.order_id = oo.order_id
WHERE
(
oo.finished_order_date IS NULL OR oo.finished_order_date >= '2021-09-27'
) AND oo.order_date <= '2021-09-27'
Results in :
#1140 - In aggregated query without GROUP BY, expression #2 of SELECT list contains nonaggregated column 'o.forwarded_to_cordroom'; this is
incompatible with sql_mode=only_full_group_by
I have also tried changing the subselect in various ways (with and without joins etc.) but without success, always the same error.
I'd prefer not turning this mode off, I think that would not be the purpose and that I can fix my query with some help.
Best Regards

Use conditional aggregation:
SELECT COUNT(DISTINCT o.order_id) AS count,
COUNT(CASE WHEN o.forwarded_to_cordroom = 1 THEN 1 END) AS count_1,
COUNT(CASE WHEN o.forwarded_to_cordroom = 0 THEN 1 END) AS count_0
FROM orders_articles AS o
LEFT JOIN orders AS oo ON o.order_id = oo.order_id
WHERE ...

Related

LEFT JOIN limited number of rows with GROUP_CONCAT too slow

I boiled the question down in the db fiddle below.
I have an object table and an images table. X images can be assigned to an object.
Now I have a list where a configurable amount of images needs to be displayed per object.
Please consider this needs to work in MariaDB 10 or MYSQL 5.7. The original construct is much bigger, where indexes are set and more is happening.
My idea was to create a string with concatenated images titles and filenames
This works, but the subquery is super slow:
SELECT o.id, o.title, im.images FROM objects o
LEFT JOIN (
SELECT i.oid, GROUP_CONCAT(i.title, "::", i.name ORDER BY i.ordering ASC SEPARATOR "|") AS images
FROM images i
WHERE i.oid IN (1, 2, 3)
GROUP BY i.oid
ORDER BY NULL) im
ON im.oid=o.id
AND o.id IN (1, 2, 3)
GROUP BY o.id -- this has to stay. original query is bigger...a lot
ORDER BY o.id DESC
DB Fiddle
https://www.db-fiddle.com/f/xi1PJ3P61miBu1fCooWUNR/5
I know this is not limiting the amount of images, but i don't think 5.7 can do that withing GROUP_CONCAT.
It would also be fine if a limited number of images gets queried as single column, so there could be 3 or 4 new columns and not one concatenated string, but i have no idea how to do that.
Use SUBSTRING_INDEX() to return the first N items in the GROUP_CONCAT() result.
SUBSTRING_INDEX(im.images, '|', 3) AS images
will return the first 3 images.
If you prefer to have, say 3 separate columns and not a concatenated string, you can filter the images of each object with a condition like i.ordering <= 3 inside the subquery and then use conditional aggregation:
SELECT o.id, o.title,
MAX(CASE WHEN im.ordering = 1 THEN CONCAT(im.title, "::", im.name) END) image1,
MAX(CASE WHEN im.ordering = 2 THEN CONCAT(im.title, "::", im.name) END) image2,
MAX(CASE WHEN im.ordering = 3 THEN CONCAT(im.title, "::", im.name) END) image3
FROM objects o
LEFT JOIN (
SELECT *
FROM images i
WHERE i.oid IN (1, 2, 3) AND i.ordering <= 3
) im ON im.oid = o.id AND o.id IN (1, 2, 3)
GROUP BY o.id
ORDER BY o.id DESC;
Also, if you want results only for objects with ids 1, 2 and 3 then you should place the condition o.id IN (1, 2, 3) in a WHERE clause and not in the ON clause.
See the demo.
The first thing I notice in your dbfiddle is that you have no index on images(oid), so your condition in your subquery is doing a table-scan of the images table. That could be a part of the performance problem.
I came up with this solution to limit the result to 3 images per oid. This is the kind of awful solution we had to use in pre-MySQL 8.0 days before windowing functions. In fact, using expressions with side-effects like this is now deprecated, because it relies on the expressions in the select-list being evaluated left to right, and there is no guarantee that will be true.
SELECT t.id, t.title, GROUP_CONCAT(t.image SEPARATOR '|') AS images
FROM (
SELECT o.id, o.title, CONCAT(i.title, '::', i.name) AS image,
#row:=IF(#oid=o.id,#row+1,1) as rownum,
#oid:=o.id
FROM objects o
CROSS JOIN (SELECT #row:=0, #oid:=0) AS _init
LEFT JOIN images i ON i.oid = o.id
WHERE o.id IN (1, 2, 3)
ORDER BY i.ordering
) AS t
WHERE t.rownum <= 3
GROUP BY t.id
ORDER BY t.id DESC;
It produces the correct result, but I am not testing it with a large dataset so I can't guarantee it has any better performance.
Do create the index on images(oid) regardless. Adding the ordering column to the index won't help in this query. It must do a filesort anyway, both because of the range query and because the sort is on a table that isn't the first table accessed.
P.S.: I recommend you upgrade. MySQL 5.6 has been past its end of life for over a year by now, and MySQL 8.0 has been GA since 2018.

MySQL Error: 1111 (Invalid use of group function) [duplicate]

This question already has an answer here:
ERROR 1111 (HY000): Invalid use of group function
(1 answer)
Closed 1 year ago.
I'm not yet good at MySQL. Please check my sql below and help me understand where I went wrong with it. All I need is just one record for the order.id and the returned record must be the one whose shipped date is the latest.
Database error: Invalid SQL: SELECT orders.id, orders.customer_fk FROM orders INNER JOIN order_details ON order_details.order_fk=orders.id WHERE orders.payment_method IN ('AS','AC') AND ((orders.order_status='SHP' AND order_details.item_status='SHP' AND MAX(order_details.shipped_date) <= '2021-08-07') OR (orders.order_status='CAN' AND orders.order_date <= '2021-08-07 09:56:18')) AND orders.pii_status <> '1'GROUP BY orders.id
MySQL Error: 1111 (Invalid use of group function)
Instead of using MAX alone try to use a subselect
If you don't want the mad for every order.id then you need to add a inner join
SELECT
orders.id, orders.customer_fk
FROM
orders
INNER JOIN
order_details ON order_details.order_fk = orders.id
WHERE
orders.payment_method IN ('AS' , 'AC')
AND ((orders.order_status = 'SHP'
AND order_details.item_status = 'SHP'
AND (SELECT MAX(shipped_date) FROM order_details WHERE order_fk = orders.id) <= '2021-08-07')
OR (orders.order_status = 'CAN'
AND orders.order_date <= '2021-08-07 09:56:18'))
AND orders.pii_status <> '1'
GROUP BY orders.id
To explain it somewhat further
SELECT MAX(shipped_date) FROM order_details WHERE order_fk = orders.id) <= '2021-08-07'
Return true or false for every Order.id as it checks for every row in the outer select what the maximum date is and then checks it against the date.
After selecting all rows you GROUP BY(which i still don't get as you have no aggregation function it) comes for every order.id.
Maybe you should try a DISTINCT
You select both orders.id and orders.customer_fk, but you group by orders.id only. When using group by in SQL, all other columns not present in the group by clause must be aggregate functions, since for example in this current case you group the results by the order id, meaning there can be only one row per unique order id among the results.
And something has to happen with the list of values of the other column that all belong to this one grouped order id, this is where the aggregations come in. If it is a number you can calculate the MIN/MAX/AVG etc. of these, but the simplest aggregate is to just count the matching results.
So if you wanted your query to return the number of order.customer_fk for each unique order.id, just add SELECT orders.id, COUNT(orders.customer_fk).
Otherwise, if you didn't intend to group your results, you can remove the GROUP BY clause, or replace it with an ORDER BY.
If you want to filter using aggregation functions use having. However, I'm guessing that you just want to filter by the date:
SELECT o.id, o.customer_fk
FROM orders o INNER JOIN
order_details od
ON od.order_fk= o.id
WHERE o.payment_method IN ('AS','AC') AND
((o.order_status = 'SHP' AND od.item_status='SHP' AND od.shipped_date <= '2021-08-07') OR
(o.order_status = 'CAN' AND o.order_date <= '2021-08-07 09:56:18')
) AND
o.pii_status <> '1'
GROUP BY o.id

Best way to subtract SUM and COUNT with 2 table select?

I have came up with solution to count total of fields based on specific group, but it looks quite lengthy to get to the result i expect.
I have some basic knowledge when it comes to sql.
Is there obvious improvements to be made and why?
Why i would like to shorten this: Easier to implement in ORM type systems.
Changing scheme is not an option.
Schema and sample data: http://sqlfiddle.com/#!9/62df6
Query i'm using:
SELECT s.release_id,
(s.shipments_total - IFNULL(sh.shipment_entries, 0)) AS shipments_left
FROM
( SELECT release_id,
SUM(shipments) AS shipments_total
FROM subscriptions
WHERE is_paid = 1
AND shipments > 1
GROUP BY release_id ) AS s
LEFT JOIN
( SELECT release_id,
COUNT(*) AS shipment_entries
FROM shipments
GROUP BY release_id ) AS sh ON s.release_id = sh.release_id
Expected result on sample data is in sqlfiddle.
If you bring the condition in-line and remove the group by, then you don't need ifnull():
SELECT s.release_id,
(SUM(s.Shipments) -
(SELECT COUNT(*)
FROM shipments sh
WHERE sh.release_id = s.release_id
)
) AS shipments_left
FROM subscriptions s
WHERE is_paid = 1 AND shipments > 1
GROUP BY s.release_id;
The subquery returns 0 if nothing matches, not NULL (with the GROUP BY, it would return NULL). I am not sure if this is easier with your ORM model. Your original version is fine from a SQL point of view.
You can bring the join inline instead:
SELECT s.release_id,
SUM(s.Shipments) - IFNULL(( SELECT COUNT(*) AS shipment_entries
FROM shipments sh
WHERE sh.release_id = s.release_id
GROUP BY sh.release_id ), 0) AS shipments_left
FROM subscriptions s
WHERE is_paid = 1
AND shipments > 1
GROUP BY s.release_id
The execution plan for this is more performant too.

mysql conditional statement?

i am facing a little problem in an sql query,
here is the query
SELECT events.event_id,
( SELECT favorite_events.event_id,
(CASE favorite_events.event_id WHEN NULL THEN 0 ELSE 1 END)
FROM favorite_events
WHERE favorite_events.event_id = events.event_id
) AS is_favorite
FROM events
WHERE start_date = 2013-07-16
it is giving me "Operand should contain 1 column(s)
" error, please help!
Use a JOIN, not a correlated subquery.
SELECT e.event_id,
f.event_id IS NOT NULL AS is_favorite
FROM events e
LEFT JOIN favorite_events f
ON f.event_id = e.event_id
WHERE e.start_date = '2013-07-16'
In the subquery you are selecting 2 columns. You can't rename 2 colums as is_favorite.
I assume, by looking at the query, you probably don't need the field favorite_events.event_id. Just remove that part.

optimize Mysql: get latest status of the sale

In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.