web-dev-qa-db-ja.com

移植可能なSQLステートメントを記述できますか?

以下を含むほとんどまたはすべてのデータベースと100%相互運用可能なSQLステートメントを作成できるかどうか疑問に思っています。

  • MariaDB/MySQL/Percona
  • Postgres
  • Microsoft SQL
  • オラクル
  • SQLite

(たとえば、特定のSQL標準に従うだけですか?たとえば、SQLのPOSIXコンプライアンス標準に似たものはありますか?)

もしそうなら、すべてのDBでコードをコミットすることなく、そのような標準または非準拠のSQLコードに従わないSQLの使用を拒否するためにgit post-receiveフックで使用できるリンティングツールはありますか?

35
James Shewey

いいえ、とにかく、重要で実用的な量のコードについてはそうではありません。標準に合わせて試すこともできます(たとえば、COALESCEではなくISNULLを使用します)が、違いが多すぎます(大小は異なります)。私の頭の上から:

  • SQL Serverは、識別子の二重引用符と角括弧をサポートしています。 MySQLはバッククォートを使用します
  • SQL ServerはTOPをサポートし、他のほとんどのデータベースはLIMITを使用します。
  • PostgreSQLは比較的最近実装されたストアドプロシージャのみですが、実際には代わりに関数を使用できます。
  • MySQLは、UNIXで実行する場合、テーブル名(フィールド名ではない)の大文字と小文字を区別しますが、Windowsで実行する場合は区別しません。 SQL Serverでは、大文字と小文字は区別されません(そうでない場合を除く)。
  • CTEとウィンドウ関数はすべてのシステムで使用できるわけではなく、常に同じように実装されるとは限りません。
  • SQL Serverではコマンド区切り文字は必要ありません(必要な場合を除く)が、MySQLとOracleでは必要です。 MySQLでは、ストアドプロシージャを定義するときに代替の区切り文字を使用する必要もあります。 MS SQLはそのようなことをサポートしていません。
  • ほとんどの場合、セキュリティはベンダーによって異なります。
  • エラー処理は常に異なります。
  • 上記のすべては変化する可能性があり、私が最後にそれらのシステムで作業して以来、変化する可能性があります。

多くの人が 目的を備えたソフトウェアRDBMSに依存しないクエリを作成させる と記述しています。これらの実験のほとんどは失敗し、いくつかは実験室から抜け出しました 彼らの跡形に破壊を広める景観全体に潜んでいる 。しかし、最高のものでも、ターゲットシステムを考慮して書かれたコードのパフォーマンスは得られません。

60

ANSI SQL標準があります。たとえば、Wikipediaの記事の Interoperability and standardization の部分を参照してください。問題は、これらの標準に実際に準拠している人はほとんどいないことです。これらの標準は、事後的に記述および作成されることが多く、長年の歴史がすでにさまざまなデータベース製品の手を握って別のことをしています。

ただし、すべてが失われるわけではありません。複雑なクエリやレポートをほとんど必要としないWebアプリケーションなどのささやかな目標の場合、サポートするデータベースバックエンドのリストを作成することは達成可能な目標です。たとえば、上記のリスト。最小限のバージョン番号を追加するだけで、実際にサポートおよびテストする目的がわかります。テスト、私は恐れています、あなたはそうしなければなりません。

アプリケーションコードでは、非常に基本的なSELECT、UPDATE、およびINSERTに制限することを期待してください。

  • パラメータ化され、準備されたクエリを使用できるデータベース抽象化レイヤーを見つけてください。エスケープ文字列は、特定のデータベース製品で現在有効になっている設定に基づいていても、大幅に変化する可能性があります。固定文字列リテラルを含める必要がある場合は、それを「一重引用符」で囲み、制御文字、null、バックスラッシュ、引用符などを含めることができないことを確認してください。

  • すべての識別子(テーブル、列名、エイリアス)を予約済みキーワード(from、select、left、countなど)にできないことを確認してください。基本的に、すべての単純な英語の単語は避けます。そうでなければ、それらを引用する必要があり、それはワームの缶です。すべて小文字にしておくのが最善ですが、大文字と小文字を区別して返されるとは思わないでください。

  • GROUP BYクエリの一般的な集計関数以外のSQL関数を当てにしないでください。基本的に、COUNT()、MIN()、MAX()、SUM()

  • 数値の加算、減算、乗算は一般に安全であり、データ型の範囲制限を割り引きます。除算や係数を使用することを期待しないでください。特に、SQLサーバー側で文字列を連結しようとしないでください。もちろん、彼ら全員がそれを行うことができますが、少し異なる方法で。

  • LIKE演算子を使用しないでください。

  • ORDER BYには単純な数値のみを想定し、アプリケーション側では文字列による順序付けを続けます。照合のサポートは大きく異なります。順序付けする列にNULLが含まれる可能性がある場合は、それらの列が上または下に順序付けられる可能性があると想定してください。

  • バイナリデータ(BLOB、VARBINARYなど)をデータベースに保存する必要がある場合は、サポートされているすべてのデータベースバックエンド間の差異を1つずつ慎重にテストして処理し、検索と保存の両方を行う必要があります。

