【问题标题】:Ordering and grouping in a complex MySQL query复杂 MySQL 查询中的排序和分组
【发布时间】:2013-05-13 16:25:37
【问题描述】:

我需要执行一组相当复杂的 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 调用时数据以正确的顺序开始,那会更好。

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

【问题讨论】:

  • 感谢 Alanyst 的编辑 - 现在可读性更高了。
  • 旁注:您可以在查询中使用更简洁的ifnull(t1.freq, 0) 而不是case when t1.freq is null then 0 else t1.freq end。
  • 如果标签 'foo' 在特定月份用于艺术家 A,但从未用于艺术家 B,如果艺术家 B 的结果中有标签 'foo' 的行(freq == 0),还是 B 的结果应该只有 B 使用过的标签?
  • B 的结果应该只包含艺术家 B 使用过的标签(至少一次)。原则上,我不介意在其中使用频率为零的其他标签,但数据库中有大约一百万个唯一标签,因此这会使数据结构变得不合理地大(和稀疏)。

标签: php mysql d3.js


【解决方案1】:

以下查询将为每位艺术家执行一次,而不是针对每个艺术家月的组合执行一次。它通过加入 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

【讨论】:

  • 这似乎工作得很好 - 谢谢!我所做的唯一编辑也是按 tag_month 排序,但这是一个非常微不足道的更改。我唯一关心的是执行时间……我用一个任意艺术家 ID 运行它,生成结果大约需要 2 小时……我的目标是在交互式可视化工具中部署它,但这实际上是不可能的以那种执行时间。仍然接受,因为它确实回答了我所有的问题。不过,如果您对执行时间问题有任何想法,我们将不胜感激。
  • 表上有哪些索引?
  • user_id+item_id+tag_id 上的复合主键,以及 item_id、artist_id、tag_id 和 tag_month 上的(非唯一)索引。其中一些可能是多余的,因为我已经调整数据库结构一段时间了。澄清一下,每个项目(专辑、艺术家或歌曲)都有一个唯一的 ID,但每个注释(标记项目的实例)都有一个关联的艺术家 ID。以下是 db 结构的屏幕截图,可以让事情更清楚:indexes、table structure。
  • 哦,你可以忽略“tag_date”列。仅使用“tag_month”。请记住,该表总共有大约 5000 万个条目。
猜你喜欢
  • 2015-05-11
  • 2018-08-12
  • 1970-01-01
  • 2016-08-01
  • 1970-01-01
  • 2012-09-07
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
相关资源
最近更新 更多