select min value of a field from joins table - mysql

CREATE VIEW products_view
AS
Hi guys ! I've tree tables:
Products
Categories
Prices
A product belongs to one category and may has more prices.
consider this set of data:
Product :
id title featured category_id
1 | bread | yes | 99
2 | milk | yes | 99
3 | honey | yes | 99
Price :
id product_id price quantity
1 | 1 | 99.99 | 10
2 | 1 | 150.00 | 50
3 | 2 | 33.10 | 20
4 | 2 | 10.00 | 11
I need to create a view, a full list of products that for each product select the min price and its own category.
eg.
id title featured cat.name price quantity
1 | bread | yes | food | 99.99 | 10
I tried the following query but in this way I select only the min Price.price value but Price.quantity, for example, came from another row. I should find the min Price.price value and so use the Price.quantity of this row as correct data.
CREATE VIEW products_view
AS
SELECT `Prod`.`id`, `Prod`.`title`, `Prod`.`featured`, `Cat`.`name`, MIN(`Price`.`price`) as price,`Price`.`quantity`
FROM `products` AS `Prod`
LEFT JOIN `prices` AS `Price` ON (`Price`.`product_id` = `Prod`.`id`)
LEFT JOIN `categories` AS `Cat` ON (`Prod`.`category_id` = `Cat`.`id`)
GROUP BY `Prod`.`id`
ORDER BY `Prod`.`id` ASC
My result is:
id title featured cat.name price quantity
1 | bread | yes | food | 99.99 | **50** <-- wrong
Can you help me ? Thx in advance !

As documented under MySQL Extensions to GROUP BY (emphasis added):
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause. For example, this query is illegal in standard SQL because the name column in the select list does not appear in the GROUP BY:
SELECT o.custid, c.name, MAX(o.payment)
FROM orders AS o, customers AS c
WHERE o.custid = c.custid
GROUP BY o.custid;
For the query to be legal, the name column must be omitted from the select list or named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. This means that the preceding query is legal in MySQL. You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
What you are looking for is the group-wise minimum, which can be obtained by joining the grouped results back to the table:
SELECT Prod.id, Prod.title, Prod.featured, Cat.name, Price.price, Price.quantity
FROM products AS Prod
LEFT JOIN categories AS Cat ON Prod.category_id = Cat.id
LEFT JOIN (
prices AS Price NATURAL JOIN (
SELECT product_id, MIN(price) AS price
FROM prices
GROUP BY product_id
) t
) ON Price.product_id = Prod.id
ORDER BY Prod.id

Related

MYSQL How find order products matching current storage products (compare two tables for 100% match)

