MySQL GROUBY counts is Inconsistent - mysql

I was trying to find out product counts with GROUP_BY on name
However These 2 queries confused me a bit.
How can the count of distinct products (Which is 204 -> Query-A) be higher than actual number of products (Which is single -> Query-B)?
Query-A
SELECT an.name
, COUNT(DISTINCT product.productid)
FROM product
JOIN productkeywords pk
ON product.productid = pk.productid
JOIN categorydisplayattributes cda
ON product.categoryid = cda.categoryid
JOIN attributenames an
ON cda.attributeid = an.attributeid
AND an.name = 'Number of Fixed Shelves'
WHERE pk.keywords LIKE '%mouse%'
GROUP
BY an.name
LIMIT 100
Output-A
name , COUNT(DISTINCT product.productid)
-------------------------+-------
'Number of Fixed Shelves', '204'
Query-B
SELECT an.name, product.productid FROM product
JOIN productkeywords pk ON product.productid = pk.productid
JOIN categorydisplayattributes cda ON product.categoryid = cda.categoryid
JOIN attributenames an ON cda.attributeid = an.attributeid AND an.name = 'Number of Fixed Shelves'
WHERE pk.keywords LIKE '%mouse%'
GROUP BY an.name
LIMIT 100
Output-B
name , productid
-------------------------+--------------
'Number of Fixed Shelves', '1025794284'

By adding the productid to the GROUP BY you ensure one output row per name/product combination.
By leaving it out you were only get one row per name. (And so it was being forced to pick one arbitrary productid from the list of 204 possible values.)
SELECT an.name, product.productid
FROM product
JOIN productkeywords pk ON product.productid = pk.productid
JOIN categorydisplayattributes cda ON product.categoryid = cda.categoryid
JOIN attributenames an ON cda.attributeid = an.attributeid
AND an.name = 'Number of Fixed Shelves'
WHERE pk.keywords LIKE '%mouse%'
GROUP BY an.name, product.productid
(You also want to remove the LIMIT if you want all 204 rows.)

Related

Get a specific combination from product with given attributes in Prestashop

I building a custom SQL query for my module to retrieve all combinations of a product with id_product and multiple attributes ids, but currently, I only managed to select it with one attribute and no more, I'm really missing something but didn't find it yet.
To get in context here's my query to find all combinations (color & size) of a product and its result:
SELECT
p.id_product,
pq.quantity,
pa.price AS price_diff,
p.price,
pai.id_image,
pl.name,
GROUP_CONCAT(agl.id_attribute_group, ':', pal.id_attribute ORDER BY agl.id_attribute_group SEPARATOR ", ") as combination_ids,
GROUP_CONCAT(pal.name ORDER BY agl.id_attribute_group SEPARATOR ", ") as combination
FROM ps_product p
LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product)
LEFT JOIN ps_stock_available pq ON (p.id_product = pq.id_product AND pa.id_product_attribute = pq.id_product_attribute)
LEFT JOIN ps_product_lang pl ON (p.id_product = pl.id_product)
LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute)
LEFT JOIN ps_attribute_lang pal ON (pac.id_attribute = pal.id_attribute)
LEFT JOIN ps_attribute a ON (pal.id_attribute = a.id_attribute)
LEFT JOIN ps_attribute_group_lang agl ON (a.id_attribute_group = agl.id_attribute_group)
LEFT JOIN ps_product_attribute_image pai on(pa.id_product_attribute = pai.id_product_attribute)
WHERE pl.id_lang = 1
AND pal.id_lang = 1
AND agl.id_lang = 1
AND p.id_product = 3196 -- My product
GROUP BY pac.id_product_attribute
The result
Query with a single attribute (size S for this example):
......................
......................
AND p.id_product = 3196 -- My product
AND agl.id_attribute_group = 9 -- size
AND pal.id_attribute = 761 -- 'S' size for my case
GROUP BY pac.id_product_attribute
But no success with specifying both size AND color, any idea?
I think you want a HAVING clause. To filter on two attributes, the logic would be:
SELECT ...
FROM ...
WHERE ...
GROUP BY pac.id_product_attribute
HAVING
MAX(agl.id_attribute_group = 9 AND pal.id_attribute = 761) = 1
AND MAX(agl.id_attribute_group = 2 AND pal.id_attribute = 727) = 1
I should warn that your code is not a valid aggregation query. You need more column in the GROUP BY clause to fix that flaw. It is hard to tell for sure without seeing your data, but, with a few assumptions on the primary key of each table:
GROUP BY
p.id_product,
pa.id_product_attribute,
pac.id_product_attribute,
pai.id_image,
pq.id -- if that exists?