これらに固執する場合、作業の大部分はDDL側で行われ、データベースを作成し、テーブルを定義し、サポートするように選択したデータベースごとに手動でカスタマイズします。一般に、最近ではすべてがVIEWをサポートしているため、関数や演算子の違いを抽象化し、アプリケーションに一貫したビューを提供することもできます。データベースごとに定義する必要がありました。

注意すべき問題点:

  • 符号付きの32ビットまたは64ビットの整数とbigintを使用します。 10進数が必要な場合は、さらに注意が必要です。あなたがサポートするバックエンドの定義されたリストがあれば決して不可能ではありません。

  • テキスト値の文字セットと長さ。最近では、絵文字を含め、すべてを正しく保存して処理できるようにしたいと考えています。それらでテストし、何が必要かを理解してください。例えば。 MySQLは歴史的に、そしてMariaDBは依然として、必要なものをutf8mb4と呼んでいますが、ベースutf8はそうしません。 Microsoft SQL Serverでは、_SC照合が必要で、代わりにバージョン15(2019)_SC_UTF8以降のNCHAR/NVARCHARフィールドのみを使用します。必要な数の文字を保持できる十分な大きさにしたことを確認してください。 utf-8 char(4)は4つではなく、1つの絵文字(修飾子なし)のみを保持できます。上限が非常に低くなる可能性があるため、テキスト列に何らかの種類のINDEXが必要な場合は、サイズが大きいことに注意してください。

  • テキスト値、照合。前に言ったようにそれを心に留め、データベースサーバーによる順序に依存しない場合でも、等しいかどうかを判断するときに照合順序が機能します。これは、等価値と一意のキーの両方で選択する場合に重要です。常に認識し、必要なものを手に入れていることをテストしてください。大文字と小文字の区別の有無、アクセントの区別など。望ましい結果を達成することは、さまざまなDB間で大きく異なりますが、一般的には、いくつかの警告があります。これに多くの時間を費やすことを期待してください。

  • 明らかに、より難解なタイプを忘れてください。セット、列挙、XML、配列など。

  • NULL可能な列にUNIQUEキーがあると、データベースシステムによっては、任意の数のNULL値、または正確に1つのNULL値が許可される場合があります。ただし、これをデータベース定義部分で改造して処理し、希望どおりに機能させることができます。

また、最近、MariaDBとMySQLを簡単にまとめることができるとは思わないでください。彼らは今までに重要な方法で分岐しました。それらを分離しているかのように扱い、テストします。 dbfiddle のようなツールは非常に便利です。

特定のデータベースバックエンドの長所を実際に活用しない最も一般的な分母のソリューションに簡単に到達するという観点から、すべての努力が価値があるかどうかは、自分で答えなければならない問題です。多くのブログ、CMS、および同様のシステムでは、たとえば、少なくともMySQLとPostgreSQLの両方をサポートすることが有用であることがわかりました。

41
fds

十分に些細な発言については-はい、そうです。

SELECT field FROM table

それらのDBの一部は大文字と小文字が区別されるため、大文字と小文字を正しく理解すれば、どこでも機能するはずです。

実際のアプリケーションで必要になる可能性のあるものについては、他の答えは適切です。

2
Tom

構文は1つのものです(他の回答はすでにそれをカバーしています)が、一見同一のステートメントの動作は別のものです。

それも同様に注意すべきことだと思います-問題は非常に遅く現れるだけなので、もっと重要かもしれません。

