在复杂的MySQL查询中进行排序和分组

时间:2013-05-13 16:25:38

标签: php mysql d3.js

我需要做一组相当复杂的MySQL查询来生成适合于从分配给不同音乐艺术家的标签数据库中绘制D3的数据。相关表中的行(称为“lastfm_annotations”)是:user_id,artist_id,tag_id和tag_month(即,我们记录了特定用户在特定时间用特定标签标记特定艺术家的许多实例)。这都嵌入在php脚本中。

我最终需要生成的是一个JSON对象,对于给定范围内的日期,它包含当月使用给定艺术家的每个唯一标记的次数(包括其中日期的零数)没有使用给定的标签。

这是我到目前为止(假设$ itemID和artist_id在这里可以互换):

$cal = array();
$result = mysql_query("select date from calendar;");
// this just gets all possible dates, but of course could be changed to get a different date range
if (!$result) {
    echo 'Could not run query: ' . mysql_error();
    exit;
}

for ($i = 0; $i < mysql_num_rows($result);  $i++) {
    $date =mysql_fetch_row($result)[0];
    $result2 = mysql_query("select t2.tag_id, case when t1.freq is null then 0 else t1.freq end as freq from (select distinct tag_id from lastfm_annotations where artist_id='" . $itemID . "') t2 left join (select tag_id, count(*) as freq from lastfm_annotations where artist_id='" . $itemID . "' and tag_month='" . $date . "'   group by tag_id) as t1 on t2.tag_id = t1.tag_id group by t2.tag_id");    
    $current = array();
    $current['date'] = $date;
    for ($j = 0; $j < mysql_num_rows($result2); $j++) {
        $row = mysql_fetch_row($result2);
        $tag = $row[0];
        $freq = $row[1];
        $result3 = mysql_query("select tag_name from lastfm_taglist where tag_id ='" . $tag . "' limit 1;");
        $tagName = mysql_fetch_row($result3)[0];
        $current[$tagName] = $freq;
    }
    array_push($data, $current);
}
echo json_encode($data); 

(编辑:大查询如下)

select t2.tag_id
     , case 
         when t1.freq is null then 0 
         else t1.freq 
       end as freq 
  from 
       (select distinct tag_id 
          from lastfm_annotations 
         where artist_id='$itemID') t2 
       left join 
       (select tag_id, count(*) as freq 
          from lastfm_annotations 
         where artist_id='$itemID' 
           and tag_month='$date'
         group by tag_id) as t1 
       on t2.tag_id = t1.tag_id 
 group by t2.tag_id

(结束编辑。)

这很有效,但是(至少)有两个我无法弄清楚的大问题。首先,在大丑的SQL查询中,我正在做多余的工作调用

(select distinct tag_id from lastfm_annotations where artist_id='" . $itemID . "')

每次我循环,即使每次都是相同的值。知道如何解决这个问题吗?也许有可能以某种方式将唯一的tag_ids保存为php数组,然后将其插入到查询中?

其次,我需要确保标签始终按其整体频率排序(即在所有时间,而不仅仅是在特定月份内),但我不确定如何做到这一点。我可以使用类似的查询来获得正确的序列:

select tag_id, count(*) as freq from lastfm_annotations where item_id=XXX order by freq desc

但我需要确保循环中的每个查询以相同的顺序返回标记。有任何想法吗?当我真正开始绘制数据时,或许在d3中处理排序会更好,但是当我进行SQL调用时,如果数据以正确的顺序启动会更好。

抱歉这个大问题,谢谢你的帮助!

1 个答案:

答案 0 :(得分:0)

这是一个为每位艺术家执行一次的查询,而不是每个艺术家 - 月份组合。它通过加入artist_id上的子查询然后过滤where子句中的artist_id = $itemID来解决您的第一个问题。数据库引擎在处理查询时应该将条件下推到子查询中,因此它不会像看起来那么低效,并且因为它不会在月份循环内调用,所以整体上应该做的工作少。

通过从第一个子查询获取整体频率,并按该频率按降序排列整个结果集,解决了第二个问题。这将把标记最多的月份放在第一位。

这样做的缺点是结果中没有表示没有标签的月份。您可以在应用程序逻辑中解决这个问题(例如,通过跟踪每个标记的日期范围中的哪些月份,然后合成“0”行)。还应该可以扩展查询以包含缺失的月份,但由于复杂性,除非您感兴趣,否则我不会详细介绍。

select t1.tag_id
     , t2.tag_month
     , t2.freq as month_freq
     , t1.freq as total_freq
  from (select tag_id
             , artist_id
             , count(*) as freq
          from lastfm_annotations
         group by tag_id, artist_id) t1
       inner join
       (select tag_id
             , tag_month
             , artist_id
             , count(*) as freq
          from lastfm_annotations 
         group by tag_id, tag_month, artist_id) t2
       on t1.artist_id = t2.artist_id and t1.tag_id = t2.tag_id
 where t2.tag_month between '$dateRangeStart' and '$dateRangeEnd'
   and t1.artist_id = '$itemID'
 order by total_freq desc, t1.tag_id