36

myTable私はテーブルと列を持つSQLiteデータベースを持っていますid, posX, posY. 行数は常に変化します (増減する可能性があります)。id各行の値と行数がわかっている場合、1 つの SQL クエリを実行して、ID に応じてすべてのフィールドposXposYフィールドを異なる値で更新できますか?

例えば:

---------------------
myTable:

id   posX    posY

1      35     565
3      89     224
6      11     456
14     87     475
---------------------

SQL クエリの擬似コード:

UPDATE myTable SET posX[id] = @arrayX[id], posY[id] = @arrayY[id] "

@arrayXandは、 andフィールド@arrayYの新しい値を格納する配列です。posXposY

たとえば、arrayXarrayY次の値が含まれているとします。

arrayX = { 20, 30, 40, 50 }
arrayY = { 100, 200, 300, 400 }

... クエリ後のデータベースは次のようになります。

---------------------
myTable:

id   posX    posY

1      20     100
3      30     200
6      40     300
14     50     400
---------------------

これは可能ですか?現在、クエリごとに 1 行を更新していますが、行数が増えると数百のクエリが必要になります。ちなみに、私はこれをすべてAIRで行っています。

4

8 に答える 8

40

これを適切に効率的に行うには、いくつかの方法があります。

最初 -
可能であれば、一時テーブルに何らかの一括挿入を行うことができます。これは、RDBMS/ホスト言語に多少依存しますが、最悪の場合、単純な動的 SQL (VALUES()句を使用) と、別のテーブルからの標準的な update-from-another-table で実現できます。ほとんどのシステムは一括読み込み用のユーティリティを提供していますが、

2 つ目 -
これも RDBMS に多少依存します。動的な更新ステートメントを作成できます。この場合、VALUES(...)CTE 内の句がオンザフライで作成されます。

WITH Tmp(id, px, py) AS (VALUES(id1, newsPosX1, newPosY1), 
                               (id2, newsPosX2, newPosY2),
                               ......................... ,
                               (idN, newsPosXN, newPosYN))

UPDATE TableToUpdate SET posX = (SELECT px
                                 FROM Tmp
                                 WHERE TableToUpdate.id = Tmp.id),
                         posY = (SELECT py
                                 FROM Tmp
                                 WHERE TableToUpdate.id = Tmp.id)


WHERE id IN (SELECT id
             FROM Tmp)

(ドキュメントによると、これ有効な SQLite 構文である必要がありますが、フィドルで動作させることはできません)

于 2012-07-19T15:47:36.650 に答える
23

一方向: SET x=CASE..END (任意の SQL)

はい、これを行うことができますが、クエリのレイテンシが非常に大きい場合を除き、パフォーマンスが向上するとは思えません。

クエリが検索値でインデックス化されている場合 (たとえばid、主キーの場合)、目的のタプルの検索は非常に高速で、最初のクエリの後、テーブルはメモリに保持されます。

したがって、この場合の複数の UPDATE はそれほど悪いことではありません。

一方、条件が完全なテーブル スキャンを必要とし、さらに悪いことに、テーブルのメモリへの影響が大きい場合は、UPDATE の評価が単純な UPDATE よりもコストがかかるとしても、単一の複雑なクエリを使用する方が適切です (これは、内部的に最適化されます)。

この後者の場合、次のことができます。

 UPDATE table SET posX=CASE
      WHEN id=id[1] THEN posX[1]
      WHEN id=id[2] THEN posX[2]
      ...
      ELSE posX END [, posY = CASE ... END]
 WHERE id IN (id[1], id[2], id[3]...);

総コストは、おおよそ次のように計算されます: NUM_QUERIES * ( COST_QUERY_SETUP + COST_QUERY_PERFORMANCE )。この方法では、NUM_QUERIES を (N 個の個別の ID から 1 に) 減少させますが、COST_QUERY_PERFORMANCE は増加します (MySQL 5.28 では約 3 倍、MySQL 8 ではまだテストしていません)。

それ以外の場合は、id でインデックスを作成するか、アーキテクチャを変更してみます。

これは PHP の例です。完全なテーブル スキャンが必要な条件があり、それをキーとして使用できると仮定します。

