web-dev-qa-db-ja.com

IndexedDBで、ソートされた複合クエリを作成する方法はありますか?

テーブルに名前、ID、年齢、性別、学歴などがあるとします。IDがキーで、テーブルには名前、年齢、性別のインデックスも付けられます。私は、25歳以上のすべての男子生徒を名前順に並べておく必要があります。

これはmySQLでは簡単です。

    SELECT * FROM table WHERE age > 25 AND sex = "M" ORDER BY name

IndexDBでは、インデックスを作成し、そのインデックスに基づいてクエリを並べ替えることができます。ただし、年齢や性別などの複数のクエリは許可されません。 queryIndexedDB(https://github.com/philikon/queryIndexedDB)と呼ばれる小さなライブラリを見つけました。これは、複合クエリを許可しますが、ソートされた結果を提供しません。

では、IndexedDBを使用しながら、ソートされた複合クエリを作成する方法はありますか?

32
jason

この回答で使用されている複合クエリという用語は、WHERE句に複数の条件が含まれるSQL SELECTステートメントを指します。そのようなクエリはindexedDB仕様では言及されていませんが、配列で構成されるkeypathでインデックスを作成することにより、複合クエリの動作を概算できますプロパティ名の。

これは、インデックスの作成時にマルチエントリフラグを使用することとはまったく関係ありません。マルチエントリフラグは、indexedDBが単一の配列プロパティに対してインデックスを作成する方法を調整します。オブジェクトの単一の配列プロパティの値ではなく、オブジェクトプロパティの配列にインデックスを付けています。

インデックスを作成する

この例では、「name」、「gender」、および「age」は、studentsオブジェクトストア内に格納されている学生オブジェクトのプロパティ名に対応しています。

_// 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への範囲パラメーターの使用はオプションです。範囲を指定しない場合、_IDBKeyRange.only_が暗黙的に使用されます。言い換えると、境界カーソルにはIDBKeyRangeのみを使用する必要があります。

基本的なインデックスの概念

インデックスはオブジェクトストアに似ていますが、直接変更することはできません。代わりに、参照されるオブジェクトストアでCRUD(読み取り、読み取り、削除の作成)操作を使用すると、indexedDBが更新をインデックスに自動的にカスケードします。

ソートを理解することは、インデックスを理解するための基本です。インデックスは基本的に、特別にソートされたオブジェクトのコレクションです。技術的には、これもフィルタリングされますが、これについては後ほど触れます。一般に、インデックス上でカーソルを開くと、インデックスの順序に従って繰り返し処理が行われます。この順序は、参照されるオブジェクトストア内のオブジェクトの順序とは異なる可能性があり、おそらく異なります。これにより反復がより効率的になり、インデックス固有の順序のコンテキストでのみ意味を持つカスタムの下限と上限が許可されるため、順序は重要です。

インデックス内のオブジェクトは、ストアへの変更が発生したときにソートされます。ストアにオブジェクトを追加すると、オブジェクトはインデックスの適切な位置に追加されます。並べ替えは、2つの項目を比較し、1つのオブジェクトが他のオブジェクトよりも小さいか、他のオブジェクトよりも大きいか、等しいかを返す、Array.prototype.sortと同様の比較関数に要約されます。したがって、比較関数の詳細を掘り下げることにより、ソート動作をよりよく理解できます。

文字列は辞書式に比較されます

これは、たとえば、「Z」が「a」より小さく、string '10'がstring '020'より大きいことを意味します。

異なるタイプの値は、仕様定義の順序を使用して比較されます

たとえば、仕様では、文字列型の値が日付型の値の前または後にどのように来るかを指定します。値が何を含んでいるかは問題ではなく、タイプのみです。

IndexedDBは型を強制しません。ここで自分を足で撃つことができます。一般的に、異なるタイプを比較したくありません。

未定義のプロパティを持つオブジェクトは、キーパスがそれらのプロパティの1つ以上で構成されるインデックスには表示されません

既に述べたように、インデックスには、参照されるオブジェクトストアのすべてのオブジェクトが常に含まれるとは限りません。オブジェクトをオブジェクトストアに配置すると、インデックスの基になるプロパティの値が欠落している場合、そのオブジェクトはインデックスに表示されません。たとえば、年齢がわからない学生がいて、これを学生ストアに挿入した場合、特定の学生はmales25インデックスに表示されません。

インデックス上でカーソルを反復したときにオブジェクトが表示されない理由を不思議に思うときは、これを覚えておいてください。

Nullと空の文字列の微妙な違いにも注意してください。空の文字列はnot欠損値です。プロパティの空の文字列を持つオブジェクトは、そのプロパティに基づくインデックスに引き続き表示されますが、プロパティは存在するが未定義または存在しない場合はインデックスに表示されません。そして、それがインデックス内にない場合は、インデックス上でカーソルを反復しても表示されません。

IDBKeyRangeを作成するときは、配列のキーパスの各プロパティを指定する必要があります

範囲の上でカーソルを開くときに範囲で使用する下限または上限を作成するときは、配列のキーパスの各プロパティに有効な値を指定する必要があります。そうしないと、何らかのタイプのJavascriptエラーが発生します(ブラウザーによって異なります)。たとえば、nameプロパティが定義されていないため、IDBKeyRange.only([undefined, 'male', 25])などの範囲を作成できません。

紛らわしいことに、IDBKeyRange.only(['male', 25])のように、値の間違ったtypeを指定すると、名前が未定義の場合、上記の意味でエラーは発生しませんが、無意味になります。結果。

この一般的なルールには例外があります。長さの異なる配列を比較できます。したがって、配列のendから行う場合、および配列を適切に切り捨てる場合は、技術的に範囲からプロパティを省略できます。たとえば、IDBKeyRange.only(['josh','male'])を使用できます。

短絡配列ソート

indexedDB仕様 は、配列をソートするための明示的な方法を提供します。

タイプArrayの値は、次のようにタイプArrayの他の値と比較されます。

  1. Aを最初の配列値、Bを2番目の配列値とします。
  2. Aの長さとBの長さの小さい方を長さとする。
  3. 私を0とします。
  4. Aのi番目の値がBのi番目の値より小さい場合、AはB未満です。残りの手順をスキップします。
  5. Aのi番目の値がBのi番目の値より大きい場合、AはBより大きいです。残りの手順をスキップします。
  6. Iを1増やします。
  7. Iが長さに等しくない場合は、手順4に戻ります。それ以外の場合は、次の手順に進みます。
  8. Aの長さがBの長さより小さい場合、AはB未満です。Aの長さがBの長さより大きい場合、AはBより大きいです。それ以外の場合、AとBは等しくなります。

キャッチはステップ4と5にあります:残りのステップをスキップします。これが基本的に意味することは、[1、 'Z']と[0、 'A']のように2つの配列の順序を比較する場合、メソッドは最初の要素のみを考慮するということです。短絡評価(仕様のステップ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の作成者の1人(仕様、およびChrome実装)が最近cursor.continueを使用することを提案しました: https://Gist.github.com/inexorabletash/704e9688f99ac12dd336 =

IndexedDB.cmpを使用したテスト

cmp function を使用すると、並べ替えの仕組みをすばやく簡単に確認できます。例えば:

_var a = ['Hello',1];
var b = ['World',2];
alert(indexedDB.cmp(a,b));
_

IndexedDB.cmp関数の1つの優れたプロパティは、そのシグネチャが Array.prototype.filter および Array.prototype.sort への関数パラメーターと同じであることです。接続、スキーマ、インデックスなどすべてを扱うことなく、コンソールから値を簡単にテストできます。さらに、indexedDB.cmpは同期であるため、テストコードに非同期のコールバック/約束を含める必要はありません。

71
Josh

私は数年遅れていますが、ジョシュの答えはクエリの「列」がインデックスのkeyPathの一部であるシナリオのみを考慮していることを指摘しておきます。

上記の「列」のいずれかがインデックスのkeyPathの外に存在する場合は、例で作成されたカーソルが反復する各エントリでそれらを含む条件をテストする必要があります。したがって、このようなクエリを処理している場合、またはインデックスがuniqueでない場合は、反復コードを書く準備をしてください!

いずれにせよ、クエリをブール式で表すことができる場合は、 BakedGoods を確認することをお勧めします。

これらのタイプの操作の場合、厳密な等価クエリ(x ===? y、xがobjectStoreまたはインデックスキーの場合)、ただし、独自のカーソル反復コードを記述する手間を省くことができます。

bakedGoods.getAll({
    filter: "keyObj > 5 && valueObj.someProperty !== 'someValue'",
    storageTypes: ["indexedDB"],
    complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}
});

完全に透明にするために、BakedGoodsはmoiによって管理されています。

2
Kevin

Linq2indexedDB を使用してみてください。このライブラリでは、複数のフィルト、複数のソートを使用でき、オブジェクトからデータを選択することもできます。また、クロスブラウザー(IE10、Firefox、Chrome)でも機能します。

1
Kristof Degrave

非常に使いやすく、多くのコードと時間を節約できる、IndexedDBからのデータのクエリに使用できるライブラリJsStoreがあります。 。 ここ からさらに探索できます

これは JsStore を使用した同等のSQLクエリです。

var connection = new JsStore.Instance("DbName");

connection.select({
    From: "TableName",
    Where: {
        age :  {'>':'25'},
        sex : 'M'
    },
    Order: {
        By: 'Name'
    },
    OnSuccess:function (results){
        console.log(results);
    },
    OnError:function (error) {
        console.log(error);
    }
});

SQLで考え、JSで書くだけです。お役に立てれば!

1
Uday

IndexedDBでは 1つのキー範囲クエリを開く のみを開くことができます。したがって、最も効率的なインデックス、この場合は「年齢」を使用します。カーソルの反復で性別を除外するだけです。配列反復メソッドを使用して後で実行できる順序。 IndexedDB APIは、事前に配置されたインデックスエントリ以外の順序付けには関心がありません。

0
Kyaw Tun