I have this schema:
items | taxonomy | subjects
| |
ID headline | item_id subject_id | subject_id subject
-------------------------------------------------------------------------
1 information | 1 1 | 1 cities
2 here we are | 2 1 | 2 towns
3 more things | 3 2 | 3 water
4 doo dah | 3 4 | 4 telephones
| 4 1 |
| 4 3 |
I would like to select a single row from "items" and with it, include all the rows from "subjects" which are joined by the "taxonomy" table. So for example, getting item.ID=3 would result in something like:
items.ID = 3
items.headline = "more things"
subjects.subject = "towns"
subjects.subject = "telephones"
I've started with this query
SELECT
i.ID,
i.headline,
s.subject_name
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3
But this only returns a single value from subject_name even if there are multiple values associated with that item_id.
EDIT
I actually had a LIMIT 1 on the query which was causing (as #Gordon Linoff said) only one row to be returned, even though there were multiple rows in the result set corresponding to the multiple subjects. His solution still does nicely, because I only want to return a single row.
Your query returns the subjects on multiple rows. If you want the subjects on a single row, then you need some form of concatenation:
SELECT i.ID, i.headline, GROUP_CONCAT(s.subject_name) as subjects
FROM items i JOIN
taxonomy t
ON i.ID = t.item_id JOIN
subjects s
ON t.subject_id = s.subject_id
WHERE i.ID = 3
GROUP BY i.ID, i.headline;
For one item, the GROUP BY is optional, but it is good form in case you modify the query to handle multiple items.
I would suggest you the "union all" clause (or "union", if you are not needing the duplicates).
(SELECT
"taxonomy" As Name,
i.headline As Value
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
WHERE i.ID = 3)
Union All
(SELECT
"subject" As Name,
s.subject_name As Value
FROM items i
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3)
You can add a 2nd field in each select to indicate type of item selected ("headline", "subjects", etc).
Related
I am creating a database to store music.
There are different categories that each have sub categories. Example:
id name parentID
1 instrumentation null
2 solo_instrument null
3 Concert Band 1
4 Brass Band 1
5 Fanfare Band 1
6 Clarinet 2
7 Saxophone 2
8 Trumpet 2
On the other hand I have a table that stores the musicID that is linked to a categoryID
id categoryID musicID
1 4 1
2 8 1
3 3 2
4 6 2
I need the following result from a query:
musicID instrumentation solo_instrument
1 Brass Band Trumpet
2 Concert Band Clarinet
I have been told to use a tree structure as in the future it is likely that other categories are added and this should be able to support that. However, I am not able to figure out how to write a query to get the result above.
I kind of get the result I want when selecting first the instrumentation, second the solo_instrument, but this is all hardcoded and does not allow for music tracks to only have one parentID as I select them individually.
Is this even possible or should I overhaul my database structure? I'd like to see your recommendations.
You should be able to tackle this using conditional aggregation.
Query :
SELECT
mu.musicID,
MAX(CASE WHEN cp.name = 'instrumentation' THEN ca.name END) instrumentation,
MAX(CASE WHEN cp.name = 'solo_instrument' THEN ca.name END) solo_instrument
FROM
musics mu
INNER JOIN categories ca ON ca.id = mu.categoryID
INNER JOIN categories cp ON cp.id = ca.parentID
GROUP by mu.musicID
The INNER JOINs pull up the corresponding category, and then goes up one level to find the parent category. If new root categories are created, you would just need to add more MAX() columns to the query.
In this DB Fiddle demo with your sample data, the query returns :
| musicID | instrumentation | solo_instrument |
| ------- | --------------- | --------------- |
| 1 | Brass Band | Trumpet |
| 2 | Concert Band | Clarinet |
First you group by musicid in table_music and the join twice to table_categories:
select t.musicid, c1.name instrumentation, c2.name solo_instrument
from (
select musicid, min(categoryid) instrumentationid, max(categoryid) solo_instrumentid
from table_music
group by musicid
) t inner join table_categories c1
on c1.id = t.instrumentationid
inner join table_categories c2
on c2.id = t.solo_instrumentid
order by t.musicid
I have two tables that I need to cross and return as many results as the ids of one of them.
The first is a table of roles/tasks:
id | rolename
---+---------
1 | check_in
2 | cleaning
3 | taxi
4 | guide
5 | car_rental
6 | meals
7 | house_owner
20 | custom
and another table that has the columns:
id | client_booking_id | staff_role_id | confirmed | staff_cost
I need a query that always gives me as many results as nr of columns in the first table. Because for each unique client_booking_id there will be only one (if any) of those tasks/roles.
So if I do:
SELECT sr.role_name, sr.id, ss.staff_cost, ss.confirmed
FROM staff_role AS sr
LEFT JOIN staff_schedule AS ss ON sr.id=ss.staff_role_id
I get a result with the nr of rows I want. Now I need to match it with a specific client_booking_id so I did
SELECT sr.role_name, sr.id, ss.staff_cost, ss.confirmed
FROM staff_role AS sr
LEFT JOIN staff_schedule AS ss ON sr.id=ss.staff_role_id
WHERE ss.client_booking_id=1551 // <-- this is the new line
And this gives me only 2 results because in the second table I have only booked 2 tasks to a id.
But I need a result with all tasks even those that do not match, with NULL values. How can I do this?
With your query (without where clause) you get rows with null and non-null values for client_booking_id. You want to match specific client_booking_id and at the same time leave all records with null values, so you add additional condition with specific client_booking_id to left join.
Moving condition to left join:
select sr.role_name
, sr.id
, ss.staff_cost
, ss.confirmed
from staff_role sr
left join staff_schedule ss on sr.id = ss.staff_role_id
and ss.client_booking_id = 1551
There are a number of left join questions already, but still I can't quite put my finger on this issue. The WHERE condition doesn't look sensible to move.
The problem is that there should be 4 rows returned but only 1 is.
In checking the left join conditions, there is 1 row returned for each left join, which is correct for the number of records in the table, however the query below returns 1 record instead of 4, but I can't see how to return 4, yet.
Query follows: (Gives 1 result not 4; 4 being expected)
SELECT
list.uid,
list.business_uid,
list.creator_name,
business.company_name,
list_alias.uid AS list_alias_uid,
list_alias.alias AS list_alias,
list_member.uid AS list_member_uid,
mailbox.full_name AS list_member_name,
mailbox.email_address AS list_member_email_address
FROM
mailbox,
business,
list
LEFT JOIN
list_alias ON list_alias.list_uid=list.uid
LEFT JOIN
list_member ON list_member.list_uid=list.uid
WHERE
list.business_uid='1'
AND list.business_uid=business.uid
AND mailbox.uid=list_member.mailbox_uid
ORDER BY
list.full_name ASC
Data:
Business UID 1 has 4 lists
SELECT * FROM list WHERE business_uid=1 -- gives 4 results
SELECT * FROM list_alias WHERE list_uid IN (SELECT uid FROM list WHERE business_uid=1) -- gives 1 result
SELECT * FROM list_member WHERE list_uid IN (SELECT uid FROM list WHERE business_uid=1) -- gives 1 result
Any pointers on what I could check would be welcome.
Table Sample Data:
list:
uid | business_uid | creator_name | full_name
--------------------------------------------------
1 1 List Maker Subscribe to W
2 1 List Maker Subscribe to X
3 1 List Maker Subscribe to Y
4 1 List Maker Subscribe to Z
business:
uid | company_name
-------------------
1 List Company
list_alias:
uid | list_uid | alias
----------------------------------------
1 1 subscriber#list-url.com
list_member:
uid | list_uid | mailbox_uid
------------------------------------
1 1 1
mailbox:
uid | full_name | email_address
-------------------------------
1 I am He me#me.com
Try this using a single join methodology, like so.
SELECT list.uid,
list.business_uid,
list.creator_name,
b.company_name,
la.uid AS list_alias_uid,
la.alias AS list_alias,
lm.uid AS list_member_uid,
m.full_name AS list_member_name,
m.email_address AS list_member_email_address
FROM list LEFT JOIN list_member lm ON lm.list_uid=list.uid
LEFT JOIN mailbox m ON m.uid=lm.mailbox_uid
LEFT JOIN business b ON list.business_uid=b.uid
LEFT JOIN list_alias la ON la.list_uid=list.uid
WHERE list.business_uid=1
ORDER BY list.full_name ASC
Question: What are the values of 'uid' from the 'list' table? Because 'uid' is not the same as 'business_uid'. What I mean is ...
If the 'list' table has this ...
'uid' 'business_uid'
1 1
2 1
3 1
4 1
Then that is the problem. You are returning the same 'busines_uid' but a different 'uid' which means it will only match the first record.
I have 3 tables:
photos: id, name
keywords: id, keyword
photo_keyword relationship table: photo_id, keyword_id
In other words one photo may be associated to multiple keywords describing the photo
photos:
id | name
1 | photo 1
2 | photo 2
3 | photo 3
keywords:
id | keyword
1 | NHL
2 | Toronto
3 | Montreal
4 | Boston
5 | Chicago
6 | Canadiens
7 | Leafs
photo_keyword:
photo_id | keyword_id
1 | 2
1 | 7
1 | 1
2 | 1
2 | 3
2 | 6
3 | 2
3 | 7
I would like to write/build a query dynamically where i can get a list of photos that meet all criteria asked for.
example: I want all photos that have Toronto (2) and Leafs (7) as keywords
ie) I want to retrieve photo 1 and photo 3 as they are the two that have both keywords
if I asked for photos that have NHL (1) Toronto (2) and Leafs (7) as criteria, only photo 1 would be returned.
The obvious first attempt involved joining the tables:
SELECT photo.id, photo.name
FROM photo
LEFT JOIN photo_keyword ON photo_keyword.photo_id = photo.id
LEFT JOIN keyword ON keyword.id = photo_keyword.keyword_id
WHERE keyword.keyword = 'Toronto'
returns photos 1 and 2
This type of query can be used whenever i am searching for all photos meeting 1 keyword criteria. However,
trying to get photos that meet the criteria of two keywords is a different story.
I tried
WHERE keyword.keyword IN ('NHL, 'Toronto', 'Leafs')
but that returns photos that meet either criteria but no necessarily both. In this case photo 1 and 2 as well.
So how do I build a query that only returns only the photos that meets all keyword criteria?
Any help on this would be immensely appreciated!
Update:
With everyone's comments this is what I put together and seems to do what I require:
SELECT
photos.id
FROM photos
LEFT JOIN photo_keyword pk ON pk.photo_id = photos.id
LEFT JOIN keywords kw ON kw.id = pk.keyword_id
WHERE kw.keyword IN ('NHL', 'Toronto', 'Leafs')
GROUP BY photos.id
HAVING COUNT(kw.keyword) = 3;
If you know keywords cannot be repeated for a given photo, I think you could accomplish this with something like:
SELECT photo.name from photo where photo.id IN (
SELECT photo.id
FROM photo
LEFT JOIN photo_keyword ON photo_keyword.photo_id = photo.id
LEFT JOIN keyword ON keyword.id = photo_keyword.keyword_id
WHERE keyword.keyword IN ('NHL, 'Toronto', 'Leafs')
GROUP BY (photo.id)
HAVING COUNT (photo.name) = *Number of passed keywords*)
Could also do this with UNIONS, etc. but a lot of this depends on what you know about your keywords and how you are getting them.
Try this query -
SELECT p.id, p.name FROM photo_keyword pk
JOIN keywords k
ON k.id = pk.keyword_id
JOIN photos p
ON p.id = pk.photo_id
GROUP BY
pk.photo_id
HAVING
COUNT(IF(k.keyword = 'Toronto', 1, NULL)) > 0 AND
COUNT(IF(k.keyword = 'Leafs', 1, NULL)) > 0
I'm listing product properties in a MySQL table where each row contains a product ID prod and a property ID prop. If a product has three properties, this results in three rows for that product. Example table:
prod | prop
-----+-----
1 | 1
2 | 1
2 | 2
2 | 3
3 | 2
3 | 4
How can I find which products have both properties #1 and #2 (product #2)?
The only way that I can think of is one select and inner join per property, but I think that would be very inefficient. It's a search function for a website and has to work for 10k lines in the table and 10 requested properties.
SELECT prod
FROM tbl
WHERE prop IN (1, 2)
GROUP BY prod
HAVING COUNT(*) = 2
And if there will be always 2 properties to find - then INNER JOIN would be a bit more efficient:
SELECT t1.p
FROM tbl t1
INNER JOIN tbl.t2 ON t2.prod = t1.prod
AND t2.prop = 2
WHERE t1.prop = 1
The recommended index for this query to be efficient as much as possible is a compound one (prop, prod)