3

我在 CouchDB 中的文档结构如下所示:

{
 "_id": "0a68cdbe4a7f3abf4046bc",
 "_rev": "1-1508",
 "score": {"math":90, "physics": 88, "chemistry": 60},
 "student_name": "Mike"
}

我需要在前端显示以下统计信息来设置学生的个人资料:

  • 给定一个学生 _id,我如何检索学生在每门课程中的排名;
  • 给定一个学生 _id,我如何检索学生的总分排名(数学+物理+化学)。

假设我只有 2 个学生,第 2 个学生的记录如下:

{
 "_id": "0a68cdbe2344a3abf4046bc",
 "_rev": "1-1608",
 "score": {"math":80, "physics": 98, "chemistry": 90},
 "student_name": "Jane"
}

所以迈克的排名应该是:

math: 1
physics: 2
chemistry: 2
total: 2

简的等级应该是

math: 2
physics: 1
chemistry: 1
total: 1

如果我没有清楚地说明问题,请告诉我。

我没有弄清楚创建视图以获得排名的方法。我试过的:

  • 创建将分数映射到学生信息的视图。然后我可以查询一个分数范围,让学生知道他们的分数在那个范围内。

编辑:通过用户名查询和检索排名的功能不需要仅由视图实现。欢迎任何想法!

Edit2:课程数量为 1K 到 3K。学生人数为 1M 到 2M。

4

4 回答 4

6

我认为仅仅用一个视图来做你想做的事情是不可能的。会尝试这样的地图功能:

function(doc) {
  emit (["math", doc.score.math], doc.student_name);
  emit (["physics", doc.score.physics], doc.student_name);
  emit (["chemistry",doc.score.chemistry], doc.student_name);
  emit (["total",doc.score.math+doc.score.physics+doc.score.chemistry], doc.student_name);
}

然后我会按课程查询。这将返回按分数排序的学生列表。在那之后,我认为您必须以编程方式选择软件中的等级。

我认为 reduce 函数没有用,因为该函数不会缩小结果集,还因为我无法想出一种通过学生姓名查询并同时拥有整个学生列表的方法。我也认为一个列表是没有用的,因为我再一次看不到如何让列表知道学生的名字,同时让整个学生集都在结果中。

于 2012-11-18T19:51:55.557 回答
2

也许排序视图和列表的组合会起作用。

您的 resultByChemistryScore 视图看起来像这样

function(doc) {
    emit(doc.score.chemistry, [doc._id, doc.student_name]);
}

然后您的 GET 请求将类似于http://localhost:5984/results/_design/results/_view/resultByChemistryScore?descending=true此时您始终可以通过使用offsetlimitGET 查询参数来实现分页。

从这一点开始,alist可以计数,直到它到达您指定的学生。

你的列表函数“rank”看起来像这样

function(head, req) {
    start({ "headers": { "content-type": "application/json" } } );
    var row, rank = 0; 
    while ( row = getRow() ) {
        if ( row.id == req.query.id ) break;
        // increment rank if not a tie
        if ( old_row != null && old_row.key != row.key ) 
           rank++;
        old_row = row;
    }; 
    send( JSON.stringify( { "rank" : rank } ) );
}

你的要求基本上是http://localhost:5984/results/_design/results/_list/rank/resultByChemistryScore?id=fet&descending=true

这不是很漂亮,我给你。如果您说...第 1,000,000 名最好的化学学生,服务器可能需要一段时间才能浏览整个列表。但服务器肯定比客户端更容易做到这一点。

编辑 添加了领带处理案例

于 2012-11-21T01:10:49.250 回答
2

所以我不认为有一个解决方案可以完全在 CouchDB 中完成,它会为学生、科目配对检索单个值。但是,可以创建一个 map/reduce 视图,它几乎可以生成您正在寻找的内容。然后可以使用该视图的结果来查找学生、科目对的排名。

我们首先使用与 joscas 建议的地图非常相似的地图构建视图。唯一的区别是主题名称不是硬编码的:

地图.js

function(doc) {
    var total = 0;
    for (var subject in doc.score)  {
        var score = doc.score[subject];
        emit([subject, score], doc.student_name);
        total += score;
    }
    emit(["total", total], doc.student_name);
}

