MySQL: How to select min() using subquery and joins - mysql

I have 3 MySQL tables. I've trimmed them down to show just the important columns
items
id | title |
1 | Hamlet |
2 | Romeo and Juliet |
3 | The Merchant of Venice |
listings
id | item_id | condition |
1 | 1 | Mint |
2 | 1 | Used - Good |
3 | 2 | New |
4 | 2 | Mint |
5 | 2 | Used - Poor |
6 | 3 | Used - Poor |
7 | 3 | Used - Good |
8 | 3 | Used - Good |
itemListings_variations
id | listing_id | price
1 | 1 | 20.00
2 | 1 | 10.00
3 | 1 | 5.00
4 | 2 | 6.00
5 | 2 | 5.50
6 | 3 | 2.00
7 | 4 | 12.00
8 | 5 | 3.00
9 | 6 | 9.00
If e.g. I search for Romeo and Juliet, it should return:
item_id | title | min_price
2 | Romeo and Juliet | 2.00
So far I have two queries which somehow need combining via subqueries and joins. I have:
SELECT
i.id AS item_id,
i.title
FROM items i
WHERE MATCH (i.title) AGAINST (:search)
also:
SELECT
l.item_id,
v.listing_id,
MIN(price) AS min_price
FROM itemListings_variation v, itemListings l
WHERE v.listing_id = l.id
GROUP BY v.listing_id

Try this -
SELECT i.id, i.title, MIN(price)
FROM items i
INNER JOIN listings l ON i.items = l.item_id
INNER JOIN itemListings_variations il ON l.id = il.listing_id
GROUP BY i.id, i.title
Hope this helps.

No group by is needed as calling min(price) will return the row with the lowest price.
select t1.id, title, min(price) from
(select * from items where title = 'Romeo and Juliet' ) as t1
left join listings on t1.id = listings.item_id
left join itemlistings_variations on listing_id = listings.id;
Tested on my test server.

Related

MySQL: Finding the most efficient use of INNER JOIN with subquery

I have a working query using INNER JOIN and a subquery but was wondering if there is a more effient way of writing it.
with prl
as
(
SELECT `number`, creator, notes
FROM ratings
INNER JOIN
projects on ratings.project_id = projects.project_id
WHERE ratings.rating = 5 AND projects.active = 1
)
SELECT prl.`number`, creator, notes
FROM prl
INNER JOIN(
SELECT `number`
HAVING COUNT(creator) > 1
)temp ON prl.`number` = temp.`number`
ORDER BY temp.`number`
projects table
project_id| number | creator | active |
| 1 | 3 | bob | 1 |
| 2 | 4 | mary | 1 |
| 3 | 5 | asi | 1 |
rating table
project_id| notes | rating |
| 1 | note1 | 5 |
| 1 | note2 | 5 |
| 3 | note3 | 5 |
| 1 | note4 | 1 |
| 2 | note5 | 5 |
| 3 | note6 | 2 |
result
| number | creator | notes |
| 3 | bob | note1 |
| 3 | bob | note2 |
It seems like you're using MySQL version that support window function. If so, then try this:
SELECT number, creator, notes
FROM
(SELECT p.number, p.creator, r.notes,
COUNT(creator) OVER (PARTITION BY creator) AS cnt
FROM project p
JOIN rating r ON p.project_id=r.project_id
WHERE r.rating=5
AND p.active = 1) v
WHERE cnt=2;
As far as whether this is more efficient, I'm not really sure because it depends in your table indexes but for a small dataset, I assume this will do well.
Demo fiddle

query to get customer list using JOIN with sum () of the amounts spent in orders

