SQL query for an unpopular product - mysql

Trying to calculate the most unpopular product in MySQL 5.7, I run the following query:
SELECT
ClientOrderItem.ItemId AS ID_product,
countIf(Orders.ClientOrderStateID = 3) AS cnt
FROM
ClientOrderItem
LEFT JOIN Orders ON ClientOrderItem.ClientOrderID = Orders.id
LEFT JOIN AdditionalInfo ON ClientOrderItem.ClientOrderID = AdditionalInfo.ClientOrderID
WHERE
Orders.ClientOrderStateID = 3
AND (
AdditionalInfo.code = 'IsTestOrder'
AND AdditionalInfo.value = '0'
)
GROUP BY cnt
ORDER BY cnt DESC;
I get the following error:
SQL Error [1305] [42000]: FUNCTION ozon.countIf does not exist
Can you tell me what to replace the "countIf" function with?
Thanks.

Your question does not explain what you mean by "most unpopular". However, in MySQL, if you want to count the rows where Orders.ClientOrderStateID = 3, then you can use
sum(Orders.ClientOrderStateID = 3) AS cnt
The more standard syntax is:
sum(case when Orders.ClientOrderStateID = 3 then 1 else 0 end) as cnt

as mentioned in the comment mysql doesn;t support 'countif'
you can follow this syntax:
SELECT
ClientOrderItem.ItemId AS ID_product,
count(IF Orders.ClientOrderStateID = 3,1,NULL) AS cnt
FROM
...
OR
SELECT
ClientOrderItem.ItemId AS ID_product,
count(case when Orders.ClientOrderStateID = 3 then 1 end) AS cnt
FROM
...

Related

MySQL: Invalid use of group function updating two columns with update/select

I am trying to update two columns within a table from a select statment in MySQL 5.7.
The error I get is "invalid use of group function"
Stmt:
UPDATE
catalog mpc
JOIN
reviews mpr ON mpr.merchant_id = mpc.MERCHANT_ID and mpr.sku = mpc.ARTICLE_ID
SET
mpc.RATING = avg(mpr.rating),
mpc.RATINGS = count(mpr.rating)
WHERE
mpr.MERCHANT_ID = 1
AND mpr.sku = '133';
It looks about right to me, what could be the problem here?
You must aggregate first in reviews and then join to catalog:
UPDATE catalog mpc
INNER JOIN (
SELECT merchant_id, sku, AVG(rating) avg_rating, COUNT(rating) count_rating
FROM reviews
WHERE merchant_id = 1 AND sku = '133'
GROUP BY merchant_id, sku
) mpr ON mpr.merchant_id = mpc.MERCHANT_ID and mpr.sku = mpc.ARTICLE_ID
SET mpc.RATING = mpr.avg_rating,
mpc.RATINGS = mpr.count_rating

MySQL - Joining same table twice, parent sum column value getting duplicated

I'm just 3 months of experience in MySQL. Here I'm trying to generate a report based on log and part table. When trying to join the "Part" table twice, the "Log" table Quantity gets doubled. Kindly let me know where I'm doing wrong.
Log Table
Part Table
Expected Report
Query Used
SELECT
report.*,
(SUM(report.quantity)) AS totalQuantity,
normalPart.price AS normalPrice,
premiumPart.price AS premiumPrice
FROM
log AS report
LEFT JOIN
(SELECT
*
FROM
part AS normalPart
WHERE
normalPart.type = 'normal'
GROUP BY normalPart.partNumber , normalPart.genId) AS normalPart ON report.partNumber = normalPart.partNumber
AND report.genId = normalPart.genId
AND normalPart.cat = report.fromCat
LEFT JOIN
(SELECT
*
FROM
part AS premiumPart
WHERE
premiumPart.type = 'premium'
GROUP BY premiumPart.partNumber , premiumPart.genId) AS premiumPart ON report.partNumber = premiumPart.partNumber
AND report.genId = premiumPart.genId
AND premiumPart.cat = report.toCat;
Query Result
This answers the original version of the question.
Aggregate before you join:
select l.*, p.normal_price, p.premium_price
from (select genid, partnumber, sum(quantity) as quantity
from log
group by genid, partnumber
) l left join
(select partnumber, genid,
max(case when type = 'normal' then price end) as normal_price,
max(case when type = 'premium' then price end) as premium_price
from part
group by partnumber, genid
) p
on p.partnumber = l.partnumber and p.genid = l.genid