// Multiple update rules 
$updates = [
   "fldA='01' AND fldB='X'" => [ 'fldC' => 12, 'fldD' => 15 ],
   "fldA='02' AND fldB='X'" => [ 'fldC' => 60, 'fldD' => 15 ],
   ...
];

右側の式で更新されるフィールドは 1 つまたは複数の可能性があり、常に同じでなければなりません (この場合は常に fldC と fldD)。この制限は取り除くことができますが、アルゴリズムを変更する必要があります。

次に、ループを介して単一のクエリを作成できます。

$where = [ ];
$set   = [ ];
foreach ($updates as $when => $then) {
    $where[] = "({$when})";
    foreach ($then as $fld => $value) {
       if (!array_key_exists($fld, $set)) {
           $set[$fld] = [ ];
       }
       $set[$fld][] = $value;
    }
}

$set1 = [ ];
foreach ($set as $fld => $values) {
    $set2 = "{$fld} = CASE";
    foreach ($values as $i => $value) {
        $set2 .= " WHEN {$where[$i]} THEN {$value}";
    }
    $set2 .= ' END';
    $set1[] = $set2;
}

// Single query
$sql  = 'UPDATE table SET '
      . implode(', ', $set1)
      . ' WHERE '
      . implode(' OR ', $where);

別の方法: ON DUPLICATE KEY UPDATE (MySQL)

MySQL では、存在しない条件 ("id = 777" with 777) がテーブルに挿入され、おそらくエラーが発生することを念頭に置いて、id が主キーであると仮定するINSERT ON DUPLICATE KEY UPDATEと、 multipleを使用してこれをより簡単に実行できると思います。たとえば、他の必要な列 (NOT NULL と宣言) がクエリで指定されていません。

INSERT INTO tbl (id, posx, posy, bazinga)
     VALUES (id1, posY1, posY1, 'DELETE'),
     ...
ON DUPLICATE KEY SET posx=VALUES(posx), posy=VALUES(posy);

DELETE FROM tbl WHERE bazinga='DELETE';

上記の 'bazinga' トリックにより、ID が存在しないために無意識に挿入された可能性のある行を削除できます (ただし、他のシナリオでは、挿入された行を残しておきたい場合があります)。

たとえば、収集された一連のセンサーからの定期的な更新ですが、一部のセンサーは送信されていない可能性があります。

INSERT INTO monitor (id, value)
VALUES (sensor1, value1), (sensor2, 'N/A'), ...
ON DUPLICATE KEY UPDATE value=VALUES(value), reading=NOW();

(これは不自然なケースです。おそらく、テーブルをロックし、すべてのセンサーを N/A に UPDATE して NOW() にしてから、存在する値のみを INSERT する方が合理的です)。

3 番目の方法: CTE (PostgreSQL、SQLite3 については不明)

これは概念的には INSERT MySQL トリックとほぼ同じです。書かれているように、PostgreSQL 9.6 で動作します。

WITH updated(id, posX, posY) AS (VALUES
    (id1, posX1, posY1), 
    (id2, posX2, posY2),
    ...
)
UPDATE myTable
    SET 
    posX = updated.posY,
    posY = updated.posY
FROM updated
WHERE (myTable.id = updated.id);
于 2012-07-19T15:33:09.613 に答える
6

このようなものがうまくいくかもしれません:

"UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;"

posX または posY のいずれかの値が同じ場合、それらを 1 つのクエリに結合できます。

UPDATE myTable SET posX='39' WHERE id IN('2','3','40');
于 2012-07-19T15:25:26.163 に答える
0

最近のバージョンの SQLite (2018 年の 3.24.0 以降) では、UPSERT句を使用できます。一意の列を持つ既存のデータセットのみが更新されると仮定すると、@LSerni の提案idに似たこのアプローチを使用できます。ON DUPLICATE

INSERT INTO myTable (id, posX, posY) VALUES
  ( 1, 35, 565),
  ( 3, 89, 224),
  ( 6, 11, 456),
  (14, 87, 475)
ON CONFLICT (id) DO UPDATE SET
  posX = excluded.posX, posY = excluded.posY
于 2022-02-21T15:49:56.450 に答える
-13

「タブレットセットの更新 (row='value' where id=0001'), (row='value2' where id=0002'), ... で試してください。

于 2013-11-04T10:05:35.017 に答える