SELECT rows with MAX value - mysql

Well, I have three different tables (check below). I wanted to make that I can see, which is the latest image (req_image_1, etc) saved for each category, which is not parent (cat_parent = 0)
One table, which holds general information about requests
+----+--------------------------+------------+
| id | req_name | req_parent |
+----+--------------------------+------------+
| 3 | Send pack | 19 |
| 4 | Go Visit | 18 |
| 5 | Stop by | 19 |
| 6 | Deliver cookies | 34 |
+----+--------------------------+------------+
Second table, which holds meta information about requests
+----------+------------+------------+----------------------------+
| umeta_id | request_id | meta_key | meta_value |
+----------+------------+------------+----------------------------+
| 1 | 3 | req_city | London |
| 2 | 3 | req_street | 11 Baker street |
| 3 | 3 | req_img_1 | a1c8f69edb37bf6c6.jpg |
| 4 | 4 | req_city | Manchester |
| 5 | 4 | req_street | 71 Main street |
| 6 | 4 | req_img_2 | a71f4160d7f7f7555.jpg |
| 7 | 5 | req_city | Sheffield |
| 8 | 5 | req_street | 240 Duke street |
| 9 | 6 | req_city | Manchester |
| 10 | 6 | req_street | 13 Chapel street |
| 11 | 6 | req_img_1 | 854b9faaa53d8fe02.jpg |
+----------+------------+------------+----------------------------+
Third table, which holds information about categories
+----+------------------------+------------+
| ID | cat_name | cat_parent |
+----+------------------------+------------+
| 1 | Category_01 | 0 |
| 6 | Category_02 | 0 |
| 18 | Category_01_01 | 1 |
| 19 | Category_01_02 | 1 |
| 34 | Category_02_01 | 6 |
+----+------------------------+------------+
So far I managed, that I could get all images for each category with this query:
SELECT cat.cat_parent AS category, req.ID, meta.meta_value AS image
FROM d_requests req
LEFT JOIN d_requests_meta meta ON ( req.ID = meta.request_id )
LEFT JOIN d_categories cat ON ( req.req_parent = cat.ID )
WHERE meta.meta_key LIKE 'req_img_%'
I got this result:
+------------+----+-----------------------+
| category | ID | image |
+------------+----+-----------------------+
| 1 | 3 | a1c8f69edb37bf6c6.jpg |
| 1 | 4 | a71f4160d7f7f7555.jpg |
| 6 | 6 | 854b9faaa53d8fe02.jpg |
+------------+----+-----------------------+
But I wanted to make enhancement, so I would get only result, where each category has only one image against, for example category 1, has image a71f4160d7f7f7555.jpg and category 6 has image 854b9faaa53d8fe02.jpg
I bet, that I miss some basic knowledge, and simple enhancement with subquery and selecting MAX would work as a charm.
Thanks!

SQL Fiddle
select
category,
(select request_id
from d_requests_meta
where umeta_id = s.ID
) as ID,
(select meta_value
from d_requests_meta
where umeta_id = s.ID
) AS image
from (
SELECT cat.cat_parent AS category, max(meta.umeta_id) ID
FROM d_requests req
LEFT JOIN d_requests_meta meta ON ( req.ID = meta.request_id )
LEFT JOIN d_categories cat ON ( req.req_parent = cat.ID )
WHERE meta.meta_key LIKE 'req_img_%'
group by cat.cat_parent
) s

SELECT category, ID, image
FROM ( SELECT cat.cat_parent AS category, req.ID, meta.meta_value AS image
FROM d_requests AS req
LEFT JOIN d_requests_meta AS meta
ON req.ID = meta.request_id
LEFT JOIN d_categories AS cat
ON req.req_parent = cat.ID
WHERE meta.meta_key LIKE 'req_img_%'
ORDER BY req.ID DESC) AS h
GROUP BY category
I edited Clodoaldo's answer with the use of the inofficial MySQL assumption that GROUP BY will return the 1st row based on ORDER BY in subquery.

