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
Related
I am trying to retrieve datasets from a normalised MySQL 5.7 table shema where I am struggling to get the values.
There are 4 tables: https://www.db-fiddle.com/f/q2PJZVegdWXnpkotN2utu2/0
Table1: articles
article_id | title
1 First Car
2 Second Car
Table2: articles_attr
article_id | attr_id
1 1
1 2
1 3
1 5
2 3
2 4
Table3: attr_groups
attr_id | attr_group_id | attribute
1 1 red
2 2 diesel
3 3 automatic
4 3 airbag
5 3 radio
Table4: attr_groups_names
attr_group_id | name
1 color
2 engine
3 features
Now I would like to retrieve all datasets (car1, car2, ..) with all attributes where the ones with multiple attributes per group get agregated.
e.g.
article_id | title | color | engine | features
1 Car 1 red diesel automatic,radio
2 ...
The amount of groups is huge (20+), so I would like to avoid to many joins.
My best shot:
SELECT
a.article_id,
a.title,
GROUP_CONCAT(CASE attr.attr_group_id WHEN 26 THEN cat.attr_de END) AS functions,
GROUP_CONCAT(CASE attr.attr_group_id WHEN 27 THEN cat.attr_de END) AS miscellaneous
FROM articles_attr AS attr
INNER JOIN articles a ON a.article_id = attr.article_id
INNER JOIN articles_attr AS cat ON cat.attr_id = attr.attr_id
GROUP BY a.article_id
LIMIT 3
How can this be done?
Your query has a correct basic structure, but your CASE expressions look somewhat off. Try this version:
SELECT
a.article_id,
a.title,
GROUP_CONCAT(CASE WHEN agn.name = 'color' THEN ag.attribute END) color,
GROUP_CONCAT(CASE WHEN agn.name = 'engine' THEN ag.attribute END) engine,
GROUP_CONCAT(CASE WHEN agn.name = 'features' THEN ag.attribute END) features
FROM articles a
INNER JOIN articles_attr aa
ON a.article_id = aa.article_id
INNER JOIN attr_groups ag
ON ag.attr_id = aa.attr_id
INNER JOIN attr_groups_names agn
ON agn.attr_group_id = ag.attr_group_id
GROUP BY
a.article_id,
a.title;
Demo
GROUP_CONCAT works here by ignoring NULL values in the aggregation, which then do not get added to the concatenated string. Note also that depending on your MySQL version, you might have to GROUP BY both the id and title in the articles table.
There is an error in my query and I would like some help. I have three tables
Rooms{id,number,name,type(ECO/LUX),active(0/1)}
Men{passport,roomid,status(YOUTH/ADULT)}
Women{passport,roomid,status(YOUTH/ADULT)}
**In each room there can be more than one woman or man.
I want to count how many women and men have the same room with roomid in (1,2,3), status='ADULT', type='LUX' and active=1. Therefore I need a result like this:
+----+--------+-----------+----------+------------+
| id | number | name | CountMen | CountWomen |
+----+--------+-----------+----------+------------+
| 1 | 23 | 1st suite | 2 | 4 |
| 3 | 4 | 2nd suite | 1 | 2 |
+----+--------+-----------+----------+------------+
SELECT id,number,name,
sum(case when Men.status='ADULT' then 1 else 0 end) as CountMen,
sum(case when Women.status='ADULT' then 1 else 0 end) as CountWomen
FROM Rooms left join Men
on Rooms.id=Men.roomid
left join Women on Room.id=Women.roomid where
(type='LUX') and (active=true) and (id in (1,2,3))
group by id;
The problem is that I get sometimes wrong results in the counters.
In a left join, conditions on the second table need to be in the on clause. It would help if you qualified all column names in the query.
However your problem is because you are getting a Cartesian product between the gender tables. This is definitely a case where gender segregation is not a good thing. You should have just one table for people (and this doesn't even bring up other issues with defining binary genders).
SELECT r.id, r.number, r.name,
(SELECT COUNT(*)
FROM men m
WHERE m.status = 'ADULT' AND r.id = m.roomid
) as CountMen,
(SELECT COUNT(*)
FROM women w
WHERE w.status = 'ADULT' AND r.id = w.roomid
) as CountWomen
FROM Rooms r
WHERE r.type = 'LUX' AND r.active = true AND r.id IN (1, 2, 3);
However, you should fix your data model so you have people rather than segregated gender tables.
I am trying to find one query that returns all people including the company they belong to and companies that do not have any person assigned yet.
Company
cid | cname
--------------
1 Company 1
2 Company 2
Person
pid | pname | fk_company
---------------------------
1 Person 1 1
2 Person 2 1
desired result
pid | pname | fk_company | cid | cname
----------------------------------------------
1 Person 1 1 1 Company 1
2 Person 2 1 1 Company 1
NULL NULL NULL 2 Company 2
Thanks in advance
If you want everything from both tables, regardless of match left AND right, you need a FULL JOIN:
SELECT *
FROM person
FULL JOIN company
ON person.fk_company = company.cid
edit: Apparently mysql doesn't support FULL JOIN. You'll have to do both LEFT JOINS by hand and UNION ALL them.
You should mention something you tried. Anyway, I will explain the method so you can work on it.
SELECT <column_names>FROM <table1_name> LEFT JOIN <table2_name>ON
<table1.column_name> = <table2.column_name>;
For more explanations please refer this link.
SQL Left Join
table 1
--------
pid | name
1 someone
2 another
table2
--------
bid | valu
1 drum
2 guitar
3 flower
4 cake
table3
------
id | pid | bid | pref
1 1 3 yes
2 1 1 maybe
3 1 2 no
4 2 4 definately
5 2 2
6 2 3 no
So as you can see I have 3 simple tables where the third one is used to create a mapping between table 1 and 2 along with some additional data. Now I need to write a query to display the valu and pref in a concatenated string based on the pid,
So against pid = 1, the expected output is something like
flower yes, drum maybe, guitar no....so How do I write this query?
I tried( pretty much a blind guess):
SELECT opa.name, GROUP_CONCAT(CONCAT(opb.valu,' ',opc.pref) SEPARATOR ',') AS myChoice
From
table_1 opa
INNER JOIN table_3 opc ON opc.pid = opa.pid
INNER JOIN table_2 opb ON opb.bid = opc.bid
Any help is appreciated.
your query is right you just forgot the GROUP BY
SELECT opa.name, GROUP_CONCAT(CONCAT(opb.valu,' ',opc.pref) SEPARATOR ',') AS myChoice
From
table1 opa
INNER JOIN table3 opc ON opc.pid = opa.pid
INNER JOIN table2 opb ON opb.bid = opc.bid
group by opc.pid
DEMO HERE
I have three tables:
TABLE 1 contracts
-------------------
id | contract_name
--------------------
1 test name
2 test name 2
2 test name 3
4 test name 4
TABLE 2 rooms
-------------------
id | room_name
--------------------
1 test name
2 test name 2
2 test name 3
4 test name 4
TABLE 3 promos
----------------------------------
id | contracts_id | rooms_id
----------------------------------
1 1,3 1,3,4
1 2 1,2,3
No I am trying to do an inner join to get the names of the contract and the rooms according to the ids in the array saved in database. I know this is not ideal at all, but I can not change the database set up. So here is what I would like to do with my query, but obviously it is impossible. Does anyone have any idea on how I can accomplish this?
mysql_query("SELECT pb.*, c.contract_name, r.room_name FROM promo_blackouts AS pb
INNER JOIN contracts as c ON c.contract_id IS IN pb.contracts_id
INNER JOIN rooms as r ON r.room_id IS IN pb.rooms_id
WHERE pb.promo_id = '$promo_id'") or die(mysql_error());
Are you looking for something like this?:
SELECT DISTINCT
contract_name,
room_name
FROM
promos
INNER JOIN
contracts ON FIND_IN_SET(contracts.id, contract_id) != 0
INNER JOIN
rooms ON FIND_IN_SET(rooms.id, room_id) != 0
WHERE
promos.id = 1