代码之家  ›  专栏  ›  技术社区  ›  Damien Cassou

CouchDB视图为每个“组”返回一个元素

  •  0
  • Damien Cassou  · 技术社区  · 3 年前

    我的数据库中有大量如下所示的文档:

    {
        "_id": "7fa2e319f3b908818d1c6eda9205fc6f",
        "_rev": "3-9db3d8cc45c9a45b35c3981011e77bb5",
        "Guid": "2d69ba2e-972e-4659-8d3f-35f660229b6d",
        "CompanyId": "foo",
        "Date": "2021-02-12T08:59:48Z",
        "Author": "Me",
        ...
    }
    

    这个 CompanyId 密钥出现在所有文档中,并且多个文档具有相同的密钥 公司ID

    我正在寻找一种方法来获取最新版本的文档 Date 公司ID 价值换言之,我需要一个文档数组,每个文档具有不同的 公司ID

    这是一对map/reduce:

    function(doc) {
      emit(doc.CompanyId, {doc: doc, date: Date.parse(doc.Date)});
    }
    
    function(keys, values, rereduce) {
      var maxDate = 0;
      var maxDoc = '';
    
      for(i = 0; i<values.length; i++) {
        if(values[i].date>maxDate) {
          maxDate=values[i].date;
          maxDoc=values[i].doc;
        }
      }
      return {date: maxDate}
    }
    

    如果我通过 group=true 对于我的查询,上面的map/reduce对成功地按公司id返回了最新文档的日期:

    {
      "rows": [
        {
          "key": "a-testagency-556049-0897",
          "value": {
            "date": 1613123486000
          }
        },
        {
          "key": "a-testagency-556677-1317",
          "value": {
            "date": 1613123435000
          }
        }
      ]
    }
    

    但我想要的不仅仅是结果中的日期,我想要文档本身,所以我将最后一行更改为:

    return {date: maxDate, doc: maxDoc}
    

    不幸的是,这会引发以下错误:

    如果我再次将返回线更改为较小的值:

    return {date: maxDate, companyId: maxDoc.Author}
    

    然后查询现在返回 null 作为值,而不是日期和作者。

    0 回复  |  直到 3 年前
        1
  •  1
  •   RamblinRose    3 年前

    最后的代码片段使用PockDB演示了实现所需结果的一种方法(在几种方法中)。这是一个非常简单的解决方案,但结果是从2个请求而不是1个请求中获得的。

    要了解其工作原理,请查看 Reduce/Rereduce 在CouchDB文档中,特别是关于 rereduce . 这里的一篇深入的文章只是对该文档的重新整理。

    这是演示片段中的设计文档。

    {
        "_id": "_design/SO-66173759",
        "views": {
          "most_recent": {
            "map": `function (doc) {             
               if(doc.CompanyId && doc.Date) {                         
                  emit([doc.CompanyId,doc.Date],doc._id);
               }
            }`,
            "reduce": `function(keys,values,rereduce) {               
               return values[0];
            }`
          }
        }
      }
    

    emit生成复杂键[companyId,Date],value=document\u id。例如:

    [Acme Amalgamated,2012-07-26T18:57:12.409Z]   7fb19b37-5baa-4c78-b122-fd0bc67253d0
    [Acme Amalgamated,2013-08-16T02:53:12.062Z]   93d480a5-a666-4fca-b4a4-c1ba7935de92
    [Acme Amalgamated,2013-08-23T22:12:14.401Z]   96a236b1-50d6-4b7e-af45-d05432ca7847
    [Acme Amalgamated,2015-05-20T13:17:21.500Z]   ad55f6e7-6c61-4793-a75c-9601debd5eaf
    [Acme Amalgamated,2015-12-17T09:18:33.741Z]   cf1a0844-aa54-42e2-a9d6-16e80f5c420a
    [Acme Amalgamated,2016-08-24T04:01:15.551Z]   0658a417-901f-4cc7-a005-c10c72d2720c
    [Acme Amalgamated,2020-06-19T07:58:44.680Z]   56126238-496a-475e-9001-8a4a86e055a3 **
    [Cyberdyne Systems,2012-02-02T17:07:30.692Z]    d8a2b649-10b0-467b-b7e1-304b13f7560a
    [Cyberdyne Systems,2012-05-31T17:47:15.607Z]    64699ddb-e8f5-449f-b1cb-804ccddbbfb2
    [Cyberdyne Systems,2012-07-11T13:08:00.879Z]    1aa285f4-dc4b-49e4-9f0b-331b3d552dad
    [Cyberdyne Systems,2014-08-12T22:43:47.651Z]    f5597fb0-e37e-4a62-8ef2-244140b0439b
    [Cyberdyne Systems,2015-03-25T04:54:40.459Z]    c80f58d3-9fb1-466f-9fd2-2b3f86331fc3
    [Cyberdyne Systems,2017-08-13T02:42:49.530Z]    c2b2d636-b444-4216-9b0b-e982ea467e51 **
    [Lorem Ipsum Inc.,2010-01-26T21:31:34.752Z]   d6e2b66d-286f-48b9-b678-7afeca2c3c01
    [Lorem Ipsum Inc.,2012-07-04T02:29:20.509Z]   c7d528fe-ce4f-4c35-a054-39f726962b4a
    [Lorem Ipsum Inc.,2012-10-26T01:21:11.765Z]   773af027-1fdd-4f41-b1b1-8d5169871684
    [Lorem Ipsum Inc.,2012-12-15T22:27:12.999Z]   78d60df3-aed6-4cab-8071-4d08ccfd0184
    [Lorem Ipsum Inc.,2014-11-04T02:03:49.328Z]   098f600c-8059-44d5-a526-b67f0bbeb609
    [Lorem Ipsum Inc.,2017-04-30T14:54:17.967Z]   639ddd9c-9a41-42fd-b1be-7fea8eb4f3ec
    [Lorem Ipsum Inc.,2017-11-21T10:45:53.152Z]   ee2a7445-276d-4e9c-9f9d-1fa76d87741f **
    

    一般来说,发送文档_id是多余的,因为视图查询返回文档id,但是在这种情况下,有一个很好的理由这样做,这将在后面很明显。

    密切关注索引的排序方式;在每个公司集团内,最近的日期为 公司ID组的条目。

    现在使用简洁的reduce函数

    function(keys,values,rereduce) { return values[0]; }
    

    {
       reduce: true,
       group_level: 1,
       descending: true
    }
    

    这个 descending reduce 函数将在中接收键和值 逆序 减少 将始终根据日期字段生成最新的文档id,即使在 rereduce = true

    给定前面的示例视图,使用上述参数访问视图将返回以下键/值行

    [Lorem Ipsum Inc.]    ee2a7445-276d-4e9c-9f9d-1fa76d87741f    
    [Cyberdyne Systems]   c2b2d636-b444-4216-9b0b-e982ea467e51
    [Acme Amalgamated]    56126238-496a-475e-9001-8a4a86e055a3
    

    它就在那里——召唤 _all_docs

    {
      include_docs: true,
      keys: [
       "ee2a7445-276d-4e9c-9f9d-1fa76d87741f",
       "c2b2d636-b444-4216-9b0b-e982ea467e51",
       "56126238-496a-475e-9001-8a4a86e055a3"
      ]
    }
    

    将返回按companyId区分的最新日期的文档。

    这里有一个演示片段。该代码生成20个随机文档并生成压缩视图数据。

    重要的
    _changes 喂养YMMV。

    async function view_reduce() {
      let result = await db.query('SO-66173759/most_recent', {
        reduce: true,
        group_level: 1,
        descending: true
      });
      // show  
      gel('view_reduce').innerText = result.rows.map(row => `${row.key}\t${row.value}`).join('\n');
    
      return result;
    }
    
    async function showMostRecentDocs() {
      // Use results from reduce to get documents
      let result = await view_reduce();
      // The result row values are document ids; use allDocs to fetch the docs 
      result = await db.allDocs({
        include_docs: true,
        keys: result.rows.map(row => row.value)
      });
      //show  
      gel('view_most_recent').innerText = result.rows.map(row => [row.doc.CompanyId, row.doc.Date, row.doc.Author].join('\t')).join('\n');
    }
    
    async function showViewKeyValues() {
      let result = await db.query('SO-66173759/most_recent', {
        reduce: false,
        include_docs: false
      });
      //show  
      gel('view_key_value').innerText =
        result.rows.map(row => `[${row.key}]\t${row.value}`).join('\n');
    }
    
    async function showViewDocs() {
      let result = await db.query('SO-66173759/most_recent', {
        reduce: false,
        include_docs: true
      });
      //show  
      gel('view_docs').innerText = result.rows.map(row => [row.doc.CompanyId, row.doc.Date, row.doc.Author].join('\t')).join('\n');
    }
    
    function getDocsToInstall(count) {
      const sourceDocs = [{
          "CompanyId": "Acme Amalgamated",
          "Author": "Wile E. Coyote",
        },
        {
          "CompanyId": "Acme Amalgamated",
          "Author": "Road R. Unner",
        },
        {
          "CompanyId": "Lorem Ipsum Inc.",
          "Author": "Caesar Augustus",
        },
        {
          "CompanyId": "Lorem Ipsum Inc.",
          "Author": "Marcus Aurelius",
        },
        {
          "CompanyId": "Cyberdyne Systems",
          "Author": "Miles Dyson",
        }
      ];
      // design document
      const ddoc = {
        "_id": "_design/SO-66173759",
        "views": {
          "most_recent": {
            "map": `function (doc) {             
               if(doc.CompanyId && doc.Date) {                         
                  emit([doc.CompanyId,doc.Date],doc._id);
               }
            }`,
            "reduce": `function(keys,values,rereduce) {               
               return values[0];
            }`
          }
        }
      };
    
      // create a set of random documents.
      let docs = new Array(count);
      const dateSeed = [new Date(2010, 0, 1), new Date(), 0, 24];
      while (count--) {
        let doc = Object.assign({}, sourceDocs[Math.random() * sourceDocs.length | 0]);
        doc.Date = randomDate(...dateSeed).toISOString();
        docs[count] = doc;
      }
      docs.push(ddoc);
      return docs;
    }
    
    const db = new PouchDB('SO-66173759', {
      adapter: 'memory'
    });
    // install docs and show view in various forms.
    (async() => {
      await db.bulkDocs(getDocsToInstall(20));
      await showViewDocs();
      await showViewKeyValues();
      return showMostRecentDocs();
    })();
    
    const gel = id => document.getElementById(id);
    
    /*
    https://stackoverflow.com/questions/31378526/generate-random-date-between-two-dates-and-times-in-javascript/31379050#31379050
    */
    function randomDate(start, end, startHour, endHour) {
      var date = new Date(+start + Math.random() * (end - start));
      var hour = startHour + Math.random() * (endHour - startHour) | 0;
      date.setHours(hour);
      return date;
    }
    <script src="https://cdn.jsdelivr.net/npm/pouchdb@7.1.1/dist/pouchdb.min.js"></script>
    <script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
    <div>View: distinct, most recent</div>
    <pre id='view_most_recent'></pre>
    <hr/>
    <div>View: reduce result</div>
    <pre id='view_reduce'></pre>
    <hr/>
    <div>View: key/value</div>
    <pre id='view_key_value'></pre>
    <hr/>
    <div>View: docs</div>
    <pre id='view_docs'></pre>

    不管好坏