I have the following tables
table anag (customer registry)
id | surname | name | phone |
----------------------------------------------
1 | Brown | Jack | +3989265781 |
2 | Smith | Bill | +3954872358 |
3 | Rogers | Stan | +3912568453 |
4 | Pickford | Eric | +3948521358 |
----------------------------------------------
table levels (table that connects each customer to his salesperson. For database registration reasons, the link between customer and seller is given by the customer's telephone number)
id | client_phone | id_seller |
--------------------------------------
1 | +3989265781 | 4 |
2 | +3954872358 | 7 |
3 | +3912568453 | 7 |
4 | +3948521358 | 8 |
--------------------------------------
table orders (contains all purchases made by customers, of course)
id | id_client | id_item | id_seller | price | status |
--------------------------------------------------------------------
1 | 1 | 2 | 4 | 12.50 | 2 |
2 | 2 | 2 | 7 | 12.50 | 2 |
3 | 2 | 3 | 7 | 10.00 | 3 |
4 | 2 | 3 | 7 | 10.00 | 3 |
5 | 2 | 4 | 7 | 20.50 | 1 |
6 | 3 | 2 | 7 | 12.50 | 1 |
7 | 3 | 5 | 7 | 19.00 | 3 |
8 | 3 | 7 | 7 | 31.00 | 2 |
9 | 4 | 1 | 8 | 5.00 | 1 |
--------------------------------------------------------------------
What I'm trying to do is get from the JOIN of these tables a complete list by seller of his customers sorted in descending order by the amount spent on orders as long as the order status is 2 or 3
Something like this (example seller id 7):
id | surname | name | amaount |
----------------------------------------
3 | Rogers | Stan | 50.00 |
2 | Smith | Bill | 32.50 |
----------------------------------------
I have tried with this query which seems correct to me, but unfortunately it returns me error in fetch_assoc()
SELECT a.id, a.surname, a.name, o.amount FROM levels AS l
JOIN anag AS a ON a.phone = l.client_phone
JOIN {
SELECT id_client, SUM(price) AS amount FROM orders
WHERE id_seller = '7' AND (status = '2' OR status = '3') GROUP BY id_client
} AS o ON o.id_client = a.id
WHERE l.id_seller = '7'
ORDER BY o.amount DESC
If I separate the subquery from the main query, both return the data I expect and it seems strange to me the JOIN between the two does not work properly
I think the only real error is using curly braces instead of parentheses:
SELECT a.id, a.surname, a.name, o.amount
FROM levels l JOIN
anag a
ON a.phone = l.client_phone JOIN
(SELECT id_client, SUM(price) AS amount
FROM orders
WHERE id_seller = '7' AND status IN ('2', '3'))
GROUP BY id_client
) o
ON o.id_client = a.id
WHERE l.id_seller = '7'
ORDER BY o.amount DESC;
In addition:
You can use IN to shorten an equality comparison to multiple values.
Although I left them in, status and id_seller look like numbers. If so, drop the single quotes. Don't mix data types.
Your question is ambiguous on what to do if the seller in orders differs from the seller in anag for a customer. This keeps your logic (the sellers need to match).
SELECT a.id, a.surname, a.name, sum(o.price) 'amount'
FROM anag a
LEFT JOIN levels l ON l.id =a.id
LEFT JOIN orders of ON o.id_seller = l.id_seller AND o.id_client = l.id
GROUP BY o.id_seller
ORDER BY amount DESC

Multiple grouped counts against categories and subcategories

