尝试基本的 couchdb erlang 视图

Attempting a basic couchdb erlang view

我正在尝试做一个简单的视图,它只发出 id 以 "rating-" 开头的文档。我似乎根本无法在 couchdb 中调用任何 "string:" 函数。真的不知道该怎么做......我见过的每个例子都从不比较部分价值,总是比较整个价值。在 ecmascript 中,我可能只是做 if (!doc._id.indexOf('rating-'))。在下面的代码中,它抱怨提供给 split 的参数之一无效。

fun({Doc}) ->
    Id = proplists:get_value(<<"_id">>, Doc),
    DocChk = binary:split(Id, <<"-">>),
    case array:get(0, DocChk) of
      <<"rating-">> -> Emit(Id, nil)
    end
end.

我已经进行了几次尝试,我一直在 Erlang shell 中尝试一些东西,虽然我偶尔不会遇到语法错误,但我永远无法得到 运行 上的任何东西沙发数据库。这是我试图从 ecmascript 改编的整个视图。如您所见...我仍然停留在第 1 行哈哈。

地图

function (doc) {
  if (doc._id.indexOf('rating-') !== 0) return;
  if (!doc.isValid) return;
  var nps;
  doc.results.forEach(function (r) {
    if (r.type === 'Nps') {
      nps = r;
    }
  });

  if (!nps) return;
  var result = { t: 1, d: 0, p: 0 };

  switch (nps.score) {
    case 10:
    case 9:
      result.p++;
      break;
    case 8:
    case 7:
      break;
    default:
      result.d++;
      break;
  }

  var week = new Date(doc.dateCaptured.year, doc.dateCaptured.month -1, doc.dateCaptured.day);
  week.setDate(week.getDate() - ((week.getDay() === 0 ? 7 : week.getDay())-1));

  emit(['week', doc.companyId, week.getFullYear(), week.getMonth()+1, week.getDate()], result);
  emit(['day', doc.companyId, doc.dateCaptured.year, doc.dateCaptured.month, doc.dateCaptured.day], result);
  emit(['month', doc.companyId, doc.dateCaptured.year, doc.dateCaptured.month], result);
  emit(['year', doc.companyId, doc.dateCaptured.year], result);

  emit(['week-bylocation', doc.companyId, doc.locationId, week.getFullYear(), week.getMonth()+1, week.getDate()], result);
  emit(['day-bylocation', doc.companyId, doc.locationId, doc.dateCaptured.year, doc.dateCaptured.month, doc.dateCaptured.day], result);
  emit(['month-bylocation', doc.companyId, doc.locationId, doc.dateCaptured.year, doc.dateCaptured.month], result);
  emit(['year-bylocation', doc.companyId, doc.locationId, doc.dateCaptured.year], result);

  emit(['week-bysource', doc.companyId, doc.sourceId, week.getFullYear(), week.getMonth()+1, week.getDate()], result);
  emit(['day-bysource', doc.companyId, doc.sourceId, doc.dateCaptured.year, doc.dateCaptured.month, doc.dateCaptured.day], result);
  emit(['month-bysource', doc.companyId, doc.sourceId, doc.dateCaptured.year, doc.dateCaptured.month], result);
  emit(['year-bysource', doc.companyId, doc.sourceId, doc.dateCaptured.year], result);
}

减少

function (keys, values, rereduce) {
  var result = { t: 0, p: 0, d: 0 };
  values.forEach(function (v) {
    result.t += v.t;
    result.p += v.p;
    result.d += v.d;
  });

  return result;
}

这个视图的性能太慢了。这不会逐渐成为问题,但我必须为索引 6.3M 行设置种子,并且在总共 12 个内核的 3 节点集群上需要大约 12 小时。肯定cpu绑定。

编辑

多亏了 Hynek,我才能够移植我的地图功能。我确定它的 erlang 效率很低,但它似乎比对应的 ecmascript 快 30 倍。

