I have what (with my limited MYSQL knowledge) is a relatively complex query:
$sQuery = "
SELECT SQL_CALC_FOUND_ROWS
songsID, song_name, artist_band_name, author, song_artwork, song_file,
song_description, uploaded_time, emotion, tempo,
user, happiness, instruments, similar_artists, play_count,
projects_count,
rating, ratings_count, waveform, datasize, display_name, user_url,
IF(user_ratings_count, 'User Voted', 'Not Voted') as voted
FROM (
SELECT
sp.songsID, projects_count,
AVG(rating) as rating,
COUNT(rating) AS ratings_count,
COUNT(IF(userid=$userid, 1, NULL)) as user_ratings_count
FROM (
SELECT songsID, COUNT(*) as projects_count
FROM $sTable s
LEFT JOIN $sTable2 p ON s.songsID = p.songs_id
GROUP BY songsID) as sp
LEFT JOIN $sTable3 r ON sp.songsID = r.songid
GROUP BY sp.songsID) as spr
JOIN $sTable s USING (songsID)
LEFT JOIN $sTable5 q ON s.user = q.ID
$sWhere
$sOrder
$sLimit
This query gets all the information about a given song from 5 different tables.
the variable $sWhere builds the WHERE part of the query and depends on the data sent to the server in order for this list of songs to be dynamically filterable based on the user input.
$sOrder and $sLimit order and limit the results again based on user input.
I now need to add a 6th table in to this equation which contains a list of genre’s (wp_genres), this table is linked to the songsID column in $sTable via a link table (wp_song_genres) with the following structure:-
$sTable(wp_songs) $sTable6(wp_genre_songs) $sTable7(wp_genres)
songsID* song_id* genre_id**
column2 genre_id** genre_name
column3 icon_url
column4
column5
etc...
The asterisks denote the links.
What I need to do is to allow my dynamic filter of the song results to be able to filter the list of songs by Genre but I can’t work out how to introduce the new table(s) ($sTable6 and $sTable7) into the query.
Below is the code generating the WHERE part of my query:
This part takes the text recieved via ‘sSearch’ and searches the columns for any matching text.
$sWhere = "";
if ( $_GET['sSearch'] != "" )
{
$aWords = preg_split('/\s+/', $_GET['sSearch']);
$sWhere = "WHERE (";
for ( $j=0 ; $j<count($aWords) ; $j++ )
{
if ( $aWords[$j] != "" )
{
$sWhere .= "(";
for ( $i=0 ; $i<count($aColumns) ; $i++ )
{
$sWhere .= $aColumns[$i]." LIKE '%".mysql_real_escape_string( $aWords[$j] )."%' OR ";
}
$sWhere = substr_replace( $sWhere, "", -3 );
$sWhere .= ") AND ";
}
}
$sWhere = substr_replace( $sWhere, "", -4 );
$sWhere .= ')';
}
This part is a ‘range’ filter so that the user can input a minimum tempo value and a maximum tempo value (done via a jquery range slider on the font end). The data is recieved via sSearch_*.
/* Individual column filtering */
for ( $i=0 ; $i<count($aColumns) ; $i++ )
{
if ( $_GET['bSearchable_'.$i] == "true" && $_GET['sSearch_'.$i] != '' )
{
if ( $sWhere == "" )
{
$sWhere = "WHERE ";
}
else
{
$sWhere .= " AND ";
}
$columnFilterValue = mysql_real_escape_string($_GET['sSearch_' . $i]);
// check for values range
$rangeSeparator = "~";
if (!empty($rangeSeparator) && strstr($columnFilterValue, $rangeSeparator)) {
// get min and max
$columnFilterRangeMatches = explode('~', $columnFilterValue);
// get filter
if (empty($columnFilterRangeMatches[0]) && empty($columnFilterRangeMatches[1]))
$sWhere .= " 0 = 0 ";
else if (!empty($columnFilterRangeMatches[0]) && !empty($columnFilterRangeMatches[1]))
$sWhere .= $aColumns[$i] . " BETWEEN '" . $columnFilterRangeMatches[0] . "' and '" . $columnFilterRangeMatches[1] . "' ";
else if (empty($columnFilterRangeMatches[0]) && !empty($columnFilterRangeMatches[1]))
$sWhere .= $aColumns[$i] . " < '" . $columnFilterRangeMatches[1] . "' ";
else if (!empty($columnFilterRangeMatches[0]) && empty($columnFilterRangeMatches[1]))
$sWhere .= $aColumns[$i] . " > '" . $columnFilterRangeMatches[0] . "' ";
} else {
$sWhere .= $aColumns[$i] . " LIKE '%" . $columnFilterValue . "%' ";
}
}
}
The data for the genre_id’s will be coming in via sSearch_23. i.e. $_GET[‘sSearch’] will contain the genre_id I wish to filter the results by. Please note that I need to be able to add the results of many genre tags together.
If a user chooses genre:rock (genre_id: 1) then all songs in $sTable that have a genre_id:1 associated with them should be returned. (along with the rest of the information about the songs as retrievedby my current query.
But… If the user selects genre:rock (genre_id:1) AND genre:pop (genre_id:2) AND genenre:rap (genre_id:3) then all songs with ANY of those tags should be returned.
My apologies that this is such a complex question and I realise I may not have articulated it well. Also I understand that this is all quite specific to my current code. Regardless I really hope someone can help me or at least pint me in the right direction.
Many thanks!
EDIT:
I have just anaged to get his to ‘nearly’ work by adding the folling into my query:
LEFT JOIN (
SELECT igs.song_id, igs.genre_id, g.genre_name
FROM $sTable6 AS igs
JOIN wp_genres AS g
ON igs.genre_id = g.genre_id
) as gs
ON songsID = gs.song_id
So that the whole thing now looks like:-
$sQuery = "
SELECT SQL_CALC_FOUND_ROWS
songsID, song_name, artist_band_name, author, song_artwork, song_file,
genre, song_description, uploaded_time, emotion, tempo,
user, happiness, instruments, similar_artists, play_count,
projects_count,
rating, ratings_count, waveform, datasize, display_name, user_url, genre_id,
IF(user_ratings_count, 'User Voted', 'Not Voted') as voted
FROM (
SELECT
sp.songsID, projects_count,
AVG(rating) as rating,
COUNT(rating) AS ratings_count,
COUNT(IF(userid=$userid, 1, NULL)) as user_ratings_count
FROM (
SELECT songsID, COUNT(*) as projects_count
FROM $sTable s
LEFT JOIN $sTable2 p ON s.songsID = p.songs_id
GROUP BY songsID) as sp
LEFT JOIN $sTable3 r ON sp.songsID = r.songid
GROUP BY sp.songsID) as spr
LEFT JOIN (
SELECT igs.song_id, igs.genre_id, g.genre_name
FROM $sTable6 AS igs
JOIN wp_genres AS g
ON igs.genre_id = g.genre_id
) as gs
ON songsID = gs.song_id
JOIN $sTable s USING (songsID)
LEFT JOIN $sTable5 q ON s.user = q.ID
$sWhere
$sOrder
$sLimit
The queries by genre_id now happen perfectly and dynamically as intended. However I am getting dupilcate rows when no genre_id is specified in the WHERE part of the query. Each song that has an entry in the songs/genres link table gets duplicated.
First of all is my current solution a good one in terms of optimization/best practices? Secondly, how can I prevent these duplicate rows?
NOTE: I do already have a unique primary key across both columns in the link table.
http://hackmysql.com/case4
I think this would serve as a good read for you, especially considering your database may reach huge amounts of rows. Using index keys in your mysql table can greatly speed up queries where one table must draw from another table or where one table has many rows which may have similar values in one column but not others. This keeps them from having to scan the whole table, at least to my knowledge.