リレーショナルデータベースを扱う私たちは皆、SQLが異なることを学んでいます(または学んでいます)。目的の結果を引き出し、効率的に行うには、なじみのないパラダイムを学習し、最も馴染みのあるプログラミングパターンの一部がここでは機能しないことを発見することを特徴とする退屈なプロセスを伴います。あなたが見た(またはコミットした)一般的なアンチパターンは何ですか?
データアクセス層にUIロジックを混在させるほとんどのプログラマーの傾向には、常に失望しています。
SELECT
FirstName + ' ' + LastName as "Full Name",
case UserRole
when 2 then "Admin"
when 1 then "Moderator"
else "User"
end as "User's Role",
case SignedIn
when 0 then "Logged in"
else "Logged out"
end as "User signed in?",
Convert(varchar(100), LastSignOn, 101) as "Last Sign On",
DateDiff('d', LastSignOn, getDate()) as "Days since last sign on",
AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' +
City + ', ' + State + ' ' + Zip as "Address",
'XXX-XX-' + Substring(
Convert(varchar(9), SSN), 6, 4) as "Social Security #"
FROM Users
通常、プログラマーは、データセットを直接グリッドにバインドすることを意図しているため、これを行います。SQLServerをクライアント側の形式よりもサーバー側形式にすると便利です。
上記のようなクエリは、データレイヤーをUIレイヤーに緊密に結合するため、非常に脆弱です。さらに、このスタイルのプログラミングは、ストアドプロシージャの再利用を徹底的に防ぎます。
これが私のトップ3です。
番号1。フィールドリストの指定の失敗。 (編集:混乱を避けるため:これは量産コードのルールです。私が著者でない限り、1回限りの分析スクリプトには適用されません。)
SELECT *
Insert Into blah SELECT *
あるべき
SELECT fieldlist
Insert Into blah (fieldlist) SELECT fieldlist
番号2。カーソルとwhileループを使用し、ループ変数を使用したwhileループが実行される場合。
DECLARE @LoopVar int
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable)
WHILE @LoopVar is not null
BEGIN
-- Do Stuff with current value of @LoopVar
...
--Ok, done, now get the next value
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable
WHERE @LoopVar < TheKey)
END
数字3。文字列型によるDateLogic。
--Trim the time
Convert(Convert(theDate, varchar(10), 121), datetime)
あるべき
--Trim the time
DateAdd(dd, DateDiff(dd, 0, theDate), 0)
私は最近、「1つのクエリが2つよりも優れているのは間違いない」という急増を見ました。
SELECT *
FROM blah
WHERE (blah.Name = @name OR @name is null)
AND (blah.Purpose = @Purpose OR @Purpose is null)
このクエリには、パラメータの値に応じて2つまたは3つの異なる実行プランが必要です。 1つの実行プランのみが生成され、このSQLテキストのキャッシュにスタックされます。その計画は、パラメーターの値に関係なく使用されます。これにより、パフォーマンスが断続的に低下します。 2つのクエリ(目的の実行プランごとに1つのクエリ)を記述する方がはるかに優れています。
人間が読めるパスワードフィールド、egad。自明。
インデックス付きのLIKE列を使用し、一般的にLIKEとだけ言いたいと思います。
SQLで生成されたPK値のリサイクル。
誰も言及していないサプライズ神のテーブルまだ。 100列のビットフラグ、大きな文字列、整数のような「オーガニック」というものはありません。
次に、「。iniファイルが見つかりません」パターン:CSV、パイプ区切り文字列、またはその他の解析に必要なデータを大きなテキストフィールドに格納します。
また、MS SQLサーバーの場合は、カーソルをすべて使用します。特定のカーソルタスクを実行するより良い方法があります。
たくさんあるので編集しました!
深く掘り下げる必要はありません。準備済みステートメントを使用しないでください。
無意味なテーブルエイリアスを使用する:
from employee t1,
department t2,
job t3,
...
大きなSQLステートメントの読み取りが必要以上に難しくなります
var query = "select COUNT(*) from Users where UserName = '"
+ tbUser.Text
+ "' and Password = '"
+ tbPassword.Text +"'";
私のバグベアは、マネージングディレクターの親友であるドッググルーマーの8歳の息子によってまとめられた450列のAccessテーブルと、データ構造を適切に正規化する方法がわからないためにのみ存在する危険なルックアップテーブルです。
通常、このルックアップテーブルは次のようになります。
ID INT、 Name NVARCHAR(132)、 IntValue1 INT、 IntValue2 INT、 CharValue1 NVARCHAR(255)、 CharValue2 NVARCHAR(255)、 Date1 DATETIME、 Date2 DATETIME
私は、このような憎悪に依存するシステムを持っている人を見たことがありますが、その数を失いました。
私が一番嫌いなのは
テーブル、Sprocなどを作成するときにスペースを使用します。CamelCaseまたはunder_scoresと単数形または複数形および大文字または小文字で問題ありませんが、テーブルまたは列を参照する必要があります。私はこれに遭遇しました)本当に私をいらいらさせます。
非正規化データ。テーブルを完全に正規化する必要はありませんが、現在の評価スコアや主なものに関する情報を持っている従業員のテーブルに出くわすと、ある時点で別のテーブルを作成する必要があり、その後、それらの同期を維持してください。最初にデータを正規化し、次に非正規化が役立つ場所が見つかったらそれを検討します。
ビューまたはカーソルの過剰使用。ビューには目的がありますが、各テーブルがビューでラップされている場合は多すぎます。私は数回カーソルを使用しなければなりませんでしたが、一般的にこれには他のメカニズムを使用できます。
アクセス。プログラムはアンチパターンにできますか?私の職場にはSQL Serverがありますが、多くの人が、技術に詳しくないユーザーにとっての使いやすさ、使いやすさ、使いやすさからアクセスを使用しています。ここにはあまりにも多くの情報がありますが、似たような環境にいたことがあるなら知っています。
ストアプロシージャ名のプレフィックスとしてSPを使用します。カスタムプロシージャではなく、システムプロシージャの場所を最初に検索するためです。
一時テーブルとカーソルの過剰使用。
時間値を保存するには、UTCタイムゾーンのみを使用する必要があります。現地時間は使用しないでください。
SCOPE_IDENTITY()の代わりに@@ IDENTITYを使用
この回答 から引用:
「デッド」フィールドを意図していないものに再利用する(例:「ファクス」フィールドにユーザーデータを保存する)
select some_column, ...
from some_table
group by some_column
そして、結果がsome_columnでソートされると仮定します。仮定が当てはまるSybaseでこれを少し見ました(今のところ)。
SELECT FirstName + ' ' + LastName as "Full Name", case UserRole when 2 then "Admin" when 1 then "Moderator" else "User" end as "User's Role", case SignedIn when 0 then "Logged in" else "Logged out" end as "User signed in?", Convert(varchar(100), LastSignOn, 101) as "Last Sign On", DateDiff('d', LastSignOn, getDate()) as "Days since last sign on", AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' + City + ', ' + State + ' ' + Zip as "Address", 'XXX-XX-' + Substring(Convert(varchar(9), SSN), 6, 4) as "Social Security #" FROM Users
または、すべてを1行に詰め込みます。
FROM TableA, TableB WHERE
ではなく、JOINSのFROM TableA INNER JOIN TableB ON
構文
クエリツールでのテスト中に表示されたという理由だけで、ORDER BY句を挿入せずに、クエリが特定の方法で並べ替えられて返されることを前提としています。
キャリアの最初の6か月でSQLを学習し、今後10年間は他に何も学習しない。特に、ウィンドウ/分析SQL機能を学習していないか、効果的に使用していない。特に、over()とpartition byの使用。
集計関数のようなウィンドウ関数は、定義された行のセット(グループ)で集計を実行しますが、ウィンドウ関数はグループごとに1つの値を返すのではなく、グループごとに複数の値を返すことができます。
ウィンドウ関数の素晴らしい概要については O'Reilly SQL Cookbook Appendix A をご覧ください。
リストを完成させるために、ここに自分の現在のお気に入りを入れる必要があります。私の好きなアンチパターンは クエリをテストしない。
これは次の場合に適用されます。
また、非定型または不十分なデータに対して実行されるテストはカウントされません。ストアドプロシージャの場合は、テストステートメントをコメントに入れて、結果とともに保存します。それ以外の場合は、結果と共にコード内のコメントに入れてください。
一時テーブルの乱用。
具体的にはこの種のもの:
SELECT personid, firstname, lastname, age
INTO #tmpPeople
FROM People
WHERE lastname like 's%'
DELETE FROM #tmpPeople
WHERE firstname = 'John'
DELETE FROM #tmpPeople
WHERE firstname = 'Jon'
DELETE FROM #tmpPeople
WHERE age > 35
UPDATE People
SET firstname = 'Fred'
WHERE personid IN (SELECT personid from #tmpPeople)
不要な行を削除するためだけに、クエリから一時テーブルを作成しないでください。
そして、はい、私はこの形式のコードのページを本番DBで見ました。
反対の見解:正規化への過度の執着。
ほとんどのSQL/RBDBシステムは、正規化されていないデータであっても非常に役立つ1つの多くの機能(トランザクション、レプリケーション)を提供します。ディスク容量は安価であり、1NFスキーマを作成し、その中のすべての面倒な処理(複雑な結合、厄介な副選択)を処理するよりも、フェッチしたデータを操作/フィルター/検索する方が簡単な場合があります(コードがより簡単で、開発時間が短縮されます)など)。
過剰に正規化されたシステムは、特に開発の初期段階で、しばしば時期尚早な最適化であることがわかりました。
(それについてのさらなる考察... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/ )
SOのSQL応答のいくつかに基づいて、これをまとめました。
イベントハンドラはOOPに対するものであり、トリガーはデータベースに対するものであると考えるのは深刻なアンチパターンです。トランザクション(イベント)がテーブルで発生したときに起動される古いロジックだけをトリガーに入れることができるという認識があります。
違います。大きな違いの1つは、トリガーが同期であるということです。トリガーは行操作ではなくセット操作で同期するため、復を伴います。 OOP側では、まったく逆です-イベントは非同期トランザクションを実装する効率的な方法です。
コメントなしのストアドプロシージャまたは関数...
変更されたビュー-頻繁に変更され、通知や理由なく変更されたビュー。変更は、最も不適切な時間に通知されるか、さらに悪いことに、誤って通知されることはありません。誰かがその列のより良い名前を考えたために、アプリケーションが壊れる可能性があります。原則として、ビューは、消費者との契約を維持しながら、ベーステーブルの有用性を拡張する必要があります。問題を修正しますが、機能を追加したり動作を悪化させたりしないでください。そのため、新しいビューが作成されます。軽減するには、他のプロジェクトとビューを共有しないようにし、プラットフォームで許可されている場合は CTEs を使用します。ショップにDBAがいる場合、おそらくビューを変更することはできませんが、その場合、すべてのビューが古くなったり、役に立たなくなったりします。
!Paramed-クエリには複数の目的がありますか?おそらくしかしそれを読む次の人は深い瞑想まで知らないでしょう。今すぐそれらを必要としない場合でも、デバッグするだけの場合でもチャンスがあります。パラメータを追加すると、メンテナンス時間が短縮され、物事が乾燥したままになります。 where句がある場合、パラメーターが必要です。
ケースなしの場合-
SELECT
CASE @problem
WHEN 'Need to replace column A with this medium to large collection of strings hanging out in my code.'
THEN 'Create a table for lookup and add to your from clause.'
WHEN 'Scrubbing values in the result set based on some business rules.'
THEN 'Fix the data in the database'
WHEN 'Formating dates or numbers.'
THEN 'Apply formating in the presentation layer.'
WHEN 'Createing a cross tab'
THEN 'Good, but in reporting you should probably be using cross tab, matrix or pivot templates'
ELSE 'You probably found another case for no CASE but now I have to edit my code instead of enriching the data...' END
1)「公式の」アンチパターンであることはわかりませんが、データベース列のマジック値としての文字列リテラルを嫌い、回避しようとしています。
MediaWikiのテーブル 'image'の例:
img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO",
"MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
img_major_mime ENUM("unknown", "application", "audio", "image", "text",
"video", "message", "model", "multipart") NOT NULL default "unknown",
(私は別のケースを避けるために別のことに気づくだけです)
Int主キーを持つテーブルImageMediaTypeおよびImageMajorMimeへのintルックアップなどのケースを設計します。
2)特定のNLS設定に依存する日付/文字列変換
CONVERT(NVARCHAR, GETDATE())
フォーマット識別子なし
クエリ内の同一のサブクエリ。
私が最も見つけ、パフォーマンスの面でかなりのコストがかかる可能性のある2つは次のとおりです。
セットベースの式の代わりにカーソルを使用します。これは、プログラマーが手順を踏んで考えているときに頻繁に起こると思います。
派生テーブルへの結合がジョブを実行できる場合、相関サブクエリを使用します。
一時テーブル、特にSQL ServerからOracleに切り替える人は、一時テーブルを使いすぎる傾向があります。ネストされた選択ステートメントを使用するだけです。
SQLアプリケーション(個々のクエリとマルチユーザーシステムの両方)を高速化または低速化する理由をよく理解せずにクエリを記述する開発者。これには、以下に関する無知が含まれます。
ISAM(インデックス付きシーケンシャルアクセス方式)パッケージとしてのSQLの使用。特に、SQLステートメントを1つの大きなステートメントに結合する代わりに、カーソルをネストします。実際にはオプティマイザーができることはあまりないため、これは「オプティマイザーの乱用」としてもカウントされます。これは、最大の非効率性のために準備されていないステートメントと組み合わせることができます。
DECLARE c1 CURSOR FOR SELECT Col1, Col2, Col3 FROM Table1
FOREACH c1 INTO a.col1, a.col2, a.col3
DECLARE c2 CURSOR FOR
SELECT Item1, Item2, Item3
FROM Table2
WHERE Table2.Item1 = a.col2
FOREACH c2 INTO b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
END FOREACH
(ほとんどの場合)正しい解決策は、2つのSELECTステートメントを1つに結合することです。
DECLARE c1 CURSOR FOR
SELECT Col1, Col2, Col3, Item1, Item2, Item3
FROM Table1, Table2
WHERE Table2.Item1 = Table1.Col2
-- ORDER BY Table1.Col1, Table2.Item1
FOREACH c1 INTO a.col1, a.col2, a.col3, b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
ダブルループバージョンの唯一の利点は、内側のループが終了するため、Table1の値間のブレークを簡単に見つけることができることです。これは、コントロールブレークレポートの要因になる可能性があります。
また、通常、アプリケーションでの並べ替えは不可です。
アプリケーションの結合SQLの問題だけでなく、問題の説明を探してこの質問を見つけると、リストに載っていないことに驚きました。
使用されるフレーズを聞いたように、アプリケーション結合とは、2つ以上のテーブルのそれぞれから行のセットを引き出し、ネストされたforループのペアを使用して(Java)コードでそれらを結合することです。これにより、システム(アプリとデータベース)がクロスプロダクト全体を識別し、それを取得してアプリケーションに送信する必要があります。アプリがデータベースと同じ速さでクロス積をフィルター処理できると仮定すると(疑わしい)、結果セットをより早くカットするだけでデータ転送が少なくなります。
EXISTS
を完全に気づかずに、IN (...)
に愛情を込めて持ちこたえている人が多すぎます。良い例については、Symfony Propel ORMを参照してください。
私はこのようなビュー定義に出会いました:
CREATE OR REPLACE FORCE VIEW PRICE (PART_NUMBER, PRICE_LIST, LIST_VERSION ...)
AS
SELECT sp.MKT_PART_NUMBER,
sp.PRICE_LIST,
sp.LIST_VERSION,
sp.MIN_PRICE,
sp.UNIT_PRICE,
sp.MAX_PRICE,
...
ビューには50個程度の列があります。一部の開発者は、列のエイリアスを提供しないことで他の人を苦しめているため、ビューのどの列が対応するかを把握するために、両方の場所で列のオフセットをカウントする必要があります。
レコードアドレスの代理として主キーを使用し、レコードに埋め込まれたポインターの代理として外部キーを使用します。
1つのテーブルを持っている
code_1
value_1
code_2
value_2
...
code_10
value_10
3つのテーブルを持つ代わりに
コード、値、およびcode_value
10組以上のコード、値が必要になる場合があります。
必要なカップルが1つだけの場合、ディスク容量を無駄にしません。
私のお気に入りのSQLアンチパターン:
JOIN
一意でない列でSELECT DISTINCT
を使用して結果をトリミングします。
1つのテーブルからいくつかの列を選択するためだけに、多くのテーブルを結合するビューを作成します。
CREATE VIEW my_view AS
SELECT * FROM table1
JOIN table2 ON (...)
JOIN table3 ON (...);
SELECT col1, col2 FROM my_view WHERE col3 = 123;
re:SCOPE_IDENTITY()の代わりに@@ IDENTITYを使用
どちらも使用しないでください。代わりに出力を使用する
冗長テーブルを次のようなクエリに結合します。
select emp.empno, dept.deptno
from emp
join dept on dept.deptno = emp.deptno;
アンチパターンではないかもしれませんが、特定のDBのDBA(ここでOracleについて話しているのですが)がOracleスタイルとコード規則を使用してSQL Serverコードを記述し、それが非常に悪いときに文句を言うとき、私を困らせます。カーソルOracleの人々で十分です! SQLは設定ベースになっています。
With句または適切な結合を使用せず、サブクエリに依存します。
アンチパターン:
select
...
from data
where RECORD.STATE IN (
SELECT STATEID
FROM STATE
WHERE NAME IN
('Published to test',
'Approved for public',
'Published to public',
'Archived'
))
より良い:
with句を使用して、意図を読みやすくします。
with valid_states as (
SELECT STATEID
FROM STATE
WHERE NAME IN
('Published to test',
'Approved for public',
'Published to public',
'Archived'
)
select ... from data, valid_states
where data.state = valid_states.state
ベスト:
select
...
from data join states using (state)
where
states.state in ('Published to test',
'Approved for public',
'Published to public',
'Archived'
)