ここにあなたに驚くかもしれないし、そうでないかもしれないいくつかの例があります:

整数除算

PostgresとSQL Serverは、2つの整数を除算すると整数を返します。その場合、OracleとMySQLは10進数値を返します。

次のサンプルテーブルをご覧ください。

create table t (nr integer);
insert into t values (1), (1), (2), (2), (2), (3);

そして、各数値の発生率を計算するクエリ:

select nr, count(*) / (select count(*) from t) as pct
from t
group by nr;

PostgresとSQL Serverはすべての行に対して0(ゼロ)を返しますが、MySQLとOracleは期待されるパーセンテージを返します。

LIKEの動作

SQL Serverは、ある種の「貧乏人の正規表現」をLIKEワイルドカードとして使用します。角括弧。次のサンプルデータを使用します。

create table foo (bar varchar(100));
insert into foo values ('2'), ('[42]');

次のステートメント(100%純粋なANSI SQL):

select *
from foo
where bar like '%[42]%';

構文について文句を言うDBMSはありません。ただし、SQL Serverは両方の行を返しますが、他のすべての行は[42]のある行のみを返します

(大文字と小文字を区別する/区別しない問題に巻き込まれないように、私はそこに意図的に数値を入れました)

一意のインデックスとNULL

次の表を検討してください。

CREATE TABLE foo (col1 integer, col2 integer);
CREATE UNIQUE INDEX idx_foo ON foo (col1, col2);

上記は、ほとんどすべてのDBMSで変更なしで実行されます。

次に、2つのINSERTステートメントについて考えます。

INSERT INTO foo (col1, col2) VALUES (1, null);
INSERT INTO foo (col1, col2) VALUES (1, null);

PostgresとMySQLはNULLが決して何にも等しくないため、これらの2つの行を喜んで挿入します。したがって、一意のインデックス(制約)に違反しません。

OracleとSQL Serverは2行目の挿入を拒否します。

外部キー評価

この自己参照テーブルを見てください:

CREATE TABLE fk_test
(
  id          integer PRIMARY KEY,
  name        varchar(20),
  parent_id   integer,
  FOREIGN KEY (parent_id) REFERENCES fk_test (id)
);

次の挿入は単一のステートメントです(100%ANSI SQL-Oracleではサポートされていませんが、ここでは重要ではありません)。

INSERT INTO fk_test 
  (id, name, parent_id) 
VALUES 
  (4, 'Four', 1),
  (3, 'Three', 2),
  (2, 'Two', 1),
  (1, 'OnNe', null);

単一のアトミックステートメントであるため、外部キー参照はすべて有効です。上記はSQL ServerとPostgresで問題なく実行され、ステートメントは単一のアトミックINSERTとして扱われ、ステートメントレベルの制約がチェックされます。 MySQLは、ステートメントの終了時ではなく、行ごとに制約をチェックするため、失敗します。

複数の行を削除する場合も同様です。これら4つの行すべてを正しい順序で挿入し、ルート以外のすべてを削除したいとします。

DELETE FROM fk_test
WHERE id IN (2,3,4);

これもMySQLでは失敗しますが、Postgres、Oracle、SQL Serverで機能します。

同様のことが、一意の制約で発生する可能性があります。

ロック

大きな違いは、(デフォルトの)ロック動作でもあります。 PostgresとOracleでは、リーダーはライターをブロックせず、ライターはリーダーをブロックしません(FOR UPDATEまたはLOCK TABLEを別に使用した明示的なロック)が、SQL ServerまたはMySQLでは当てはまりません。 OracleとPostgresにもロックのエスカレーションはありません。そのため、ロック動作は通常、ロックの数に影響されません。


これが、本番環境で使用されているもの以外のDBMSでテストするとテストがかなり無意味になると思う理由でもあります(H2やHSQLDBのような組み込み/メモリ内エンジンと「実際の」ものの比較)。

SQLコードが複雑になるほど、移植性が低下します。特定のアプリについては、OracleとMSSQLをサポートする必要があります。その他、より複雑な違いは別として、文字列の連結(|| vs +)は私を怒らせます。

次に、ドライバー。 Javaはかなり適切に設計されたJDBC APIを使用してこれらのSQLサーバーにアクセスできます。Cの実装に少し腹を立てたり、単に他のドライバを見つけられないことがあります。YMMV。

0
fraxinus