A SQL query to get the last status for a file uploaded possibly using a sub query

I need to write a query which I think will need a subquery in it. Currently I'm writing the query as a raw SQL statement using DataGrip and will need to work on a postGres server. I am using Laravel to write the application in which this query needs to work in.
The two tables needed to write the query are media_files and statuses. There is a link between the 2 tables:
media_files.id = statuses.model_id
Files are stored in media_files and can have two statuses which are pending and attached. The statuses for files are stored in statuses. The statuses table can also contain statuses of other things such as tasks, events, users, etc.
I need a way of getting all the files where the last status for them is pending. Some files may not even have a pending status and these can be ignored.
The statuses table can hold multiple statuses of the same media file. So for example you can have:
Record 1
media_files.id = 1
media_files.name = 'CV document'
statuses.id = 2
statuses.model_id = 1
statuses.model_type = 'App\MediaFile'
statuses.name = 'attached'
statuses.created_at = '2020-06-16 17:39:08'
Record 2
media_files.id = 1
media_files.name = 'CV document'
statuses.id = 1
statuses.model_id = 1
statuses.model_type = 'App\MediaFile'
statuses.name = 'pending'
statuses.created_at = '2020-06-14 17:30:00'
I have made a start on the query but it doesn't seem to be working correctly:
select media_files.*, (
select name
from statuses
where model_id = media_files.id
and model_type = 'App\File'
order by statuses.created_at desc
limit 1
)
as latest_status
from media_files
inner join statuses on statuses.model_id = media_files.id
where statuses.model_type = 'App\Entities\Media\File'
order by media_files.id desc;
You can use conditional aggregation to determine if the last pending status is the last status:
select mf.*
from media_files mf join
(select s.model_id,
max(case when s.status = 'pending' then s.created_at end) as last_pending_created_at,
max(s.created_at) as last_created_at
from statuses s
group by s.model_id
) s
on s.model_id = mf.id
where last_pending_created_at = last_created_at;
You can also use a correlated subquery:
select mf.*
from (select mf.*,
(select s.status
from statuses s
where s.model_id = mf.id
order by s.created_at desc
limit 1
) as last_status
from media_files mf
) mf
where last_status = 'pending';
you can use an analytic function as well
SELECT MEDIA_FILES.*,LATEST_STATUS.*
FROM
(SELECT NAME,MODEL_ID,
MAX(CREATED_AT) OVER(PARTITION BY NAME) AS MAX_TM
WHERE MODEL_ID = MEDIA_FILES.ID
AND MODEL_TYPE = 'App\File') AS LATEST_STATUS,MEDIA_FILES
WHERE LATEST_STATUS.MODEL_ID=MEDIA_FILES.MODEL_ID

MYSQL SELECT is slow when crossing multiple tables

