4

並べ替え/フィルター処理されたクエリを使用して SQLite データベース テーブルからデータを取得し、表示するキャッシュ システムを作成しています。プルしているテーブルは潜在的に非常に大きくなる可能性があり、もちろん、メモリ内に常に最大数の行のみを保持することで、メモリへの影響を最小限に抑える必要があります。LIMITこれは、 を使用しOFFSETて、必要なレコードのみをロードし、必要に応じてキャッシュを更新することで簡単に実行できます。これを実装するのは簡単です。私が抱えている問題は、特定のクエリに挿入された新しいレコードの挿入インデックスがどこにあるかを判断して、UI を適切に更新できるようにすることです。これを行う簡単な方法はありますか?これまでのところ、私が持っていたアイデアは次のとおりです。

  1. キャッシュ全体をダンプし、クエリ結果を再カウントし (新しい行が含まれる保証はありません)、キャッシュを更新し、UI 全体を更新します。それが本当に望ましくない理由が明らかであることを願っています。
  2. 独自のアルゴリズムを使用して、新しい行が現在のクエリに含まれているかどうか、現在のキャッシュされた結果に含まれているかどうか、現在のキャッシュされたスコープ内にある場合はどのインデックスに挿入する必要があるかを判断します。このアプローチの最大の欠点は、その複雑さと、私自身のソート/フィルタリング アルゴリズムが SQLite のものと一致しないというリスクです。

もちろん、私が望むのは、SQLite に質問できるようにすることです。クエリ結果全体をロードせずに、「クエリ A」が「行 B」のインデックスとは何かを指定します。しかし、これまでのところ、これを行う方法を見つけることができませんでした。

問題ではないと思いますが、これはすべて iOS デバイス上で、objective-c プログラミング言語を使用して行われています。

より詳しい情報

クエリと後続のキャッシュは、ユーザー入力に基づいています。基本的に、ユーザーは再ソートおよびフィルタリング (または検索) して、表示される結果を変更できます。挿入時 (および実際には編集時) にキャッシュを単純に再作成するのをためらうのは、「よりスムーズな」UI エクスペリエンスを提供するためです。

現時点では、オプション「2」に傾いていることを指摘しておく必要があります。すべてのレコードをテーブルにロードし、独自のアルゴリズムを使用してメモリ内で並べ替え/フィルター処理を実行することで、独自のキャッシュ/インデックス システムを作成してみました。特定のレコードがキャッシュ内にあるかどうか、および/またはどこにあるかを判断するために必要なコードの多くは既にそこにあるため、私はそれを使用する傾向があります。基になるクエリと一致しないキャッシュを持つ危険性があります。クエリが返さないレコードをキャッシュに含めると、問題が発生し、おそらくクラッシュします。

4

3 に答える 3

1

レコード番号は必要ありません。

順序付けられたフィールドの値を、LIMITed クエリ結果の最初と最後のレコードに保存します。次に、これらを使用して、新しいレコードがこの範囲に収まるかどうかを確認できます。

Nameつまり、フィールドで並べ替え、元のクエリが次のようになっていると仮定します。

SELECT Name, ...
  FROM mytab
  WHERE some_conditions
  ORDER BY Name
  LIMIT x OFFSET y

次に、同様のクエリを使用して新しいレコードを取得しようとします。

SELECT 1
  FROM mytab
  WHERE some_conditions
    AND PrimaryKey = LastInsertedValue
    AND Name BETWEEN CachedMin AND CachedMax

同様に、新しいレコードが挿入された前 (または後) のレコードを見つけるには、挿入されたレコードの直後から開始し、次のように 1 の制限を使用します。

SELECT Name
  FROM mytab
  WHERE some_conditions
    AND Name > MyInsertedName
    AND Name BETWEEN CachedMin AND CachedMax
  ORDER BY Name
  LIMIT 1

これでは数値が得られません。返された名前がキャッシュ内のどこにあるかを確認する必要があります。

于 2012-09-07T18:24:56.783 に答える
1

通常、基になるデータの変更があった場合、キャッシュが無効になることが予想されます。ドロップして最初からやり直すことが、最も簡単で保守可能なソリューションになると思います。よほどの理由がない限りお勧めします。

行数を返すだけの別のクエリを記述して (以下の例)、キャッシュを無効にする必要があるかどうかを確認できます。これにより、キャッシュが変更されていないときにキャッシュを再作成する必要がなくなります。

SELECT name,address FROM people WHERE area_code=970;
SELECT COUNT(rowid) FROM people WHERE area_code=970;

キャッシュがいつ無効になったかを知るために sqlite から必要な情報には、クエリやインデックスがどのように機能していたかについてのかなり詳細な知識が必要です。カップリングがかなり高いと言えます。

