リレーショナルデータベースを扱う私たち全員が、SQLが異なることを学びました(または学び始めました)。望ましい結果を引き出して効率的に行うには、不慣れなパラダイムを習得し、最も身近なプログラミングパターンの一部がここでは機能しないことを発見することを部分的に特徴とする退屈なプロセスを伴います。あなたが見た(またはあなた自身が犯した)一般的なアンチパターンは何ですか?
リレーショナルデータベースを扱う私たち全員が、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
通常、プログラマはデータセットをグリッドに直接バインドするつもりで、SQL Serverフォーマットをクライアント側のフォーマットよりもサーバー側にすると便利なので、これを行います。
上に示したようなクエリは、データレイヤーを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サーバーでは、カーソルをまったく使用しません。特定のカーソルタスクを実行するためのより良い方法があります。
たくさんあるので編集しました!
LIKE '%LIKE'
です。
深く掘り下げる必要はありません。準備されたステートメントを使用しないでください。
無意味なテーブルエイリアスの使用:
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、 名前NVARCHAR(132)、 IntValue1 INT、 IntValue2 INT、 CharValue1 NVARCHAR(255)、 CharValue2 NVARCHAR(255)、 Date1 DATETIME、 日付2日時
このような忌まわしさに依存するシステムを持っているクライアントを見たことがありません。
一番嫌いなのは
テーブル、sprocsなどを作成するときにスペースを使用します。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クックブックの付録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)
クエリから一時テーブルを作成しないでください。不要な行を削除するだけです。
そして、はい、私は本番データベースでこの形式のコードのページを見てきました。
逆張りの見方:正規化へのこだわり。
ほとんどのSQL / RBDBシステムは、正規化されていないデータでも非常に役立つ1つの多くの機能(トランザクション、レプリケーション)を提供します。ディスク容量は安価であり、フェッチされたデータを操作/フィルタリング/検索する方が、1NFスキーマを作成してその中のすべての手間(複雑な結合、厄介な副選択)を処理するよりも簡単(コードが簡単で開発時間が短い)の場合があります、など)。
特に、開発の初期段階では、過剰に正規化されたシステムはしばしば時期尚早の最適化であることがわかりました。
(それについてのより多くの考え... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/)
1)それが「公式の」アンチパターンであることは知りませんが、データベース列の文字列リテラルを魔法の値として使用しないようにします。
MediaWikiのテーブル「イメージ」の例:
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())
フォーマット識別子なし
クエリ内の同一のサブクエリ。
変更されたビュー-通知や理由なしに頻繁に変更されるビュー。変更は、最も不適切なときに通知されるか、またはさらに悪いことに、通知されない場合があります。誰かがその列のより良い名前を考えたため、アプリケーションが壊れる可能性があります。原則として、ビューは、コンシューマとの契約を維持しながら、ベーステーブルの有用性を拡張する必要があります。問題を修正しますが、機能を追加したり、動作を変更したりしないでください。これにより、新しいビューが作成されます。軽減するには、他のプロジェクトとビューを共有せず、プラットフォームで許可されている場合はCTEを使用します。ショップに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
私が最も多く見つけ、パフォーマンスの点でかなりのコストがかかる可能性がある2つは次のとおりです。
セットベースの式の代わりにカーソルを使用する。これは、プログラマーが手順に従って考えているときに頻繁に発生すると思います。
派生したテーブルへの結合でジョブを実行できる場合の、相関サブクエリの使用。
SQLアプリケーション(個々のクエリとマルチユーザーシステムの両方)が高速または低速になる理由についてよく理解していないクエリを作成する開発者。これには、以下に関する無知が含まれます。
栄光のISAM(Indexed Sequential Access Method)パッケージとしての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
二重ループバージョンの唯一の利点は、内部ループが終了するため、表1の値の間の区切りを簡単に見つけることができることです。これは、コントロールブレークレポートの要素になる可能性があります。
また、アプリケーションでの並べ替えは通常、ノーノーです。
レコードのアドレスの代理として主キーを使用し、レコードに埋め込まれたポインタの代理として外部キーを使用します。