subquery error in group by when converting from MySQL to PostgresSQL - mysql

I'm converting my database from MySQL to PostgresSQL, and I have this query which tries to sord the products by the cheapest price and the most popular in a given location. It works fine in MySQL, but in Postgres I'm running into problems with this query :
SELECT products.product_id,
suppliers.supplier_id,
product_code.desc_fa,
products.name_fa,
MIN(product_supplier.price) AS price,
SUM(COALESCE(orders.quantity, 0)) AS n_orders
FROM products
JOIN product_code ON product_code.code_id = products.code_id
JOIN product_supplier ON product_supplier.product_id = products.product_id
JOIN suppliers ON suppliers.supplier_id = product_supplier.supplier_id
JOIN product_tags ON product_tags.product_id = products.product_id
JOIN tags ON tags.tag_id = product_tags.tag_id
JOIN product_crop ON product_crop.product_id = products.product_id
JOIN crops ON crops.crops_id = product_crop.crop_id
LEFT JOIN orders
ON orders.product_id = products.product_id and orders.crop_id = product_crop.crop_id
LEFT JOIN user ON user.user_id = orders.user_id and user.location_id = 883
WHERE crops.crops_id = 1
AND product_supplier.quantity >= 3
AND tags.tag = 'علف کش'
GROUP BY products.name_fa
ORDER BY n_orders DESC
LIMIT 10;
It gives me this error :
column must appear in the GROUP BY clause or be used in an aggregate function
Any suggestions to how to work around this error ?
UPDATE :
According to the answers i was able to make it work by using this query:
WITH tem_1 AS (SELECT product_id, MIN(price) AS price FROM product_supplier GROUP BY product_id) ,
tem_2 AS (SELECT product_id, SUM(quantity) AS n_orders FROM orders Group by product_id)
SELECT products.product_id, suppliers.supplier_id, product_code.desc_fa, products.name_fa, tem_1.price,
products.telegraph, suppliers.location_id, COALESCE(tem_2.n_orders,0) AS quant FROM products
INNER JOIN product_supplier ON product_supplier.product_id = products.product_id
INNER JOIN suppliers ON suppliers.supplier_id = product_supplier.supplier_id
INNER JOIN product_code ON product_code.code_id = products.code_id
INNER JOIN product_crop ON product_crop.product_id = products.product_id
INNER JOIN crops ON crops.crops_id = product_crop.crop_id
INNER JOIN product_tags ON product_tags.product_id = products.product_id
INNER JOIN tags ON tags.tag_id = product_tags.tag_id
INNER JOIN tem_1 ON tem_1.price = product_supplier.price AND tem_1.product_id = products.product_id
LEFT JOIN tem_2 ON tem_2.product_id = products.product_id
WHERE crops.crops_id = 1 AND product_supplier.quantity >= 3 AND tags.tag = 'علف کش'
ORDER BY quant DESC
LIMIT 10;;
But since i'm fairly new to SQL, I wanted to know if my code is correct or is there a better way to implement it?

When you use aggregate function (as SUM, MIN and others) the other columns in your field list without aggregate function must be included in GROUP BY clause.
These fields:
products.product_id,
suppliers.supplier_id,
product_code.desc_fa,
products.name_fa
must be in GROUP BY.
Instead your GROUP BY clause has only the following field:
products.name_fa
You must add the other missing 3 fields
In MySql this error has turned off by default, so your query works fine, but in other DBMS you are in an error case.
You can see here how set MySql environment about the GROUP BY behaviour

Related

MySQL - how to join row with minimal value only once

Im trying to get row with minimal value for each product. In a simple database. I will outline below. I studied a lot of answers and nothing works unfortunately.
Ive tried some ways of doing this, like self join and it works fine except on the same values it returns doubling values.
SELECT
products.id,
products.name,
prices.price,
prices.price_init
FROM products
LEFT JOIN prices ON prices.product_id = products.id
JOIN(
SELECT products.id, min(prices.price) as min_price FROM products
LEFT JOIN prices ON prices.product_id = products.id
GROUP BY products.id
) as min_prices
ON
min_prices.id = products.id
AND
min_prices.min_price = prices.price
This query works fine, but doubles the results.
link to sqlfiddle:
http://sqlfiddle.com/#!9/d106d9a/1
As you can see I need to take price_init field for minimal price.
Thanks.
I you want only one row per product -- even when there are multiple minimum prices -- you can filter by the id rather than the price.
This looks like
SELECT p.id, p.name, pr.price, pr.price_init
FROM products p LEFT JOIN
prices pr
ON pr.product_id = p.id AND
pr.id = (SELECT pr2.id
FROM prices pr2
WHERE pr2.product_id = pr.product_id
ORDER BY pr2.price
LIMIT 1
);
Here is a SQL Fiddle.
Note in MySQL 8+, you would instead use window functions:
SELECT p.id, p.name, pr.price, pr.price_init
FROM products p LEFT JOIN
(SELECT pr.*,
ROW_NUMBER() OVER (PARTITION BY pr.product_id ORDER BY pr.price) as seqnum
FROM prices pr
) pr
ON pr.product_id = p.id AND pr.seqnum = 1;