Sql trouble with coalesce() not working propely

i have a query and i'm having trouble to change the name of the last row of columb name to 'TOTAL'. The result gives me the same name of the row above the last row.
Here's my query:
SELECT COALESCE(ticket_types.name,'TOTAL') AS name,
COUNT(1) AS quantity
FROM tr_logs
LEFT JOIN tickets ON tr_logs.value = tickets.id
LEFT JOIN ticket_types ON tickets.ticket_type_id = ticket_types.id
LEFT JOIN transactions ON tr_logs.transaction_id = transactions.id
LEFT JOIN tr_fields_data AS tfd_shipping ON tfd_shipping.transaction_id = transactions.id
WHERE type = 'ADDITEM'
AND transactions.event_id = '46'
AND DATE(tr_logs.created_date)
BETWEEN '2017-03-26' AND '2017-05-24'
AND tfd_shipping.data IN ('0','570','571','771')
AND name IS NOT NULL
GROUP BY ticket_types.id WITH ROLLUP
The result looks like this:
name quantity
premium 56
outlaw 6
outlaw 62
Last row name from rollup is not null.... I need it to be TOTAL and not outlaw
Thanks
You haven't changed the name to TOTAL at all: you've changed the name of the column to name, and you've told it to replace any null values with TOTAL.
If you want to change the name of ticket_types.name to total, you just want
SELECT ticket_types.name AS total ...
(But it would be weird to rename something called name to total, so perhaps you need to clarify your requirements a little.)
This may or not be related to your observed problem, but the WHERE and GROUP BY clauses turn all the outer joins into inner joins. You should simplify the query to:
SELECT COALESCE(tt.name, 'TOTAL') AS name, COUNT(1) AS quantity
FROM tr_logs l JOIN
tickets
ON l.value = t.id JOIN
ticket_types tt
ON t.ticket_type_id = tt.id JOIN
transactions tr
ON l.transaction_id = tr.id JOIN
tr_fields_data fd
ON fd.transaction_id = tr.id
WHERE type = 'ADDITEM' AND
tr.event_id = '46' AND
DATE(l.created_date) BETWEEN '2017-03-26' AND '2017-05-24' AND
fd.data IN ('0', '570', '571', '771') AND
tt.name IS NOT NULL
GROUP BY tt.id WITH ROLLUP
Thanks to Gordon Linoff I have figure out my problem.
The name of the last row was never null beacause i GROUP BY with a different attribute.
Here's the solution.
SELECT COALESCE(tckn,'TOTAL') AS name, quantity FROM
(SELECT tt.name AS tckn, COUNT(1) AS quantity
FROM tr_logs AS l
LEFT JOIN tickets AS t ON l.value = t.id
LEFT JOIN ticket_types AS tt ON t.ticket_type_id = tt.id
LEFT JOIN transactions AS tr ON l.transaction_id = tr.id
LEFT JOIN tr_fields_data AS tfd ON tfd.transaction_id = tr.id
WHERE type = 'ADDITEM'
AND tr.event_id = '46'
AND DATE(l.created_date)
BETWEEN '2017-03-26' AND '2017-05-24'
AND tfd.data IN ('0','570','571','771')
GROUP BY tckn WITH ROLLUP) as sum;

SQL query left join where clause

