如何使用 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: </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: </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>
我只想获取至少与 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: </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: </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>