これはかなりうまく機能します。ただし、場合によっては、曲の合計に丸め誤差が発生します(1を超えないようにしてください)。
/*
 * It's more likely to fill the first two arrays from a query, but for 
 * the example I defined them like
 * genreId => value
 */
$genres = array(
    1 => 'rock',
    2 => 'pop',
    3 => 'dance',
    4 => 'folk',
    27 => 'classical'
);
$pointsPerGenre = array(
    1 => 5, //rock, 5 out of 10
    2 => 2, //pop, 2 out of 10
    3 => 1, //etc...
    4 => 10,
    27 => 7
);
$totalPoints  = array_sum($pointsPerGenre);
$numberOfSongs = 20;
$songsPerPoint = $numberOfSongs / $totalPoints;
$songsPerGenre = array();
foreach(array_keys($genres) as $genreId)
{
    $songsPerGenre[$genreId] = round($pointsPerGenre[$genreId] * $songsPerPoint);
}
$queryParts = array();
foreach($songsPerGenre as $genreId => $numberOfSongsPerGenre)
{
    $queryParts[] = "(SELECT * FROM TRACKS WHERE genre_id = $genreId ORDER BY RAND() LIMIT $numberOfSongsPerGenre)";
}
$query = implode("\nUNION\n", $queryParts);
これにより、次のクエリが出力されます(丸め誤差が表示されている場合、この場合、ユーザーは1つのボーナス曲を受け取ります)。
(SELECT * FROM TRACKS WHERE genre_id = 1 ORDER BY RAND() LIMIT 4)
UNION
(SELECT * FROM TRACKS WHERE genre_id = 2 ORDER BY RAND() LIMIT 2)
UNION
(SELECT * FROM TRACKS WHERE genre_id = 3 ORDER BY RAND() LIMIT 1)
UNION
(SELECT * FROM TRACKS WHERE genre_id = 4 ORDER BY RAND() LIMIT 8)
UNION
(SELECT * FROM TRACKS WHERE genre_id = 27 ORDER BY RAND() LIMIT 6)