我们将它与一个 reduce 函数配对,该函数将为给定 agroup=truegrouping_level=1

减少.js

function(keys, values) {
    var rankings = {};              // In order to return ties, a simple array can't be used.
    var rank = 0;
    var place = 0;
    var last_score = -1;
    for (var i = 0; i < values.length; i++) {
        var name = values[i];
        var score = keys[i][0][1];  // The 0th element of the key is the [subject, score] array.
        if (score == last_score) {
            // Tie, add another student to this rank.
            place++;
        } else {
            // Not a tie, create a new rank.
            rank += (place + 1);
            rankings[rank] = new Array();
            place = 0;
            last_score = score;
        }
        rankings[rank][place] = name;
    }
    return rankings;
}

数据

我在数据集中添加了第三个学生,并创建了一些联系以使其变得有趣。这是使用的数据:

{
    "_id": "ce6b2cd97e73258014679ab7bb9e7cdc",
    "_rev": "2-b62581d22c186bfc8ebe1703a2dfb506",
    "score": {
        "chemistry": 60,
        "math": 90,
        "physics": 88
    },
    "student_name": "Mike"
}

{
    "_id": "ce6b2cd97e73258014679ab7bb9e8ada",
    "_rev": "5-94d6cfbd3cf22f903ebc306570d1f1af",
    "score": {
        "chemistry": 90,
        "math": 90,
        "physics": 98
    },
    "student_name": "Jane"
}

{
    "_id": "ce6b2cd97e73258014679ab7bb9e960b",
    "_rev": "1-d8c7fe88de63cf3d6e9743696f96aad0",
    "score": {
        "chemistry": 61,
        "math": 89,
        "physics": 88
    },
    "student_name":
    "Charlie"
}

结果

视图保存为排名,可以这样查询:

http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1

这产生了这个结果:

{
    "rows":[
        {"key":["chemistry"],"value":{"1":["Jane"],"2":["Charlie"],"3":["Mike"]}},
        {"key":["math"],"value":{"1":["Jane","Mike"],"3":["Charlie"]}},
        {"key":["physics"],"value":{"1":["Jane"],"2":["Charlie","Mike"]}},
        {"key":["total"],"value":{"1":["Jane"],"2":["Charlie","Mike"]}}
    ]
}

可以像这样按主题查询视图(假设最低分数为0,最高分数为100):

http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1&startkey=%5B%22math%22,0%5D&endkey=%5B%22math%22,100%5D

(没有url编码):

http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1&startkey=["math",0]&endkey=["math",100]

这产生了这个结果:

{
    "rows":[
        {"key":["math"],"value":{"1":["Jane","Mike"],"3":["Charlie"]}}
    ]
}

可以使用 Javascript(或其他客户端技术)搜索生成的词典,以确定学生在单个(或所有)科目中的排名。

于 2012-11-26T16:27:52.400 回答
1

我有一个基于@joscas 回答的想法。您可以像这样创建视图:

key         -> value
---------------------
("math", 0) -> 2
("math", 1) -> 3
("math", 2) -> 5
....
("math", 100) -> 50

我假设每门课程的分数范围是从 0 到 100。想法是:

  • 对于每门课程,您汇总分数落在该分数桶中的学生人数(每门课程有 101 个桶,即 frombucket0bucket100)。
  • 然后,您要么使用@joscas 指出的链式视图,要么使用另一个外部程序来获取得分直方图的累积分布,如下所示。

key         -> accumulative value
------------------------------------
("math", 0) -> 2
("math", 1) -> 5
("math", 2) -> 10
....
("math", 99) -> 32324
("math", 100)-> 32374

给定课程名称c和您的分数s,第二个视图会告诉您how many students play not as good as you in this course,您可以从中得出排名n-#s,其中n是注册的总学生人数c#s是获得低于分数的学生人数s。例如,查询"math", 99将返回 32374-32324 = 50,这是在“数学”中获得 99 的学生的排名。

对于问题的总分部分,您可以使用类似的想法,但更改存储桶的大小和数量。

于 2012-11-29T23:13:56.217 回答