I have a mysql query which is to return the only 1 record that need to cross multiple table. However, the mysql query is slow when executing.
Query:
SELECT *,
(SELECT TreeName FROM sys_tree WHERE TreeId = Mktg_Unit_Booking.ProjectLevelId) AS PhaseName,
(CASE WHEN ProductType = 'U' THEN (SELECT UnitNo FROM prop_unit pu WHERE pu.UnitId = mktg_unit_booking.UnitId)
ELSE (SELECT BayNo FROM prop_car_park pcp WHERE pcp.CarParkId = UnitId) END) AS UnitNo,
(SELECT CustomerName FROM mktg_customer mc WHERE mc.CustomerId = mktg_unit_booking.CustomerId) AS CustomerName
FROM Mktg_Unit_Booking
WHERE IsDeleted <> '1' AND IsApproved = '1'
AND UnitId = 1110 AND ProductType = 'U'
ORDER BY UnitNo
I have run EXPLAIN in the query and I got this:
Any other suggestion on how to improve the speed of the query?
Thank you!
you are doing the cross product, instead of that you should use join.
Don't use sub-queries in select statement instead use proper join on Mktg_Unit_Booking in after from statement.
you query should something look like :
select
sys_tree.TreeName AS PhaseName,
case
WHEN Mktg_Unit_Booking.ProductType = 'U' then prop_unit.UnitNo
else prop_car_park.BayNo
end as UnitNo,
mktg_customer.CustomerName AS CustomerName
FROM Mktg_Unit_Booking
left join sys_tree on sys_tree.TreeId = Mktg_Unit_Booking.ProjectLevelId
left join prop_unit on prop_unit.UnitId = Mktg_Unit_Booking.UnitId
left join prop_car_park on prop_car_park.CarParkId = Mktg_Unit_Booking.UnitId
left join mktg_customer on mktg_customer.CustomerId = Mktg_Unit_Booking.CustomerId
WHERE IsDeleted <> '1' AND IsApproved = '1'
AND UnitId = 1110 AND ProductType = 'U'
ORDER BY UnitNo;
I have assumed that each table consists of only 1 matching tuple. If there are more then your logic needs to be modified.

Should I find correct MySQL query or optimize database structure?

I have database of following structure:
TABLE ingredients (ingredient_name, color)
TABLE recipes (recipe_name)
TABLE recipes_ingredients_parts (recipe_name, ingredient_name, parts)
What I want is to get a recipe that corresponds with selected ingredients and their number. So what I`ve tried first was query:
SELECT rr.* FROM
(SELECT r.* FROM receipes r
INNER JOIN receipes_ingredients_parts ri
ON r.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'espresso'
AND ri.parts_number = '1') rr;
And what I get are {"Americano", "Espresso"}. But that should be "Espresso" only because for "Americano" there should be the query:
SELECT rr.* FROM
(SELECT r.* FROM receipes r
INNER JOIN receipes_ingredients_parts ri
ON r.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'espresso'
AND ri.parts_number = '1') rr
INNER JOIN receipes_ingredients_parts ri
ON rr.receipe_name = ri.receipe_name
AND ri.ingredient_name = 'water'
AND ri.parts_number = '4';
Next my idea was to alter recipe table and add columns for each ingredient to store it's quantity for the recipe. But it would be near 20 columns of that kind. So I'm confused with thought that I'm doing job in a bad style. Maybe I should use some good query for the purpose? Do you guys have any ideas about all the stuff?
I think this is what you are looking for, it should find receipe_names that have all the ingredients in your list, and no other ingredients.
SELECT receipe_name
, SUM(CASE
WHEN (ingredient_name, parts_number) IN (('espresso','1'))
THEN 1 ELSE 0
END
) AS matchedIngredients
, SUM(CASE
WHEN (ingredient_name, parts_number) NOT IN (('espresso','1'))
THEN 1 ELSE 0
END
) AS otherIngredients
FROM receipes_ingredients_parts
GROUP BY receipe_name
HAVING matchedIngredients = 1 AND otherIngredients = 0
A more generalized version/template:
SELECT aField
, SUM(CASE
WHEN someField IN ([matchList])
THEN 1
ELSE 0
END
) AS matches
, SUM(CASE
WHEN someField NOT IN ([matchList])
THEN 1
ELSE 0
END
) AS others
FROM aTable
GROUP BY aField
HAVING matches = [# of values in matchlist]
AND others = 0
Alternatively, if items in the matchlist might be repeated in the table for an "aField" value:
SELECT aField
, COUNT(DISTINCT CASE
WHEN someField IN ([matchList])
THEN someField
ELSE NULL
END
) AS matches
, COUNT(DISTINCT CASE
WHEN someField NOT IN ([matchList])
THEN someField
ELSE NULL
END
) AS others
FROM aTable
GROUP BY aField
HAVING matches = [# of values in matchlist]
AND others = 0