This is my situation.
I have 3 tables
Orders
- id status deleted
Order Lines
- related_id related_model quantity
Products
- id code price price_purchase
I want to create a list with all products. The amount of times they are purchased and a sum of the gross margin (price - price_purchase). It must only use orders lines with the related model set to 'products'. And secondly it must only pick orders with the status set to 'paid, processing, sent, ready_for_pickup or picked_up' and with the order not deleted.
So this would be the result I want:
id | code | purchases | value
-------------------------------
1 | code1 | 7 | 57,05
2 | code2 | 122 | 254,98
3 | code3 | 0 | 0,00
This is the SQL query I have so far:
SELECT p.id, p.code, IFNULL(SUM(sol.quantity) , 0) as purcahses,
sum((p.price - p.price_purchase) * quantity) as value
FROM products p
LEFT JOIN shop_orders_lines sol ON sol.related_id = p.id
AND sol.related_model = 'products'
LEFT JOIN shop_orders so ON so.id = sol.order_id
WHERE so.status IN ('paid', 'processing', 'sent', 'ready_for_pickup', 'picked_up')
AND so.deleted = 0
GROUP BY p.id
It returns the correct data. But not all problems. That is my problem. I a lot of different methods like sub queries and other methods but can't seem to solve the problem. I know the problem is my LEFT join, but don't know a solution to my problem.
I'm using MySQL Workbench.
Any help is welcome.
Your joins are wrong. You need to identify the order lines to consider separately from and prior to forming the LEFT JOIN with the product details. An inline view could help:
SELECT
p.id,
p.code,
IFNULL(SUM(ordered_item.quantity) , 0) as purchases ,
sum((p.price - p.price_purchase) * ordered_item.quantity) as value
FROM
products p
LEFT JOIN (
SELECT
sol.related_id AS related_id,
sol.quantity AS quantity
FROM
shop_orders_lines sol
INNER JOIN shop_orders so
ON so.id = sol.order_id
WHERE
so.status IN ('paid', 'processing', 'sent', 'ready_for_pickup', 'picked_up')
AND so.deleted = 0
AND sol.related_model = 'products'
) ordered_item
ON ordered_item.related_id = p.id
GROUP BY p.id
Move outer table conditions from WHERE to ON, otherwise the OUTER JOIN works like a regular INNER JOIN:
SELECT p.id, p.code, IFNULL(SUM(sol.quantity) , 0) as purcahses,
sum((p.price - p.price_purchase) * quantity) as value
FROM products p
LEFT JOIN shop_orders_lines sol ON sol.related_id = p.id
AND sol.related_model = 'products'
LEFT JOIN shop_orders so ON so.id = sol.order_id AND
so.status IN ('paid', 'processing', 'sent', 'ready_for_pickup', 'picked_up')
AND so.deleted = 0
GROUP BY p.id
Is p.id the whole primary key for that table? If not, you need to find out how to treat p.code. (Either list in GROUP BY, or use as argument to aggregate function.)
Another try:
SELECT p.id, p.code, IFNULL(SUM(sol.quantity) , 0) as purcahses,
sum((p.price - p.price_purchase) * quantity) as value
FROM products p
JOIN shop_orders_lines sol ON sol.related_id = p.id
AND sol.related_model = 'products'
WHERE EXISTS (select 1 from shop_orders so
where so.id = sol.order_id
AND so.status IN ('paid', 'processing', 'sent', 'ready_for_pickup', 'picked_up')
AND so.deleted = 0)
GROUP BY p.id

How do I do a partial match on an IN statement in MySQL

