この回答で使用されている複合クエリという用語は、WHERE 句に複数の条件が含まれる SQL SELECT ステートメントを指します。このようなクエリは indexedDB 仕様には記載されていませんが、プロパティ名の配列で構成されるキーパスを使用してインデックスを作成することにより、複合クエリの動作を近似できます。
これは、インデックスの作成時に複数エントリ フラグを使用することとはまったく関係ありません。複数エントリ フラグは、indexedDB が単一の配列プロパティに対してインデックスを作成する方法を調整します。オブジェクトの単一の配列プロパティの値ではなく、オブジェクト プロパティの配列にインデックスを付けています。
索引の作成
この例では、'name'、'gender'、'age' は、学生オブジェクト ストア内に格納されている学生オブジェクトのプロパティ名に対応しています。
// An example student object in the students store
var foo = {
'name': 'bar',
'age': 15,
'gender': 'M'
};
function myOnUpgradeNeeded(event) {
var db = event.target.result;
var students = db.createObjectStore('students');
var name = 'males25';
var keyPath = ['name', 'gender', 'age'];
students.createIndex(name, keyPath);
}
インデックスでカーソルを開く
次に、インデックスでカーソルを開くことができます。
var students = transaction.objectStore('students');
var index = students.index('males25');
var lowerBound = ['AAAAA','male',26];
var upperBound = ['ZZZZZ','male',200];
var range = IDBKeyRange.bound(lowerBound, upperBound);
var request = index.openCursor(range);
ただし、これから説明する理由により、これが常に機能するとは限りません。
余談ですが、openCursor または get への range パラメータの使用はオプションです。範囲を指定しない場合は、IDBKeyRange.only
暗黙的に then が使用されます。IDBKeyRange
つまり、境界付きカーソルにのみ使用する必要があります。
基本的なインデックスの概念
インデックスはオブジェクト ストアに似ていますが、直接変更することはできません。代わりに、参照先のオブジェクト ストアに対して CRUD (作成、読み取り、更新、削除) 操作を使用すると、indexedDB によって自動的に更新がインデックスにカスケードされます。
並べ替えを理解することは、インデックスを理解するための基本です。インデックスは基本的に、特別にソートされたオブジェクトのコレクションです。技術的には、これもフィルタリングされますが、それについては後で触れます。通常、インデックスでカーソルを開くと、インデックスの順序に従って反復します。この順序は、参照されるオブジェクト ストア内のオブジェクトの順序とは異なる可能性があり、おそらく異なる場合があります。これにより反復がより効率的になり、インデックス固有の順序のコンテキストでのみ意味のあるカスタムの下限と上限が可能になるため、順序は重要です。
インデックス内のオブジェクトは、ストアへの変更が発生した時点でソートされます。オブジェクトをストアに追加すると、インデックス内の適切な位置に追加されます。並べ替えは、Array.prototype.sort と同様の比較関数に要約されます。この関数は、2 つの項目を比較し、一方のオブジェクトが他方のオブジェクトよりも小さいか、他方よりも大きいか、または等しいかを返します。したがって、比較関数の詳細を掘り下げることで、並べ替えの動作をよりよく理解できます。
文字列は辞書式に比較されます
これは、たとえば、'Z' が 'a' より小さく、文字列'10' が文字列'020'より大きいことを意味します。
異なるタイプの値は、仕様で定義された順序を使用して比較されます
たとえば、仕様では、文字列型の値が日付型の値の前または後に来る方法を指定します。値が何を含むかは問題ではなく、型だけです。
IndexedDB は型を強制しません。ここで自分の足を撃つことができます。通常、異なるタイプを比較することは望ましくありません。
未定義のプロパティを持つオブジェクトは、キーパスがそれらのプロパティの 1 つ以上で構成されるインデックスには表示されません
前述したように、インデックスには、参照されたオブジェクト ストアのすべてのオブジェクトが常に含まれているとは限りません。オブジェクトをオブジェクト ストアに入れても、インデックスの基になっているプロパティの値が欠落している場合、そのオブジェクトはインデックスに表示されません。たとえば、年齢がわからない学生がいて、これを学生ストアに挿入すると、特定の学生は males25 インデックスに表示されません。
インデックス上でカーソルを繰り返し処理しているときにオブジェクトが表示されない理由を知りたい場合は、このことを思い出してください。
null と空の文字列の微妙な違いにも注意してください。空の文字列は欠損値ではありません。プロパティの空の文字列を持つオブジェクトは、そのプロパティに基づくインデックスに引き続き表示されますが、プロパティが存在するが未定義または存在しない場合、インデックスには表示されません。また、インデックスにない場合は、インデックス上でカーソルを繰り返しても表示されません。
IDBKeyRange を作成するときは、配列キーパスの各プロパティを指定する必要があります
ある範囲でカーソルを開くときに範囲内で使用する下限または上限を作成するときは、配列 keypath の各プロパティに有効な値を指定する必要があります。そうしないと、何らかの種類の Javascript エラーが発生します (ブラウザーによって異なります)。たとえばIDBKeyRange.only([undefined, 'male', 25])
、 name プロパティが定義されていないため、 などの範囲を作成できません。
紛らわしいことに、 name が undefined のような間違った型の値を指定するIDBKeyRange.only(['male', 25])
と、上記の意味でのエラーは発生しませんが、無意味な結果が得られます。
この一般規則には例外があります。異なる長さの配列を比較できます。したがって、技術的には、配列の末尾からプロパティを省略し、配列を適切に切り捨てれば、範囲からプロパティを省略できます。たとえば、 を使用できますIDBKeyRange.only(['josh','male'])
。
短絡配列ソート
indexedDB仕様は、配列をソートするための明示的な方法を提供します。
Array 型の値は、次のように Array 型の他の値と比較されます。
- A を最初の配列値、B を 2 番目の配列値とします。
- 長さは、A の長さと B の長さの短い方とします。
- 私を0にしましょう。
- A の i 番目の値が B の i 番目の値より小さい場合、A は B より小さいです。残りの手順をスキップします。
- A の i 番目の値が B の i 番目の値より大きい場合、A は B より大きくなります。残りの手順をスキップします。
- i を 1 増やします。
- i が長さに等しくない場合は、手順 4 に戻ります。そうでない場合は、次の手順に進みます。
- A の長さが B の長さよりも短い場合、A は B よりも小さくなります。A の長さが B の長さよりも大きい場合、A は B よりも大きくなります。それ以外の場合、A と B は等しくなります。
問題は手順 4 と 5 にあります:残りの手順をスキップします。これが基本的に意味することは、[1,'Z'] と [0,'A'] のように 2 つの配列の順序を比較する場合、その時点で 1 が > 0 であるため、メソッドは最初の要素のみを考慮するということです。短絡評価 (仕様のステップ 4 と 5) のため、Z と A のチェックに取り掛かることはありません。
したがって、前の例は機能しません。実際には、次のように機能します。
WHERE (students.name >= 'AAAAA' && students.name <= 'ZZZZZ') ||
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' &&
students.gender >= 'male' && students.gender <= 'male') ||
(students.name >= 'AAAAA' && students.name <= 'ZZZZZ' &&
students.gender >= 'male' && students.gender <= 'male' &&
students.age >= 26 && students.age <= 200)
SQL または一般的なプログラミングでこのようなブール句を使用した経験がある場合は、条件の完全なセットが必ずしも関係しないことをすでに認識しているはずです。これは、必要なオブジェクトのリストを取得できないことを意味します。これが、SQL 複合クエリとまったく同じ動作を実現できない理由です。
短絡への対処
現在の実装では、この短絡動作を簡単に回避することはできません。最悪の場合、ストア/インデックスからすべてのオブジェクトをメモリにロードしてから、独自のカスタム ソート関数を使用してコレクションをソートする必要があります。
短絡の問題を最小限に抑える、または回避する方法があります。
たとえば、index.get(array) または index.openCursor(array) を使用している場合、ショートサーキットの問題はありません。完全に一致するか、完全に一致しないかのいずれかです。この場合、比較関数は 2 つの値が同じかどうかのみを評価し、一方が他方よりも大きいか小さいかは評価しません。
考慮すべきその他の手法:
- キーパスの要素を最も狭いものから最も広いものに並べ替えます。基本的に、短絡の望ましくない結果の一部を遮断する範囲で早期クランプを提供します。
- 特別にカスタマイズされたプロパティを使用するストアにラップされたオブジェクトを格納して、非配列キーパス (非複合インデックス) を使用してソートできるようにするか、短絡の影響を受けない複合インデックスを使用できるようにします。行動。
- 複数のインデックスを使用します。これは指数爆発問題につながります。このリンクは別の SQL を使用しないデータベースに関するものですが、同じ概念と説明が indexedDB に適用されることに注意してください。リンクは妥当な (そして長くて複雑な) 説明であるため、ここでは繰り返しません。
- indexedDB (仕様および Chrome 実装) の作成者の 1 人が最近、cursor.continue の使用を提案しました: https://gist.github.com/inexorabletash/704e9688f99ac12dd336
indexedDB.cmp でのテスト
cmp 関数を使用すると、並べ替えがどのように機能するかをすばやく簡単に調べることができます。例えば:
var a = ['Hello',1];
var b = ['World',2];
alert(indexedDB.cmp(a,b));
indexedDB.cmp 関数の優れた特性の 1 つは、その署名がArray.prototype.sortの関数パラメーターと同じであることです。接続/スキーマ/インデックスなどを処理することなく、コンソールから値を簡単にテストできます。さらに、indexedDB.cmp は同期であるため、テスト コードに非同期コールバック/プロミスを含める必要はありません。