SQL Server - (AdventureWorks) List the Vendors with no products

I've been given a question using AdventureWorks to list all the vendors with no products. When I run my SELECT statement nothing is returned. What am I doing wrong? (Answer can only be done using Joins and Unions - No Subqueries)
SELECT DISTINCT pv.Name AS 'Vendors'
FROM Purchasing.Vendor pv
INNER JOIN Purchasing.ProductVendor ppv
ON pv.BusinessEntityID = ppv.BusinessEntityID
INNER JOIN Production.Product pp
ON pp.ProductID = ppv.ProductID
WHERE pp.ProductID != ppv.ProductID;
You're looking at one too many tables, all ProductVendors have Products. Not all Vendors have ProductVendors.
From there you can simply use a LEFT JOIN and look for null records.
SELECT DISTINCT v.Name
FROM Purchasing.Vendor v
LEFT JOIN Purchasing.ProductVendor pv ON pv.BusinessEntityID = v.BusinessEntityID
WHERE pv.BusinessEntityID IS NULL
Use left join to include cases where there is no product for a "vendor". Inner join will consider only those cases, where a product id exists for a vendor.
Now, do grouping on a "Vendor" and count the number of products using COUNT() function.
Finally, filter out those vendors where count is zero, using HAVING clause
Try the following:
SELECT pv.Name AS 'Vendors',
Count(pp.ProductID) AS count_products
FROM Purchasing.Vendor pv
LEFT JOIN Purchasing.ProductVendor ppv
ON pv.BusinessEntityID = ppv.BusinessEntityID
LEFT JOIN Production.Product pp
ON pp.ProductID = ppv.ProductID
GROUP BY pv.Name
HAVING count_products = 0;
SELECT DISTINCT pv.Name AS 'Vendors'
FROM Purchasing.Vendor pv
INNER JOIN Purchasing.ProductVendor ppv
ON pv.BusinessEntityID = ppv.BusinessEntityID
where not exists (SELECT 1 Production.Product pp Where pp.ProductID = ppv.ProductID)
Return all vendors wich donot have any product

Filter products by options