I'm trying to query analytical data from a MySQL 5.7 database for displaying in the frontend. This data is in a many to many structure, and I want to aggregate two columns, one based on the information in the previous column, grouped and counted by date.
The intention is to get data in the following format (see the example data):
entry_date | cat_name | subcat_name | subcat_count | total_count
-----------|----------|-------------|--------------|------
2020-07-28 | #TestOne | Alpha | 1 | 2
2020-07-28 | #TestOne | Delta | 1 | 2
2020-07-27 | #TestTwo | Bravo | 1 | 2
2020-07-27 | #TestTwo | Charlie | 1 | 2
2020-07-26 | #TestOne | Charlie | 1 | 2
2020-07-26 | #TestOne | Bravo | 1 | 2
2020-07-25 | #TestTwo | Delta | 1 | 2
2020-07-25 | #TestTwo | Alpha | 1 | 2
In the above data, value is the quantity of a given type against a given scenario on a given date, and total is the quantity of all types against a scenario on a given day. So if there were a hundred posts with type Alpha on one day, that value would be 100 and the total would be 100. If there were another hundred posts with type Delta, the total would become 200.
I got as far as this before I realised I was lost:
SELECT
ct.entry_id,
DATE(FROM_UNIXTIME(ct.entry_date)) AS entry_date,
cg.group_name,
c.cat_name
FROM
category_posts cp
LEFT JOIN channel_titles ct ON ct.entry_id = cp.entry_id
LEFT JOIN categories c ON c.cat_id = cp.cat_id
LEFT JOIN category_groups cg ON cg.group_id = c.group_id
WHERE
cg.group_name = 'Group A'
OR cg.group_name = 'Group B'
GROUP BY
entry_date,
group_name,
cat_name
ORDER BY
entry_id,
FIELD(group_name, 'Group A', 'Group B')
This returned data in an unreliable and less useful format, but was okay – if you could be absolutely certain of the presence of records in Group A, which I couldn't be.
entry_id | entry_date | group_name | cat_name
---------|------------|------------|---------
1 | 2020-07-28 | Group A | #TestOne
1 | 2020-07-28 | Group B | Alpha
1 | 2020-07-28 | Group B | Delta
2 | 2020-07-27 | Group A | #TestTwo
2 | 2020-07-27 | Group B | Bravo
2 | 2020-07-27 | Group B | Charlie
3 | 2020-07-26 | Group A | #TestOne
3 | 2020-07-26 | Group B | Charlie
3 | 2020-07-26 | Group B | Bravo
4 | 2020-07-25 | Group A | #TestTwo
4 | 2020-07-25 | Group B | Delta
4 | 2020-07-25 | Group B | Alpha
Entity Relationship Diagram
Example Data
category_groups
group_id | group_name
---------|------------------------------
1 | Group A
2 | Group B
categories
cat_id | group_id | cat_name
-------|----------|-------------------
1 | 1 | #TestOne
2 | 1 | #TestTwo
3 | 2 | Alpha
4 | 2 | Bravo
5 | 2 | Charlie
6 | 2 | Delta
category_posts
cat_id | entry_id
-------|---------
1 | 1
2 | 2
1 | 3
2 | 4
3 | 1
4 | 2
5 | 3
6 | 4
6 | 1
5 | 2
4 | 3
3 | 4
channel_titles
entry_id | entry_date
---------|-----------
1 | 1595940540 (07/28/2020)
2 | 1595882160 (07/27/2020)
3 | 1595721600 (07/26/2020)
4 | 1595635200 (07/25/2020)
The original version of the question did not specify MySQL 5.7. This answer requires MySQL 8.0.
But I think this is just an aggregation query with a window function:
SELECT ct.entry_id,
DATE(FROM_UNIXTIME(ct.entry_date)) AS entry_date,
cg.group_name,
COUNT(*) as value,
SUM(COUNT(*)) OVER (PARTITION BY entry_id, DATE(FROM_UNIXTIME(ct.entry_date)) as total
FROM category_posts cp JOIN
channel_titles ct
ON ct.entry_id = cp.entry_id JOIN
categories c
ON c.cat_id = cp.cat_id JOIN
category_groups cg
ON cg.group_id = c.group_id
WHERE cg.group_name IN ( 'Group A', 'Group B' )
GROUP BY entry_date, group_name;
I removed the LEFT JOINs because your data all seems to match and your results have no NULL values in the key columns.

Combine latest records with table

I am using mysql and I am having the following two tables:
Products:
| id | name |
|----|-----------|
| 1 | Product 1 |
| 2 | Product 2 |
| 3 | Product 3 |
Prices:
| id | Product_id | created_at | prices |
|----|------------|---------------------|--------|
| 1 | 1 | 2017-12-23 08:32:11 | 10 |
| 2 | 1 | 2017-11-21 03:33:10 | 12 |
| 3 | 2 | 2017-12-23 08:32:11 | 43 |
| 4 | 2 | 2017-11-21 03:33:10 | 23 |
| 5 | 3 | 2017-12-23 08:32:11 | 78 |
| 6 | 3 | 2017-11-21 03:33:10 | 34 |
If I do:
SELECT * FROM prices WHERE prices.id IN ( SELECT MAX(prices.id) FROM prices GROUP BY prices.created_at )
This gives me the last prices for each product_id:
| id | Product_id | created_at | prices |
|----|------------|---------------------|--------|
| 1 | 1 | 2017-12-23 08:32:11 | 10 |
| 3 | 2 | 2017-12-23 08:32:11 | 43 |
| 5 | 3 | 2017-12-23 08:32:11 | 78 |
I would like to have as final result all Products with the latest prices, which would look like the following:
| id | name | product_id | created_at | prices |
|----|-----------|------------|---------------------|--------|
| 1 | Product 1 | 1 | 2017-12-23 08:32:11 | 10 |
| 2 | Product 2 | 2 | 2017-12-23 08:32:11 | 43 |
| 3 | Product 3 | 3 | 2017-12-23 08:32:11 | 78 |
However, I am not sure how to combine:
select * from products
and
SELECT * FROM prices WHERE prices.id IN ( SELECT MAX(prices.id) FROM prices GROUP BY prices.created_at )
I highly appreciate your replies!
Assuming your first query is giving you expected result, just use join.
SELECT p.*,d.name FROM prices p
inner join product d
on d.id=p.product_id
WHERE
p.id IN
( SELECT MAX(prices.id) FROM prices GROUP BY prices.created_at )
If you believe that there are product id in price which are not mapped in product then use left join instead of inner join.
I think you have get max total price with product name so below is query according your given tables
select prices.id, products.id,products.product, sum(prices.prices) as Totalprices from prices join products on prices.id=products.id group by prices.id order by Totalprices desc limit 1
Hope it help you
Assuming that the latest prices are the most recent created ones and depicted by prices.created_at value, to get the latest prices you could better rely on prices.created_at, instead of assuming chronological ordering by mean of prices.id (autoincrement), as in:
SELECT products.id, products.name, prices.created_at, prices.prices
FROM products
INNER JOIN prices ON products.id=prices.product_id
INNER JOIN
(SELECT product_id, MAX(created_at) AS created_at FROM prices GROUP BY product_id) AS LATEST_PRICES
on prices.product_id=LATEST_PRICES.product_id
-- WHERE prices.created_at = LATEST_PRICES.created_at
AND prices.created_at = LATEST_PRICES.created_at

double JOIN and GROUP on hasMany associations

I have 3 models (tables):
Presentation hasMany PresentationView hasMany SlideView
Fields:
Presentation: id, title
PresentationView: id, presentation_id
SlideView: id, presentation_view_id, duration
I need a query to get statistics for each presentation. Statistics are:
number of PresentationView per each Presentation
total duration of all SlideView.duration from slide views that belong to Presentation (through PresentationView)
So basically it seems like double JOIN and double GROUP but the joins doesn't work for me - I tried every combination of LEFT/INNER/RIGHT double joins and I can't make it work. The best I had it was that Presentation had grouped PresentationView but duration was SUMed just from SlideViews that belonged to just one PresentationViews not all for Presentation...
I would like to avoid nested SELECTs if possible. just JOIN/GROUP
The first thing is a simple JOIN and COUNT:
SELECT p.id, COUNT(*)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
GROUP BY p.id
The second one has to use a SUM (and JOIN):
SELECT p.id, SUM(s.duration)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
JOIN SlideView s ON v.id = s.presentation_view_id
GROUP BY p.id
If you want both in a single query:
SELECT p.id, SUM(s.duration), COUNT(DISTINCT v.id)
FROM Presentation p
JOIN PresentationView v ON p.id = v.presentation_id
JOIN SlideView s ON v.id = s.presentation_view_id
GROUP BY p.id
Reason for DISTINCT:
Tables:
Presentation: PresentationView: SlideView:
p.id | title v.id | presentation_id s.id | presentation_view_id | duration
1 | abc 1 | 1 1 | 1 | 100
2 | xyz 2 | 1 2 | 1 | 150
3 | 1 3 | 2 | 200
4 | 1 4 | 2 | 250
5 | 1 5 | 3 | 300
6 | 2 6 | 3 | 400
7 | 2 7 | 4 | 500
8 | 5 | 600
9 | 6 | 100
10 | 6 | 200
11 | 7 | 350
Example result set BEFORE the group:
p.id | v.id | s.id | s.duration
-------------------------------
1 | 1 | 1 | 100
1 | 1 | 2 | 150
1 | 2 | 3 | 200
1 | 2 | 4 | 250
1 | 3 | 5 | 300
1 | 3 | 6 | 400
1 | 4 | 7 | 500
1 | 5 | 8 | 600
2 | 6 | 9 | 100
2 | 6 | 10 | 200
2 | 7 | 11 | 350
AFTER the group without distinct:
p.id | SUM | COUNT
------------------
1 | 8 | 2500
2 | 3 | 650
With distinct:
p.id | SUM | COUNT
------------------
1 | 5 | 2500
2 | 2 | 650
In mean time I found answer:
SELECT presentations.title, SUM(slide_views.duration), COUNT(DISTINCT presentation_views.id)
FROM presentations
LEFT JOIN presentation_views ON presentations.id = presentation_views.presentation_id
LEFT JOIN slide_views ON presentation_views.id = slide_views.presentation_view_id
GROUP BY presentations.id