How to retrieve datasets from normalised shema in MySQL 5.7? - mysql

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.

Related

SQL Select parent as column name and child as value

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

GROUP BY & COUNT with multiple parameters

I have a simple configuration :
2 tables linked in a many-to-many relation, so it gave me 3 tables.
Table author:
idAuthor INT
name VARCHAR
Table publication:
idPublication INT,
title VARCHAR,
date YEAR,
type VARCHAR,
conference VARCHAR,
journal VARCHAR
Table author_has_publication:
Author_idAuthor,
Publication_idPublication
I am trying to get all the authors name that have published at least 2 papers in conference SIGMOD and conference PVLDB.
Right now I achieved this but I still have a double result. My query :
SELECT author.name, publication.journal, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication = publication.idPublication
GROUP BY publication.journal, author.name
HAVING COUNT(*) >= 2
AND (publication.journal = 'PVLDB' OR publication.journal = 'SIGMOD');
returns
+-------+---------+----------+
| name | journal | COUNT(*) |
+-------+---------+----------+
| Renee | PVLDB | 2 |
| Renee | SIGMOD | 2 |
+-------+---------+----------+
As you can see the result is correct but doubled, as I just want 1 time the name.
Other question, how to modify the number parameter for only one conference, for example get all the author that published at least 3 SIGMOD and at least 1 PVLDB ?
If you don't care about the journal , don't select it, it is splitting your results. Also, normal filters need to be placed in the WHERE clause, not the HAVING clause :
SELECT author.name, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication =
publication.idPublication
WHERE publication.journal IN('PVLDB','SIGMOD')
GROUP BY author.name
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 2
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 2;
For the second question, use this HAVING() clause :
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 3
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 1;

how do I concatenate rows in a mysql query

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

What MySQL query can return a result set that meets all criteria for example return all photos that have these 'x' keywords?

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

How to find all products with some properties?

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)