I have following database structure to store product options.
Now i have problem to filter out products that match only given options. First i did WHERE option_id IN (array of options), but that would give me products that match any of the options and that is not solution. User wants to filter out only products with given material, color, and size for instance. And if i do WHERE option_id = 4 AND option_id = 6 for instance i get nothing.
Here is my query:
SELECT DISTINCT p.id AS id,
...
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id = 1 // only to get the idea
GROUP BY id
ORDER BY id DESC
LIMIT 0,
12
Just to mention it is PHP application , where user select options from select element with or without multiple attribute.
How to acomplish this?
You can use having:
SELECT p.id AS id, ...
FROM products p JOIN
product_categories pc
ON p.id = pc.product_id LEFT JOIN
product_images pi
ON p.id = pi.product_id JOIN
product_options po
ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (4, 6)
GROUP BY p.id
HAVING COUNT(DISTINCT po.option_id) = 2
ORDER BY p.id DESC
LIMIT 0, 12;
The HAVING clause is specifying that a given id has two matching options. Because of the WHERE clause, these are the only two options that you care about.
I didn't change your approach (you didn't supply the complete query), but you are doing joins along different dimensions -- categories, images, and options. This creates a Cartesian product for each product, and that is often not the best approach to such a query.
There is no need for LEFT JOIN in the solution.
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN (1, 2, 3)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
My solution keep only tables necessary to find the products with specified options.
In the case you want products having exactly this options and no others you can use NOT EXISTS:
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (1, 2, 3) and
NOT EXISTS (
SELECT 1
FROM product_options po2
WHERE p.id = po2.product_id and po2.option_id NOT IN (1, 2, 3)
)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
If you want to select products accoding to the other conditions (like product categories and so on) then use IN in the WHERE clause. This approach avoids generating duplicate po.option_id and the outer query will still work correctly even without DISTINCT in COUNT.
SELECT DISTINCT p.id AS id
FROM products p
JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1 AND
po.option_id IN (1, 2, 3) AND
-- use the following IN predicate to select products with specific features without introducing duplicates in your query
p.id IN (
select product_id FROM product_categories WHERE <your_condition>
)
GROUP BY p.id
HAVING COUNT(po.option_id) = 3
You select products with image lists. Something like:
select products.*, group_concat(product_images.id)
Additionally there may be options the product must all meet. This is criteria that belongs in the WHERE clause.
select
p.*,
(select group_concat(image) from product_images i where i.product_id = p.id) as images
from products p
where product_active = 1
and id in
(
select product_id
from product_options
where option_id in (1,3,55,97)
group by product_id
having count(*) = 4 -- four options in this example
);
Thanks guys, i've managed to return exactly what i wanted.
Now i just have problem with pagination query for the filtered products.
Final search query:
SELECT DISTINCT p.id AS id,
main_price,
promotion_price,
NEW,
sale,
recommended,
COUNT(pi.filename) AS image_count,
GROUP_CONCAT(DISTINCT pi.filename
ORDER BY pi.main_image DESC, pi.id ASC) AS images,
name_sr,
uri_sr,
description_sr
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN(1)
AND p.main_price BETWEEN 5250.00 AND 14000.00
GROUP BY id
HAVING COUNT(DISTINCT po.option_id) = 1
ORDER BY id DESC
LIMIT 0,
12
Pagination query is something like this i modified it accorgin to new filter query:
SELECT COUNT(DISTINCT p.id) AS number
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN product_images pi ON p.id = pi.product_id
LEFT JOIN product_options po ON p.id = po.product_id
WHERE p.product_active = 1
AND po.option_id IN(1)
AND p.main_price BETWEEN 5250.00 AND 14000.00
GROUP BY(p.id)
HAVING COUNT(DISTINCT po.option_id) = 1
If i leave out DISTINCT in SELECT COUNT i don't get filtered pagination, if i set DISTINCT i get number of rows that corespond to pagination. I suppose i could add another count(*) to all of this with subquery, but not sure if that is way to go and if there is more efficient and elegant way to do this.

Using cases to determine which table should join

I have four tables products, product_histories, vendor_invoices and invoices
This is the query I have developed
SELECT p.product_id, product_name, vendor_name FROM products AS p
INNER JOIN product_histories AS ph ON p.product_id = ph.product_id
CASE
WHEN ph.history_type = "P" THEN
LEFT JOIN vendor_invoices AS vi ON link_id = vi.vi_id
WHEN ph.history_type = "S" THEN
LEFT JOIN invoices AS i ON i.invoice_id = link_id
END
ORDER BY ph_id ASC
What I want that if ph.history_type is P then is should join vendor_invoices and if it is S then it should join invoices. But it says there is a syntax error.
Can anyone help me out with it? Or could show a better way to achieve this problem.

mysql #1242 - Subquery returns more than 1 row

This is for my thesis and the dead end is later i don't know what i do wrong here .. Im hoping that someone can help me to know what's wrong here thanks
SELECT
flower_id,
flower_name,
flower_description,
flower_price,
flower_category,
(quantity - (SELECT
SUM(q.quantity_value)
FROM
orders_details od
INNER JOIN
cart_details cd ON cd.cart_id = od.cart_id
INNER JOIN
quantities q ON q.quantity_id = cd.quantity_id
WHERE
od.flag = 1 AND cd.flower_id = flower_id
GROUP BY cd.flower_id)) AS 'quantity',
mfg_date,
exp_date
FROM
flower_details,
categories
WHERE
flower_details.flower_category = categories.category_id
What im doing here is getting the total quantity of products from customer bought minus to inventory stocks
If your subselect return more then a rows you should join the sum using an inner join on subselect
If your subselect return more then a rows you should join the sum using an inner join on subselect inner join on subselect
SELECT
flower_details.flower_id,
flower_name,
flower_description,
flower_price,
flower_category,
flower_details.quantity - t1.quantity,
mfg_date,
exp_date
FROM flower_details
INNER JOIN categories ON flower_details.flower_category = categories.category_id
INNER JOIN (
SELECT cd.flower_id ,
SUM(q.quantity_value) AS quantity
FROM
orders_details od
INNER JOIN
cart_details cd ON cd.cart_id = od.cart_id
INNER JOIN
quantities q ON q.quantity_id = cd.quantity_id
WHERE
od.flag = 1 AND cd.flower_id = flower_id
GROUP BY cd.flower_id
) t1 on flower_details.flower_id = t1.flower_id