mysql many-to-many query issue - php

i have many to many database and i'm there is a query that i just got stuck with and cant do it.
i have 4 tables Artists, Tracks, Albums, Clips
now i'm on the artist page so i need to get them by the artist page, i already got all of them, but not the way i want them.
because some tracks, albums, clips belong to other artists as well (duet) and i need to display their name.
but the problem is that i'm selecting using the artist id so my GROUPC_CONCAT function wont work here is the query that gets the artist albums.
SELECT al.album_name, GROUP_CONCAT(a.artist_name SEPARATOR ', ') AS 'artist_name'
FROM
Albums al
LEFT JOIN
ArtistAlbums art ON art.album_id = al.album_id
LEFT JOIN
Artists a on a.artist_id = art.artist_id
WHERE
a.artist_id = 10
GROUP BY
al.album_id
one of the albums have two artists attached to it, but it does not get the other artist name.
when i select by the album_id i get the two artists.
please note that i'm new to mysql and i did not find any answers on this particular problem almost no resources on many-to-many querying.
how can i tackle this problem.?
any resources or books on many-to-many that show how to deal with the database on the application layer will be much appreciated,
thanks in advance.

Think of table aliases as really being row aliases. That is, for purposes of expressions in the WHERE clause and the select-list, the alias refers to a single row at a time.
Since you've created a condition such that a.artist_id = 10, then only rows for that artist match the condition. What you really want is to match all artists on an album given that one artist is artist_id = 10.
For that, you need another join, so that the row where artist_id = 10 is matched to all the rows for that respective album.
SELECT al.album_name, GROUP_CONCAT(a2.artist_name SEPARATOR ', ') AS `artist_name`
FROM
Albums al
INNER JOIN
ArtistAlbums art ON art.album_id = al.album_id
INNER JOIN
Artists a on a.artist_id = art.artist_id
INNER JOIN
ArtistAlbums art2 ON art2.album_id = al.album_id
INNER JOIN
Artists a2 on a2.artist_id = art2.artist_id
WHERE
a.artist_id = 10
GROUP BY
al.album_id
P.S.: I've also replaced your use of LEFT JOIN with INNER JOIN. There's no reason you needed LEFT JOIN, since you're explicitly looking for albums that have a matching artist, not all albums whether or not it has artist 10. Review the meaning of different types of join.
Re your followup question:
I don't know of a book specifically about many-to-many queries. I'd recommend books like:
SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming, which is my own book and it does cover many-to-many tables.
SQL and Relational Theory to understand joins better.
Joe Celko's SQL Programming Style, which imho is Joe Celko's best book.

Related

Two table SQL double join?

I have two tables, sections and articles. Originally it was a one-to-many relationship, therefore articles has a sectionID column. Then one day it was decided to allow each article to belong to two sections. Rather than making another 'articles-in-section' table, I opted to not deal with refactoring the data, and I just added an additionalSectionID column onto the articles table. Now I have a problem.
I want to show all the articles associated with a given section, whether as its primary or secondary section. Essentially, I'm looking for some sort of double join between two tables.
These two questions have answers to the same issue - 1,2, but with a different db server. Is there are a way to do this in PHP/MySQL?
Tables' structure is basically like this:
-- SECTIONS:
id title description moderatorID url
-- ARTICLES:
id title shortDesc fullText photo sectionID additionalSectionID
See below.
SELECT s.*, a.*
FROM sections s
LEFT JOIN articles a
ON s.id = a.sectionID
OR s.id = a.additionalSectionID
WHERE s.title = 'My Section';
You might try two inner joins on separate table aliases, along the lines of:
SELECT
*,
s1.section_name AS sectionA,
s2.section_name AS sectionB
FROM articles
INNER JOIN sections s1 ON articles.sectionID = s1.sectionID
INNER JOIN sections s2 ON articles.additionalSectionID = s2.sectionID

Optimization of query that uses three tables to order a list

