如何使用 pouchdb-find 为对象数组创建可用索引?

How do I create a usable index for an array of objects using pouchdb-find?

我只想获取至少与 groupIDs 之一匹配的 groups 的数据。我已经尝试了一些解决方案,但我仍然做错了。假设我们有这样的数据。

const groupIDs = ['124', '245', '678', '999', '111'];

const data = [
  {
    _id: 'example:123',
    _rev: 'example',
    name: 'John',
    groups: [
      { groupID: '124', type: 'ADMIN' },
      { groupID: '345', type: 'PRACTITIONER' },
      { groupID: '678', type: 'PATIENT' },
    ]
  },
  {
    _id: 'example:456',
    _rev: 'example2',
    name: 'Yoda',
    groups: [
      { groupID: '124', type: 'ADMIN' },
      { groupID: '345', type: 'PRACTITIONER' }
    ]
  },
];

这是我的尝试:

db.createIndex({
  index: { fields: ['groups.[].groupID'] }
})
.then((result) => {
  db.find({
    selector: { groups.groupID: { $in: groupIDs } },
    use_index: results.name
  })
})

这是使用 Mango 实现预期结果的众多方法之一。我还包括了一个示例 Map/Reduce.

对于 Mango,重点是创建索引和选择器。索引是这样的

 db.createIndex({
    index: {
       fields: ['groups'],
       ddoc: 'my-index'
    }
 });

注意我更喜欢将索引命名为 (ddoc)。

因此我们需要与数组字段一起使用的组合运算符,其中有 $in$elemMatch 运算符。

从 CouchDB 中查找文档[1]:

The $elemMatch operator matches and returns all documents that contain an array field with at least one element matching the supplied query criteria.

此外,$in 文档指出

The document field must exist in the list provided.

太棒了!让我们使用 $elemMatch$in 组合运算符在 groups 数组中查找一组组 ID。

 selector: {
    groups: {
      $elemMatch: {
         groupID: { $in: groupIDs }
      }
   }

以下代码片段演示了使用上述选择器的 Mango 查询和使用 keys 查询选项的 Map/Reduce 解决方案。

两者之间的意义是Map/Reduce为每一个匹配生成一个文档,这意味着可能有冗余文件; Mango 结果集 returns 一个文档,如果它匹配 一次或多次 次。

// convenience
const gel = id => document.getElementById(id);

const all_docs = 'allDocs';
const groups_groupID = 'groupID';
const groups_groupIDView = 'groupIDView';
const g_view_result = 'view_result';
const g_groupIdHints = 'groupIdHints';

const queryFns = {};
// show all the documents used in this example.
queryFns[all_docs] = () => db.allDocs({
  include_docs: true
});
// Mango - find one or more groupIDs in the groups array.
// @groupIDs is an array of one or more values.
queryFns[groups_groupID] = (groupIDs /*array*/ ) => {
  const query = {
    selector: {
      groups: {
        $elemMatch: {
          groupID: {
            $in: groupIDs
          }
        }
      }
    },
    use_index: "my-index"
  }
  return db.find(query);
}

// Map/Reduce - query the groupIDView for one or more groupIDs
// @groupIDs is an array of one or more values.
queryFns[groups_groupIDView] = (groupIDs /*array*/ ) => db.query(
  groups_groupIDView, {
    include_docs: true,
    reduce: false,
    keys: groupIDs
  });

// execute a query named in queryFns
const query = async(fnName, csv) => {
  const html = [];
  const view_result = gel(g_view_result);

  try {
    // convert csv to array
    const groupIds = (csv || '').split(',').map(id => id.trim());
    let docs = await queryFns[fnName](groupIds);

    // Ensure docs is an array of documents (e.g. db.allDocs vs db.find result)
    // Beware the SO 'Tidy' function smashes the null coalesce operator 
    docs = docs.rows ?? docs.docs;

    html.push(`Matches: ${docs.length || 0}`);
    // collect the docs for view
    docs.forEach(doc =>
      html.push(`<pre>${JSON.stringify(doc,undefined,3)}</pre>`)
    );

  } catch (e) {
    // display any error message
    html.unshift(e.message);
  }

  view_result.innerHTML = html.join('<hr/>');
}

// canned demo documents
function getDocsToInstall() {
  return [{
      _id: 'example:123',
      //_rev: 'example',
      name: 'John',
      groups: [{
          groupID: '124',
          type: 'ADMIN'
        },
        {
          groupID: '345',
          type: 'PRACTITIONER'
        },
        {
          groupID: '678',
          type: 'PATIENT'
        },
      ]
    },
    {
      _id: 'example:456',
      // _rev: 'example2',
      name: 'Yoda',
      groups: [{
          groupID: '124',
          type: 'ADMIN'
        },
        {
          groupID: '345',
          type: 'PRACTITIONER'
        }
      ]
    },
  ]
}

//
// init db 
//
let db;

(async() => {

  db = new PouchDB('test', {
    adapter: 'memory'
  });

  // install the docs into the db
  const docs = getDocsToInstall();
  await db.bulkDocs(docs);

  // create a mango index for the 'groups' field.
  db.createIndex({
    index: {
      fields: ['groups'],
      ddoc: "my-index"
    }
  });
  // declare groups_groupIDView map/reduce index for groups.groupID
  const ddoc = {
    _id: '_design/' + groups_groupIDView,
    views: {
      groupIDView: {
        map: function(doc) {
          if (doc.groups instanceof Array) {
            doc.groups.forEach(g => {
              emit(g.groupID);
            })
          }
        }.toString()
      }
    }
  };
  // install the map/reduce design doc
  await db.put(ddoc);

  // candy - gather up and display groupIDs for this demo
  const hints = {};
  docs.forEach(doc => doc.groups.forEach(g => hints[g.groupID] = 1));
  gel(g_groupIdHints).innerText = Object.keys(hints).join(', ');

})();
.hide {
  display: none
}

.label {
  text-align: right;
  margin-right: 1em;
}

.hints {
  font-size: smaller;
}
<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>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.find.min.js"></script>

<table>
  <tr>
    <td>
      <button onclick='query(all_docs)'>Show All Docs</button>
    </td>
  </tr>
  <tr>
    <td>
      <label for='groupID'>Find GroupIDs:&nbsp;</label>
      <input type='text' id='groupID' />
      <button onclick='query(groups_groupID,gel("groupID").value)'>Mango</button>
    </td>
  </tr>
  <tr>
    <td>
      <label for='groupIDView'>Find GroupIDs:&nbsp;</label>
      <input type='text' id='groupIDView' />
      <button onclick='query(groups_groupIDView,gel("groupIDView").value)'>Map/Reduce</button>
    </td>
  </tr>
  <tr>
    <td>
      <span class='hints'>Enter CSV of groupID's, e.g. <span id='groupIdHints'></span></span>
    </td>
  </tr>
  <tr>
</table>
<div style='margin-top:2em'></div>
<div>
  <pre id='view_result'></pre>
</div>

1CouchDB find - Combination Operators