I am joining multiple tables into a single query. I need to do a partial match on values in an IN statement. Here is an example.
SELECT DISTINCT
am.id AS id,
am.flagged AS flagged,
am.name AS name,
am.type AS type,
am.file AS file,
am.s3_tag AS s3_tag,
am.low_s3_tag AS low_s3_tag
FROM accounts_media am
LEFT JOIN accounts_location_media alm ON am.id = alm.media_id
LEFT JOIN accounts_location al ON al.id = alm.location_id
LEFT JOIN accounts_person_media apm ON am.id = apm.media_id
LEFT JOIN accounts_person ap ON ap.id = apm.person_id
LEFT JOIN accounts_event_media_record aemr ON am.id=aemr.media_id
LEFT JOIN accounts_medianote_media_record amma ON am.id=amma.media_id
LEFT JOIN accounts_medianote amn ON amma.medianote_id=amn.id
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
AND ('Da' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
AND ('Rob' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
In the
AND ('Da' IN (SELECT first_name FROM accounts_person WHERE account_id = '1234')
statement there are values that say 'Dan', 'Daniel', etc. in the table. There is also 'Rob' and 'Robert'. I need that statement to make sure and name that contains 'Da' AND any name that contains 'Rob' from that table. Is there a way to do this?
So a record can be linked to multiple people in the accounts_person table. So lets say I have three records.
Record One: A person named Dan is attached to the record.
Record Two: A person named Robert is attached to the record.
Record Three: A person named Dan and a person named Robert are attached to the record.
I want the query to only return Record Three because it has the match of 'Da' and 'Rob'.
Try this:
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
-- AND ( ap.first_name LIKE '%Da%' OR ap.first_name LIKE '%Rob%')
AND EXISTS
( SELECT 1
FROM accounts_person apx
WHERE apx.first_name LIKE '%Da%'
AND apx.account_id = am.account_id
)
AND EXISTS
( SELECT 1
FROM accounts_person apy
WHERE apy.first_name LIKE '%Rob%'
AND apy.account_id = am.account_id
)
I'm not sure, but I think you want a like statement, with a wild card after the Da.
SELECT DISTINCT
am.id AS id,
am.flagged AS flagged,
am.name AS name,
am.type AS type,
am.file AS file,
am.s3_tag AS s3_tag,
am.low_s3_tag AS low_s3_tag
FROM accounts_media am
LEFT JOIN accounts_location_media alm ON am.id = alm.media_id
LEFT JOIN accounts_location al ON al.id = alm.location_id
LEFT JOIN accounts_person_media apm ON am.id = apm.media_id
LEFT JOIN accounts_person ap ON ap.id = apm.person_id
LEFT JOIN accounts_event_media_record aemr ON am.id=aemr.media_id
LEFT JOIN accounts_medianote_media_record amma ON am.id=amma.media_id
LEFT JOIN accounts_medianote amn ON amma.medianote_id=amn.id
WHERE
am.account_id = '1234'
AND am.flagged = FALSE
AND (accounts_person.first_name like '%Da%'
OR accounts_person.first_name like '%Rob%')
AND accounts_person.account_id = '1234'

selecting the least value in a joined table

Basically I have product and several models for those products. Each model has a price.
This is what I intended to do:
Mark a product as featured, then have it's title, description, number 1 image's thumbnail and the price for the cheapest model
This is my current query:
SELECT
product.title,
product.url_name,
product.description,
price.price,
image.thumbnail
FROM
mps_contents AS product
LEFT OUTER JOIN
mps_contents AS image
ON
image.page_id = product.content_id AND
image.display_order = '1' AND
image.resource_type = 'image'
LEFT OUTER JOIN
mps_contents AS model
ON
product.content_id = model.page_id
INNER JOIN
mps_product_info AS price
ON
model.content_id = price.content_id
WHERE
product.active = '1' AND
product.resource_type = 'product' AND
product.featured = '1'
ORDER BY RAND( )
LIMIT 3
You may see that my query cannot do the price sorting, I hope somebody could help me with that. An additional problem that I encounter is if I have multiple models for a product. I end up getting a set that has prices for 2 models from a single product when the intent is to have the 1 price for each product.
I am aware of the issue with ORDER BY RAND() but I will ignore it since I don't think this site will have more that 50 products.
I think something like this should work....
SELECT
product.title,
product.url_name,
product.description,
A.price,
image.thumbnail
FROM
mps_contents AS product
LEFT OUTER JOIN
mps_contents AS image
ON
image.page_id = product.content_id AND
image.display_order = '1' AND
image.resource_type = 'image'
LEFT OUTER JOIN (
SELECT price.price
FROM mps_contents AS model
JOIN mps_product_info price ON (model.content_id = price.content_id)
WHERE model.page_id = product.content_id
ORDER BY price.price
LIMIT 1
) AS A
WHERE
product.active = '1' AND
product.resource_type = 'product' AND
product.featured = '1'
ORDER BY RAND( )
LIMIT 3