Try this http://sqlfiddle.com/#!2/bfe9a/19
SELECT Category, ID, Image FROM (
SELECT Category, ID, Image,
#id:=CASE WHEN #category <> category THEN 1 ELSE #id+1 END AS ImgRank,
#category:=category AS categoryTemp FROM
(SELECT #id:= 0) AS i,
(SELECT #category:= 0) AS c,
(
SELECT cat.cat_parent AS category, req.ID, meta.meta_value AS image
FROM d_requests req
LEFT JOIN d_requests_meta meta ON ( req.ID = meta.request_id )
LEFT JOIN d_categories cat ON ( req.req_parent = cat.ID )
WHERE meta.meta_key LIKE 'req_img_%'
ORDER BY cat.cat_parent, req.id desc
) Vw
) vw2 WHERE IMGRANK = 1

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

Selecting COUNT and MAX columns with 2 tables and a bridge table

so what I am trying to do is having 3 tables (pictures, collections, and bridge) with the following columns:
Collections Table:
| id | name |
------------------
| 1 | coll1 |
| 2 | coll2 |
------------------
Pictures Table: (timestamps are unix timestamps)
| id | name | timestamp |
-------------------------
| 5 | Pic5 | 1 |
| 6 | Pic6 | 19 |
| 7 | Pic7 | 3 |
| 8 | Pic8 | 892 |
| 9 | Pic9 | 4 |
-------------------------
Bridge Table:
| id | collection | picture |
-----------------------------
| 1 | 1 | 5 |
| 2 | 1 | 6 |
| 3 | 1 | 7 |
| 4 | 1 | 8 |
| 5 | 2 | 5 |
| 6 | 2 | 9 |
| 7 | 2 | 7 |
-----------------------------
And the result should look like this:
| collection_name | picture_count | newest_picture |
----------------------------------------------------
| coll1 | 4 | 8 |
| coll2 | 3 | 9 |
----------------------------------------------------
newest_picture should always be the picture with the heighest timestamp in that collection and I also want to sort the result by it. picture_count is obviously the count of picture in that collection.
Can this be done in a single statement with table joins and if yes:
how can I do this the best way?
A simple method uses correlated subqueries:
select c.*,
(select count(*)
from bridge b
where b.collection = c.id
) as pic_count,
(select p.id
from bridge b join
pictures p
on b.picture = b.id
where b.collection = c.id
order by p.timestamp desc
limit 1
) as most_recent_picture
from collections c;
A more common approach would use window functions:
select c.id, c.name, count(bp.collection), bp.most_recent_picture
from collections c left join
(select b.*,
first_value(p.id) over (partition by b.collection order by p.timestamp desc) as most_recent_picture
from bridge b join
pictures p
on b.picture = p.id
) bp
on bp.collection = c.id
group by c.id, c.name, bp.most_recent_picture;

Getting specific values from many-to-many relationships

My database looks like this, I have client accounts which are assigned to specific profiles, and I have profiles which are assigned to specific categories, like in this schema:
| categories | | profiles | | categories_map |
--------------- ------------- ----------------------------
| ID | name | | ID | name | | ID | profile_id | cat_id |
--------------- ------------- ----------------------------
| 1 | cat1 | | 1 | p1 | | 1 | 1 | 1 |
| 2 | cat2 | | 2 | p2 | | 2 | 2 | 1 |
| 3 | cat3 | | 3 | p3 | | 3 | 3 | 1 |
| 4 | p4 | | 4 | 1 | 2 |
| 5 | 3 | 2 |
| 6 | 4 | 3 |
| profiles_map |
-----------------------------
| ID | profile_id | acc_id |
-----------------------------
| 1 | 1 | 1 |
| 2 | 3 | 1 |
| 3 | 4 | 1 |
I need to get categories assigned to accounts - which means when I want to get categories for acc_id = 1, I should get categories with ID 2 and 3 ( category with ID 2 doesn't fit because it contains profile with ID 2 which isn't assigned to this account). I tried this query but it doesn't work
select cats.id from profiles_map map
right join categories_map catm on catm.profile_id = map.profile_id
right join categories cats on cats.id = catm.cat_id
where catm.profile_id in (select profile_id from profiles_map where acc_id = 1)
and map.acc_id = 1 group by cats.id;
Could anybody help me with this question?
Can you try this one?
SELECT DISTINCT C.ID
FROM profiles_map PM
INNER JOIN categories_map CM ON CM.profile_id = PM.profile_id
INNER JOIN categories C ON C.ID = CM.cat_id
WHERE PM.acc_id= 1
If you want to get only category id, Please try following query:
SELECT DISTINCT cm.cat_id FROM categories_map cm
WHERE cm.profile_id in
(SELECT profile_id FROM profiles_map WHERE acc_id = 1)
Or if want to get category name and id then , use following query:
SELECT cat.id,cat.name FROM categories cm
WHERE cat.id in (SELECT DISTINCT cm.cat_id FROM categories_map cm
WHERE cm.profile_id in
(SELECT pm.profile_id FROM profiles_map pm WHERE pm.acc_id = 1))

MySQL group_concat with select inside select

I have a glitch which i cannot solve,let me elaborate...
These are my MySQL tables...
Therapists table
id therapist_name
1 Therapist 1
2 Therapist 2
Location table
+-----+------------+--+
| id | name | |
+-----+------------+--+
| 1 | Location 1 | |
| 2 | Location 2 | |
| 3 | Location 3 | |
+-----+------------+--+
Days_location table
+-----+-----------+--------------+-------------+--+
| id | day | therapist_id | location_id | |
+-----+-----------+--------------+-------------+--+
| 1 | monday | 1 | 1 | |
| 2 | monday | 1 | 2 | |
| 3 | wednesday | 1 | 3 | |
| 4 | wednesday | 2 | 1 | |
| 5 | tuesday | 2 | 2 | |
| 6 | friday | 2 | 1 | |
| 7 | friday | 2 | 2 | |
| 8 | friday | 1 | 1 | |
+-----+-----------+--------------+-------------+--+
Now i want to get every therapist with locations for every day,for example something like this:
therapist_name=>Therapist 1,day_locations=>monday(Location1,Location2),friday(Location1)
I need it to be as a select variable,this was my query but i got stuck there:
SELECT t.*,GROUP_CONCAT(
SELECT CONCAT(dl2.day,GROUP_CONCAT(dl2.location_id)) as concated
FROM days_location dl2
WHERE therapist_id=85
GROUP BY dl2.day
) as day_location
FROM therapists t
LEFT JOIN days_location dl
ON dl.therapist_id=t.id
This of course doesn't work,what am i doing wrong...should i try a different approach or make my tables different?
I believe this is what you're looking for, or could get you started:
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
For therapists.id = 1 this should give you results:
+----------------+-----------+-----------------------+
| therapist_name | day | locations |
+----------------+-----------+-----------------------+
| Therapist 1 | monday | Location 1,Location 2 |
| Therapist 1 | wednesday | Location 3 |
| Therapist 1 | friday | Location 1 |
+----------------+-----------+-----------------------+
If you need to concatenate day with locations column then use a simple CONCAT():
SELECT
therapist_name,
CONCAT(day, '(', locations, ')') AS locations
FROM (
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
) t
GROUP BY therapist_name, locations
Output should look like:
+----------------+-------------------------------+
| therapist_name | locations |
+----------------+-------------------------------+
| Therapist 1 | monday(Location 1,Location 2) |
| Therapist 1 | wednesday(Location 3) |
| Therapist 1 | friday(Location 1) |
+----------------+-------------------------------+
If you need to group it all into one row for each therapist, then you could GROUP_CONCAT() again.
Edit after comments:
SELECT
therapist_name,
GROUP_CONCAT( CONCAT(day, '(', locations, ')') SEPARATOR ',' ) AS locations
FROM (
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
) t
GROUP BY therapist_name
I haven't tested the code so there may be some minor mistakes to tweak. No way of testing it atm.

Categorize the identical rows without repeating one-to-many relationship using LEFT JOIN

pardon my question title, I'm not sure what should I put it, I have these two tables as below.
products orders
+------+----------+ +--------+------+-------+
| id | name | | id | qty | pid |
+------+----------+ +--------+------+-------+
| 1 | mouse | | 10001 | 20 | 1 |
| 2 | keyboard | | 10002 | 15 | 3 |
| 3 | headset | | 10004 | 5 | 3 |
+------+----------+ | 10005 | 12 | 2 |
| 10006 | 18 | 1 |
+--------+------+-------+
This is the LEFT JOIN query I am using and the output
SELECT p.id AS No, p.name AS ProductName, o.qty AS Quantity
FROM products AS p
LEFT JOIN orders AS o ON p.id = o.pid
+------+-------------+----------+
| No | ProductName | Quantity |
+------+-------------+----------+
| 1 | mouse | 20 |
| 1 | mouse | 18 |
| 2 | keyboard | 12 |
| 3 | headset | 15 |
| 3 | headset | 5 |
+------+-------------+----------+
What I am trying to achieve is an output as below:
+------+-------------+----------+
| No | ProductName | Quantity |
+------+-------------+----------+
| 1 | mouse | 20 |
| | | 18 |
| 2 | keyboard | 12 |
| 3 | headset | 15 |
| | | 5 |
+------+-------------+----------+
My question is it possible to do so? Any reply and suggestions is greatly appreciate. Thanks.
P/S: I also have tried using the GROUP_CONCAT(qty SEPARATOR ",") but it returns the result in one row as I may have more additional column to add in the Orders table in the future and it will be difficult to read.
Sure, it's possible — and without needing to use variables:
SELECT IF(c.min_oid IS NOT NULL, a.id, NULL) AS No,
IF(c.min_oid IS NOT NULL, a.name, NULL) AS ProductName,
b.qty AS Quantity
FROM products a
JOIN orders b ON a.id = b.pid
LEFT JOIN (
SELECT MIN(id) AS min_oid
FROM orders
GROUP BY pid
) c ON b.id = c.min_oid
ORDER BY a.id,
b.id
Basically what it's doing is if the row is not the minimum order id of a particular product, display blank (NULL), otherwise display the information.
SQLFiddle Demo
In this case you can use MySQL variables. I store the previous product id in the variable #prev, and only if it changes we output the product name.
http://www.sqlfiddle.com/#!2/d5fd6/9
SET #prev := NULL;
SELECT
IF( #prev = p.id, NULL, p.id) AS No,
IF( #prev = p.id, NULL, p.name) AS ProductName,
o.qty AS Quantity
,#prev := p.id
FROM products AS p
LEFT JOIN orders AS o
ON p.id = o.pid