fun({Doc}) ->
    case lists:keyfind(<<"_id">>, 1, Doc) of
        {_, <<"rating-", _/bytes>> = Id} -> 
          case couch_util:get_value(<<"isValid">>, Doc) of
            true -> 
              Results = proplists:get_value(<<"results">>, Doc),

              case lists:dropwhile(fun({R}) -> <<"Nps">> /= proplists:get_value(<<"type">>, R) end, Results) of
                [] -> ok;
                [{Nps} | _] -> 
                  Score = proplists:get_value(<<"score">>, Nps),
                  A = case Score of 
                    S when S > 8 -> [1, 0, 1];
                    S when S > 6 -> [0, 0, 1];
                    _ -> [0, 1, 1]
                  end,
                  CompanyId = proplists:get_value(<<"companyId">>, Doc),
                  LocationId = proplists:get_value(<<"locationId">>, Doc),
                  SourceId = proplists:get_value(<<"sourceId">>, Doc),
                  {DateCaptured} = proplists:get_value(<<"dateCaptured">>, Doc),
                  Year = proplists:get_value(<<"year">>, DateCaptured),
                  Month = proplists:get_value(<<"month">>, DateCaptured),
                  Day = proplists:get_value(<<"day">>, DateCaptured),
                  Emit([<<"year">>, CompanyId, Year], A),
                  Emit([<<"month">>, CompanyId, Year, Month], A),
                  Emit([<<"day">>, CompanyId, Year, Month, Day], A),

                  Emit([<<"year-bylocation">>, CompanyId, LocationId, Year], A),
                  Emit([<<"month-bylocation">>, CompanyId, LocationId, Year, Month], A),
                  Emit([<<"day-bylocation">>, CompanyId, LocationId, Year, Month, Day], A),

                  Emit([<<"year-bysource">>, CompanyId, SourceId, Year], A),
                  Emit([<<"month-bysource">>, CompanyId, SourceId, Year, Month], A),
                  Emit([<<"day-bysource">>, CompanyId, SourceId, Year, Month, Day], A)

              end;
            _ -> ok
          end;
        _ -> ok
    end
end.

问题是 binary:split/2 结果不是 array,所以您不能使用 array:get/2。好吧,任何经验丰富的 Erlanger 都会这样写:

fun({Doc}) ->
    case lists:keyfind(<<"_id">>, 1, Doc) of
        {_, <<"rating-", _/bytes>> = Id} -> Emit(Id, nil);
        _ -> ok
    end
end.

或者,如果您不喜欢冒险并希望坚持使用推荐的功能(可能会慢一点):

fun({Doc}) ->
    case couch_util:get_value(<<"_id">>, Doc) of
        <<"rating-", _/bytes>> = Id -> Emit(Id, nil);
        _ -> ok
    end
end.

编辑: Erlang 中 = 的含义需要更多解释。 Erlang 中的等号更像是数学中的等号,但不完全是。

1> X = 1.
1
2> 1 = X.
1
3> 1 = A = X.
1
4> A.
1
5> B = 1 = X.
1
6> B.
1
7> 1 = Z.
* 1: variable 'Z' is unbound
8> 2 = X.
** exception error: no match of right hand side value 1

那里发生了什么事? = 符号有两个不同的含义,并且与单个赋值 a.k.a 密切相关。捆绑。首先,它是表达式中的匹配运算符。你可以写一个匹配表达式来代替任何表达式:

 <Pattern> = <Expression>

在第 3 和第 5 个表达式中是最右边的 =,在上例中的其他表达式中是唯一的 =。在第一个表达式之后,X 已经绑定了值为 1 的变量,它的左边是一个模式。模式可以是简单值或变量或复合模式。变量可以是绑定的或未绑定的。如果变量未绑定,它将绑定到值。如果变量已经绑定,则检查匹配。如果在匹配表达式中使用,匹配失败会引发 badmatch 异常(class error),它以人类可读的格式显示在 shell 中,至于第 8 个表达式,因为我没有不知道 R17-ish。无论如何,您不能在匹配表达式中更改 = 运算符的左侧和右侧,因为变量绑定只能发生在模式中,例如= 的左操作数,如第 7 个表达式所示。

但是你也可以在模式中使用 = ,它作为统一,更类似于数学中的相等,你可以自由地左右交换。出于一个简单的原因,有经验的 Erlanger 习惯于按照我在示例中使用的方式编写模式。如果你阅读模式,你想知道值应该如何看起来像第一个和变量,你希望将值绑定为第二个不太重要的东西,或者你稍后在阅读代码的过程中需要知道的东西。

模式不仅出现在匹配表达式中,还出现在函数子句中(实际上示例中的 {Doc} 也是一种模式)以及 casetry , receive 表达式和列表理解生成器。模式匹配是 Erlang 语言本身最强大和最酷的特性之一,在我的例子中,我只展示了它的一瞥。您可以将复杂的数据结构解构为像 {_, <<"rating-", _/bytes>> = Id} 这样的元组。您可以模式匹配二进制文件,如本部分所示 <<"rating-", _/bytes>>.

二进制文件的模式匹配使用 Bit Syntax,这是一个强大的工具,它使在 Erlang 中实现二进制协议变得简单而有趣,但在这里我使用它来使代码比 ecmascript 对应的代码更高效和清晰。在这里,我只是明确表示我对以前缀 rating- 开头并继续任何内容(任何二进制 a.k.a 字节)_/bytes 的二进制文件感兴趣。 (我更喜欢使用 bytesbits 而不是 binarybitstring 因为它似乎更能表达意图。)