So I have a query that works exactly as intended but needs heavy optimization. I am using software to track load times across my site and 97.8% of all load time on my site is a result of this one function. So to explain my database a little bit before getting to the query.
First I have a films table, a competitions table and a votes table. Films can be in many competitions and competitions have many films, since this is a many to many relationship we have a pivot table to show their relationship (filmCompetition) When loading the competition page however these films need to be in order of their votes (most voted at the top. least at the bottom).
Now In the query below you can see what I am doing, grabbing from the films from the filmsCompetition table that match the current competition id $competition->id, and then I order by the total number of votes for that film. Like I said this works but is super efficient but I cannot think of another way to do it.
$films = DB::select( DB::raw("SELECT f.*, COUNT(v.value) AS totalVotes
FROM filmCompetition AS fc
JOIN films AS f ON f.id = fc.filmId AND fc.competitionId = '$competition->id'
LEFT JOIN votes AS v ON f.id = v.filmId AND v.competitionId = '$competition->id'
GROUP BY f.id
ORDER BY totalVotes DESC
") );
For this query, you want indexes on filmCompetition(competitionId, filmId), films(id), and votes(filmId, competitionId)`.
However, it is probably more efficient to write the query like this:
SELECT f.*,
(SELECT COUNT(v.value)
FROM votes v
WHERE v.filmId = f.id and v.competitionId = '$competition->id'
) AS totalVotes
FROM films f
WHERE EXISTS (SELECT 1
FROM FilmCompetition fc
WHERE fc.FilmId = f.Filmid AND
fc.competitionId = '$competition->id'
)
ORDER BY TotalVotes DESC
This saves the outer aggregation, which should be a performance win. For this version, the indexes are FilmCompetition(FilmId, CompetitionId) and Votes(FilmId, CompetitionId).
The solution I actually ended up using was a little different but since Gordon answered the question I marked him as the correct answer.
My Solution
To actually fix this I did a slightly different approach, rather than trying to do all of this in SQL I did my three queries separately and then joined them together in PHP. While this can be slower in my case doing it my original way took about 15 seconds, doing it Gordons way took about 7, and doing it my new way took about 600ms.

How to use join for more than two table?

I have three tables.They are
tb_albums---->id,title, description
tb_photos---->id,album_id, photo
tb_tags---->id,album_id, tag
Here i want to get the albums details and it photos & it tags through tb_albums.id.
How to use join query here?
Normally, you can use as many tables in JOIN, as you'd like. Just add another JOIN statement. You can refer to k102's answer for the correct syntax (which doesn't produce correct result though).
But in this particular case you don't want to use simple JOIN on all tables, unless you have only one photo per album and only one tag per album. If you have more than one photo per and more than one tag per album, JOIN both tables on album_id in single query will produce Cartesian product of both, in other words all possible combinations of tags and photos from each albums. For N photos and M tags that's N * M results, instead of N + M.
Also, there is no point of joining with tb_albums, as you do not need to repeat information about each album for each photo and each tag.
Proper approach would be to have 3 separate simple SELECTs from each table and combining their result on application level.
If for some awkward reason you'd need to do that with one query, you can do something like:
SELECT * FROM tb_albums as A JOIN
(SELECT 'photo', id, photo as value FROM tb_photos
UNION ALL
SELECT 'tag', id, tag as value FROM tb_tags) as B ON B.album_id = A.id
Note, this is way less optimal than separate SELECTs, you should only do this if you have no other choice.
select * from tb_albums a
join tb_photos p on a.id = p.album_id
join tb_tags t on a.id = t.album_id

Mysql Query Relations M-M Table

I'm having a small problem making a query in MySQL.
I have the following tables:
member;
group;
member_has_group (this one has the columns id_group referes to the group id and id_member referes to member id)
I'm trying to make a query that gives me the members from a selected group. Can you help me?
I'm not familiar with join tables, but for the search i made i think thats probably one of the solutions.
Thanks in advance.
Elkas
If you know the group id
select member.* from member m
inner join member_has_group mg on m.id = mg.id_member
where mg.id_group = [x]
If you only know the group name
select member.* from member m
inner join member_has_group mg on m.id = mg.id_member
inner join group g on g.id = mg.id_group
where g.name = 'group name'
This is trival in SQL :
SELECT m.id_member, m.name
FROM member AS m
INNER JOIN member_has_group AS h ON (m.id_member=h.id_member)
WHERE (h.id_group=#my_id_group)
#my_id_group is the group id you have to give.
Yep, you need a join here.
SELECT *
FROM `member`
JOIN `group` ON member.id = group.id
JOIN `member_has_group` ON group.id = member_has_group.id
Depending on the information in your tables, you may not need the third table at all.You only need a connector table with you have a "many to many" relationship between then.
(Ignore the rest if you already know
about database normalization)
For example, if you had two tables, Authors and Books. Authors would contain fields such as Name, Publisher, Birthday, whatever is a property of the "author". Books would contain relevant "book" information. This is a "one-to-many" relationship. An author may be linked (via a field such as author_id) to several books, but a book can only have one author. You would not need a third table here.
Building on that, say you had a third table for "Character Names". This would be a list of main character names used in any of the books in the "Books" table. One of the characters happens to be named John Steele. John has a whole series of books written about him. In the Books table, several of the books may list John Steele as a character. While in the characters table, John Steele could be listed in several books. This is "many-to-many". You need a third table here. It would only have two fields. A book_id and character_id, one entry for each book that John Steele appears in.
MySql Manual on DB Normalization

Join SQL tables

Suppose that in table "ab" I have the names of the students that get along from class "a" and class "b", identically I have table "ac" and "bc". What SQL query should I use in order to get all the combinations possible of students who can form groups (i.e. "get along together")? And how can i extend this to n classes?
For example: John from class a gets along with Jen from class b and Steff from class c, and Jen and Steff get along. Therefore John, Jen and Steff can form a group).
For this I would create two tables, a student table (id, name, class) and a relationship table (student1, student2). You might also want to add a class table for the time, location etc of the class.
A friendship would have two relationships (2,3) and (3,2) to describe it as two way. One way might be a follower or fan of another student.
This will scale up to a lot more than 3 classes.
Then you can use multiple joins to get friends of friends and so on to an arbitrary depth.
Here is a query to get friends of friends (fof):
SELECT fof_details.*
FROM relationships r
INNER JOIN relationships fof
ON r.student2 = fof.student1
INNER JOIN student fof_details
ON fof_details.id = fof.student2
WHERE r.student1 = '12';
There are also database engines made specifically for doing graph modeling like this.
http://openquery.com/blog/graph-engine-mkii
This query should return all students who can be in one group with John.
WITH ABC AS (SELECT AB.A, AB.B, AC.C FROM (SELECT * FROM AB
INNER JOIN BC
ON AB.B=BC.B)
INNER JOIN AC
ON (AC.C=BC.C AND AB.A=AC.A))
SELECT STUDENT FROM (
SELECT AB.B STUDENT FROM ABC WHERE AB.A='John'
UNION
SELECT AC.C STUDENT FROM ABC WHERE AB.A='John')
GROUP BY STUDENT
PS.: Written fast without any syntax check, hope you'll be able to bring this to work :)
The initial query can be satisfied by the code
select ab.a, ab.b, ac.c
from
ab inner join
bc on ab.b = bc.b inner join
ac on ac.a = ab.a and bc.c = ac.c
Stepping up to n classes will get progressively more complex as n=4 would be the same query with the additional three joins
inner join ad on ab.a = ad.a
inner join bd on bd.b = ab.b and ad.d = bd.d
inner join cd on cd.c = ac.c and ad.d = cd.d
2 classes requires 1 table and no joins,
3 classes requires 3 tables and 2 joins,
4 classes requires 6 tables and 5 joins
So we can see it getting progressively more complex as we proceed
First you don't want to have a table for each class. You are capturing the same type of information in multiple tables and this is generally considered a bad practice. You want to "normalize" your data so that the same data exists in one place.
Second, name your tables appropriately so that you understand what you are actually trying to build. Maybe you are generalizing to mask what your intentions for the actual implementations are by using "ab" in the question, but if you are doing this in your actual code it will hurt you in the long run.
It appears you need a people table with names and a friends table where you track who is friends with who:
create table people ( id int, name char(128) );
create table friends ( id int, person_id int, friend_id int );
Then you just need to have the query to get the groups:
SELECT person.* FROM friends
INNER JOIN friends grp
ON friends.friend_id = grp.person_id
INNER JOIN people person
ON person.id = grp.friend_id
WHERE friends.person_id = 42;

Categories