i'm looking for solution to check if multiple rows from one table have match in other table. In my situation i need to check if items from orders are in storage. Currently I use php to check orders - script is taking open orders and foreach one by one to check storage. It's generating quite a lot of queries and it's not efficient at all and i'm looking for solution to do this via sql query.
Desired result should be:
OrderId | Date | Products
1002/02 | 2020/08/16 | 1x Ipod; 2x battery; 9x some item;
0333/4 | 2020/06/22 | 10x shelf
Storage products table
id | id_product | quantity
Orders
id | reference | id_status | created_at
Order Products
Id | id_order | quantity | id_storage_product
I've written some code to generate table visible above but result it's not even close to desired.
select('orders.id', orders.created_at','orders.reference', 'storage_products.id as storageProductId')
->join('order_products', 'orders.id', '=', 'order_products.id_order')
->join('storage_products', 'order_products.id_product', '=', 'storage_products.id_product')
->where('storage_products.quantity', '>=', 'order_products.quantity')
->whereIn('orders.id_status', array(1, 2)) //get new orders/ open
->where('order_products.id_storage_product', null)
->groupBy('orders.id');
Clean sql:
SELECT `orders`.`id`,
`orders`.`created_at`,
`orders`.`reference`,
`storage_products`.`id` AS `storageProductId`,
`order_products`.`id_order`
FROM `orders`
INNER JOIN `order_products`
ON `orders`.`id` =
`order_products`.`id_order`
INNER JOIN `storage_products`
ON `order_products`.`id_product` =
`storage_products`.`id_product`
WHERE `storage_products`.`quantity` >=
'order_products.quantity'
AND `orders`.`id_status` IN ( 1, 2 )
AND `order_products`.`id_storage_product` IS NULL
GROUP BY `orders`.`id`
ORDER BY `orders`.`id` ASC
So code should find open orders (id_status); where storage quantity is equal or greater than product in order; where id_storage_products is null (means product bought on website but it was not in storage when ordered).
Upper query result is wrong because it showed me partial match to storage - even without checking quantity (some products have 0 but still displayed).
For any help many thanks
EDIT: fiddle sample: https://www.db-fiddle.com/f/6jKvKXPYvsLeXgm3Qv1nHu/0
Your query contains the condition:
AND `order_products`.`id_storage_product` IS NULL
but in your sample data all values are 0.
So instead I use COALESCE() to cover both cases.
Also I removed the condition:
AND `orders`.`id_status` IN ( 1, 2 )
because the column id_status is not included in the definition of the table orders in your sample data.
This query works:
SELECT o.id,
o.reference,
o.created_at,
GROUP_CONCAT(op.quantity, 'x', op.id_product separator ' ;') products
FROM orders o
INNER JOIN order_products op ON o.id = op.id_order
INNER JOIN storage_products sp ON op.id_product = sp.id_product
WHERE sp.quantity >= op.quantity AND COALESCE(op.id_storage_product, 0) = 0
GROUP BY o.id, o.reference, o.created_at
ORDER BY o.id ASC
See the demo.
Results:
| id | reference | created_at | products |
| --- | --------- | ------------------- | ------------- |
| 2 | 345554/02 | 2020-08-22 00:00:00 | 3x188 ; 1x155 |
If you also join the table products (I assume there is such a table) you can get the names of the products instead of their ids.
I tried the following query on the db-fiddle link and this works.
SELECT
orders.reference, orders.created_at, order_products.id_product
FROM
storage_products
LEFT JOIN
order_products ON storage_products.id_product = order_products.id_product
LEFT JOIN
orders ON orders.id = order_products.id_order;
What I did in the query is calling all storage_products with the same id_product in order_products and proceed to call all orders in the called order_products.

MySQL gruop by when there is no aggregation

I have a table called booking_details.
id | tour_id | tour_fee| booking_id
1 | 1 | 200 | 1
2 | 2 | 350 | 1
3 | 1 | 200 | 2
4 | 2 | 350 | 3
tour_id refers to the Tours table and the booking_id refers Bookings table.
I want to get a report like this
tour_id 1 refers to New york tour
tour_id 2 refers to Paris tour
I need a generate a report something like this
tour name | total_income | number_of_bookings
New york tour| 400 | 2
Paris tour | 700 | 2
Here basicaly tour name, total income from that tour and number of bookings for that tour.
What I have done upto now is this. But this gives me a syntax error. It seems I can't group by results.
SELECT booking_details.*,Tours.name as name, count(Tours.id) FROM booking_details
inner join Tours on
booking_details.tour_id = Tours.id group by Tours.name;
How do I achive this using MySQL?
you have used aggregation count() in your query and from your requirement, it shows you need aggregation. when you used aggregation you have to put selection column in group by also
SELECT Tours.name as name,sum(tour_fee) income, count(Tours.id)
FROM booking_details
inner join Tours on
booking_details.tour_id = Tours.id group by Tours.name
As you used in selection booking_details.* which means every column of booking table but you have not put those column in group by so it thrown error
You are trying to select non aggregated columns which are not part of your GROUP BY clause.
Change your query like following.
SELECT t.NAME AS NAME,
Sum(bd.tour_fee) total_income,
Count(t.id) number_of_bookings
FROM booking_details bd
INNER JOIN tours t
ON bd.tour_id = t.id
GROUP BY t.NAME;
Small suggestion, as a good practice you should use alias names for tables when joining.
You need to add all other columns in group by except aggregated fields
SELECT
booking_details.tour_id,
Tours.name AS name,
SUM(tourfee) AS total_income,
COUNT(Tours.id)
FROM
booking_details
INNER JOIN
Tours ON booking_details.tour_id = Tours.id
GROUP BY
booking_details.tour_id, Tours.name

Mysql get MIN price, not zero, not empty

I want to get MIN price from the below tables using RIGHT JOIN and WHERE price not equal to zero and not empty based on user id. How I can get single record with MIN price based on user id in single MYSQL query.
Here is my query with just right join.
SELECT *
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1'
table name: search
search | search_id | user_id | datetime
1 | 1 | 1 | -
table name: offer
offer_id | search_id | price
1 | 1 |
2 | 1 | 0
3 | 1 | 506.1
4 | 1 | 285.3
Query will be :
SELECT *
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1' AND search.price > 0
ORDER BY search.price ASC LIMIT 1
An alternative using a different join condition:
SELECT MIN(o.price) min_price
FROM search s
JOIN offers o ON (
s.search_id = o.search_id
AND o.price IS NOT NULL
AND o.price > 0
)
WHERE s.user_id = '1'
When you want to select the minimal price, you can use the MYSQL "MIN" function. For this function you need a GROUP BY in your query.
Something like this, just edit it to your requirements.
SELECT *, MIN(price) as `minPrice`
FROM SEARCH
RIGHT JOIN offers ON search.search_id=offers.search_id
WHERE search.user_id='1' AND search.price > 0
GROUP BY search.search_id

SQL, Find orders where strict critera is met, Match item, not if other items purchased

I have stumped all the IT people at my work with this one, so wondeirng if anyone can help.
I need to extract from an order table anyone who has only purchased a specific product type, (if they have order the product type and any other product types i dont want to know who you are)
for example the table is roughly
---------------------------------------------------------------------------------------
Order ID | item code | Name |
----------------------------------------------------------------------------------------
1 | ADA | item 1
2 | ADA | item 1
2 | GGG | item 2
3 | ADA | item 1
----------------------------------------------------------------------------------------
So i want to find all the order IDs of people who only purchased item code ADA, BUT not if they purchased over items, so the output of this query should be order ID 1 & 3 and skipping order 2 as this had a different item.
Would really appriciate it if anyone could help.
Assuming an order can't have multiple records with the same ItemCode, you could use:
SELECT *
FROM Orders
WHERE OrderID IN (
SELECT OrderID
FROM Orders
GROUP BY OrderID HAVING COUNT(*) = 1
)
AND ItemCode = 'ADA'
If an order could have multiple records with the same ItemCode then you'd have to change the SELECT * to SELECT DISTINCT * and then COUNT(*) to COUNT(DISTINCT ItemCode)
Based on your current explanation and example, the below should work. However, there are outstanding questions in the comments which may change the actual correct solution.
SELECT
O.OrderId, MAX(itemCode), MAX(Name)
FROM
Orders O
INNER JOIN
(SELECT
OrderId
FROM
Orders
WHERE
itemCode = 'ADA') ADA
ON
O.OrderId = ADA.OrderId
GROUP BY
O.OrderId
HAVING
COUNT(*) = 1

MySQL: Transfer Data Based on a Column Without Also Transferring That Column

My table stores revision data for my CMS entries. Each entry has an ID and a revision date, and there are multiple revisions:
Table: old_revisions
+----------+---------------+-----------------------------------------+
| entry_id | revision_date | entry_data |
+----------+---------------+-----------------------------------------+
| 1 | 1302150011 | I like pie. |
| 1 | 1302148411 | I like pie and cookies. |
| 1 | 1302149885 | I like pie and cookies and cake. |
| 2 | 1288917372 | Kittens are cute. |
| 2 | 1288918782 | Kittens are cute but puppies are cuter. |
| 3 | 1288056095 | Han shot first. |
+----------+---------------+-----------------------------------------+
I want to transfer some of this data to another table:
Table: new_revisions
+--------------+----------------+
| new_entry_id | new_entry_data |
+--------------+----------------+
| | |
+--------------+----------------+
I want to transfer entry_id and entry_data to new_entry_id and new_entry_data. But I only want to transfer the most recent version of each entry.
I got as far as this query:
INSERT INTO new_revisions (
new_entry_id,
new_entry_data
)
SELECT
entry_id,
entry_data,
MAX(revision_date)
FROM old_revisions
GROUP BY entry_id
But I think the problem is that I'm trying to insert 3 columns of data into 2 columns.
How do I transfer the data based on the revision date without transferring the revision date as well?
You can use the following query:
insert into new_revisions (new_entry_id, new_entry_data)
select o1.entry_id, o1.entry_data
from old_revisions o1
inner join
(
select max(revision_date) maxDate, entry_id
from old_revisions
group by entry_id
) o2
on o1.entry_id = o2.entry_id
and o1.revision_date = o2.maxDate
See SQL Fiddle with Demo. This query gets the max(revision_date) for each entry_id and then joins back to your table on both the entry_id and the max date to get the rows to be inserted.
Please note that the subquery is only returning the entry_id and date, this is because we want to apply the GROUP BY to the items in the select list that are not in an aggregate function. MySQL uses an extension to the GROUP BY clause that allows columns in the select list to be excluded in a group by and aggregate but this could causes unexpected results. By only including the columns needed by the aggregate and the group by will ensure that the result is the value you want. (see MySQL Extensions to GROUP BY)
From the MySQL Docs:
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. ... You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values the server chooses.
If you want to enter the last entry you need to filter it before:
select entry_id, max(revision_date) as maxDate
from old_revisions
group by entry_id;
Then use this as a subquery to filter the data you need:
insert into new_revisions (new_entry_id, new_entry_data)
select entry_id, entry_data
from old_revisions as o
inner join (
select entry_id, max(revision_date) as maxDate
from old_revisions
group by entry_id
) as a on o.entry_id = a.entry_id and o.revision_date = a.maxDate