それ以外の場合は、並べ替えに関して挿入された場所を知りたいでしょう。おそらく、ソートされたフィールドの各ページにキーを設定するでしょう。挿入/削除フィールドよりも大きいものを削除します。並べ替えを変更すると、すべてが削除されます。

C++ を使用している場合は、次のようなものから始めます。あなたが C++ を使っていないことは承知していますが、私がやろうとしていることは明らかだと思います。

struct Person {
  std::string name;
  std::string addr;
};

struct Page {
  std::string key;
  std::vector<Person> persons;
  struct Less {
    bool operator()(const Page &lhs, const Page &rhs) const {
      return lhs.key.compare(rhs.key) < 0;
    }
  };
};

typedef std::set<Page, Page::Less> pages_t;
pages_t pages;

void insert(const Person &person) {
  if (sql_insert(person)) {
    pages_t::iterator drop_cache_start = pages.lower_bound(person);
    //... drop this page and everything after it
  }
}

さまざまなデータ型を適切に機能させるには、いくつかの論争を行う必要がありますkeyが、それは可能です。

理論的には、ページを除外して、オブジェクト自体のみを使用することができます。ただし、データベースはデータを「所有」しなくなります。データベースからページを埋めるだけであれば、データの一貫性に関する心配は少なくなります。

これは少し話題から外れているかもしれませんが、ビューを再実装しているのではありませんか? それ自体はキャッシュしませんが、それがプロジェクトの要件であるかどうかは明確ではありません。

于 2012-09-07T18:18:10.307 に答える
1

私が思いついた解決策は単純ではありませんが、現在はうまく機能しています。Countクエリ ステートメント内のレコードのインデックスは、以前のすべてのレコードのインデックスでもあることに気付きました。私がする必要があったのは、クエリ内のすべてのステートメントを、前のレコードのみを返し、それらのレコードの数を取得するORDER一連のステートメントに「変換」することでした。WHERE思ったよりもトリッキーです (そうではないかもしれません...トリッキーに聞こえます)。私が抱えていた最大の問題は、クエリが実際に予測できる方法でソートされていることを確認することでした。これは、一意の値を持つ列に基づいた注文パラメーターに注文列が必要であることを意味しました。したがって、ユーザーが列を並べ替えるたびに、一意の列に別の順序パラメーターをステートメントに追加します (「変更日付スタンプ」を使用しました)。

WHEREステートメントの一部を作成するには、一連のANDs を追加するだけでは不十分です。実証する方が簡単です。Order 列が 3 つあるとします: "LastName" ASC、"FirstName" DESC、および "Modified Stamp" ASC (タイ ブレーカー)。ステートメントは次のWHEREようになります ('?' = レコード値):

WHERE
    "LastName" < ? OR
    ("LastName" = ? AND "FirstName" > ?) OR
    ("LastName" = ? AND "FirstName" = ? AND "Modified Stamp" < ?)

WHERE括弧でグループ化されたパラメーターの各セットは、タイ ブレーカーです。実際、「LastName」のレコード値が等しい場合は、「FirstName」、最後に「Modified Stamp」を確認する必要があります。明らかに、一連の順序パラメーターで並べ替える場合、このステートメントは非常に長くなる可能性があります。

上記の解決策には、まだ 1 つの問題があります。値に対する数学演算はNULL常に false を返しますが、並べ替えると SQLite はNULL最初に値を並べ替えます。したがって、NULL値を適切に処理するには、別の複雑なレイヤーを追加する必要があります。まず、すべての数学的な等値演算 を=に置き換える必要がありますIS。次に、演算子に値を適切に含めるには、すべての<操作を でネストする必要があります。これにより、上記の操作が次のようになります。OR IS NULLNULL<

WHERE
    ("LastName" < ? OR "LastName" IS NULL) OR
    ("LastName" IS ? AND "FirstName" > ?) OR
    ("LastName" IS ? AND "FirstName" IS ? AND ("Modified Stamp" < ? OR "Modified Stamp" IS NULL))

次に、上記のパラメーターを使用して RowID をカウントしWHEREます。

ステートメントを生成するために組み立てることができる SQL ステートメントのさまざまな側面を表すオブジェクトのセットを既に構築していたため、ほとんどの場合、それを行うのは簡単でした。このような SQL ステートメントを他の方法で操作しようとすることなど想像もできません。

これまでのところ、テーブルに最大 10,000 レコードを含むいくつかの iOS デバイスでこれを使用してテストしましたが、顕著なパフォーマンスの問題はありませんでした。もちろん、単一レコードの編集/挿入用に設計されているため、超高速/効率的である必要はありません。

于 2012-09-14T19:10:10.013 に答える