リレーショナル データベースを扱っている私たち全員が、SQL が他とは異なることを知っています (または学んでいます)。望ましい結果を引き出し、それを効率的に行うには、なじみのないパラダイムを学習し、最もよく知られているプログラミング パターンのいくつかがここでは機能しないことを発見することを部分的に特徴とする、退屈なプロセスが必要です。あなたが見た (またはあなた自身がコミットした) 一般的なアンチパターンは何ですか?
39 に答える
ほとんどのプログラマーがデータ アクセス レイヤーに 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 つの異なる実行プランが必要です。この SQL テキストの実行計画が 1 つだけ生成され、キャッシュに入れられます。そのプランは、パラメーターの値に関係なく使用されます。これにより、パフォーマンスが断続的に低下します。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列のアクセステーブルと、誰かがデータ構造を適切に正規化する方法を知らないためにのみ存在する危険なルックアップテーブルです。
通常、このルックアップテーブルは次のようになります。
ID INT、 名前NVARCHAR(132)、 IntValue1 INT、 IntValue2 INT、 CharValue1 NVARCHAR(255)、 CharValue2 NVARCHAR(255)、 Date1 DATETIME、 Date2 DATETIME
私は、このような忌まわしきものに依存するシステムを持っている私が見たクライアントの数の数を失いました。
私が最も嫌いなものは
テーブルや sprocs などを作成するときにスペースを使用します。キャメルケースまたは under_scores、単数または複数形、大文字または小文字は問題ありませんが、[スペースを含む] テーブルまたは列を参照する必要があります。私はこれに出くわしました)本当に私を苛立たせます。
非正規化データ。テーブルを完全に正規化する必要はありませんが、従業員の現在の評価スコアや主要なものに関する情報を含むテーブルに出くわすと、おそらく別のテーブルを作成する必要があることがわかります。次に、同期を維持してください。最初にデータを正規化し、非正規化が役立つ場所があれば検討します。
ビューまたはカーソルの過剰使用。ビューには目的がありますが、各テーブルがビューにラップされていると、多すぎます。カーソルを数回使用する必要がありましたが、通常、これには他のメカニズムを使用できます。
アクセス。プログラムはアンチパターンになり得ますか? 私の職場には SQL Server がありますが、その可用性、「使いやすさ」、および非技術者ユーザーへの「親しみやすさ」のために、多くの人がアクセスを使用しています。ここには入り込むことが多すぎますが、同様の環境にいたことがある場合はわかります。
カスタム プロシージャではなくシステム プロシージャの場所で最初に検索されるため、ストア プロシージャ名のプレフィックスとして SP を使用します。
一時テーブルとカーソルの過剰使用。
時刻の値を格納するには、UTC タイムゾーンのみを使用する必要があります。現地時間は使用しないでください。
意図されていないものに「デッド」フィールドを再利用する(たとえば、「ファックス」フィールドにユーザーデータを保存する)-しかし、簡単な修正として非常に魅力的です!
SCOPE_IDENTITY() の代わりに@@IDENTITY を使用する
この回答から引用:
- @@IDENTITYは、すべてのスコープにわたって、現在のセッションの任意のテーブルに対して生成された最後の ID 値を返します。範囲をまたいでいるので注意が必要です。現在のステートメントではなく、トリガーから値を取得できます。
- SCOPE_IDENTITYは、現在のセッションおよび現在のスコープで任意のテーブルに対して生成された最後の ID 値を返します。一般的に使用したいもの。
- IDENT_CURRENTは、任意のセッションおよび任意のスコープで特定のテーブルに対して生成された最後の ID 値を返します。これにより、上記の 2 つが必要なものではない場合 (非常にまれ) に、どのテーブルから値を取得するかを指定できます。レコードを挿入していないテーブルの現在の IDENTITY 値を取得する場合は、これを使用できます。
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 行に詰め込みます。
select some_column, ...
from some_table
group by some_column
結果がsome_columnでソートされると仮定します。仮定が成り立つ Sybase でこれを少し見てきました (今のところ)。
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)
必要のない行を削除するためだけに、クエリから一時テーブルを作成しないでください。
はい、本番データベースでこの形式のコードのページを見たことがあります。
逆張りの見方: 正規化への過度の執着。
ほとんどの SQL/RBDB システムは、正規化されていないデータでも非常に役立つ多くの機能 (トランザクション、レプリケーション) を提供します。ディスク容量は安価であり、フェッチされたデータを操作/フィルタリング/検索する方が、1NF スキーマを作成し、その中のすべての手間 (複雑な結合、厄介なサブセレクト) を処理するよりも簡単な場合があります (コードが簡単になり、開発時間が短縮されます)。など)。
過度に正規化されたシステムは、特に開発の初期段階では、最適化が時期尚早であることが多いことがわかりました。
(それについてのより多くの考え... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/)
コメントのないストアド プロシージャまたは関数...
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())
フォーマット識別子なし
The Altered View - 通知や理由なく頻繁に変更されるビュー。変更は、最も不適切なタイミングで気付くか、さらに悪いことに、間違っていてまったく気付かないかのいずれかです。誰かがその列のより良い名前を考えたために、アプリケーションが壊れる可能性があります。原則として、ビューは、消費者との契約を維持しながら、ベース テーブルの有用性を拡張する必要があります。問題を修正しますが、機能を追加したり、動作を悪化させたりしないでください。そのためには、新しいビューを作成します。軽減するには、ビューを他のプロジェクトと共有せず、プラットフォームが許可する場合はCTEを使用します。ショップに DBA がいる場合、おそらくビューを変更することはできませんが、その場合、すべてのビューが古くなって役に立たなくなります。
!Paramed - クエリには複数の目的がありますか? おそらくですが、次にそれを読む人は、深く瞑想するまでわからないでしょう。デバッグするのが「ただ」であっても、今はそれらを必要としない可能性があります。パラメータを追加すると、メンテナンス時間が短縮され、物事が DRY に保たれます。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
クエリ内の同一のサブクエリ。
SQL アプリケーション (個々のクエリとマルチユーザー システムの両方) を高速または低速にする理由をよく理解せずにクエリを作成する開発者。これには、以下に関する無知が含まれます。
- ほとんどのクエリのボトルネックが CPU ではなく I/O であることを考えると、物理 I/O 最小化戦略
- さまざまな種類の物理ストレージ アクセスのパフォーマンスへの影響 (たとえば、大量のシーケンシャル I/O は多数の小さなランダム I/O よりも高速ですが、物理ストレージが SSD の場合はそれほど速くはありません!)
- DBMS が不適切なクエリ プランを生成する場合にクエリを手動で調整する方法
- データベースのパフォーマンスの低下を診断する方法、遅いクエリを「デバッグ」する方法、クエリ プランを読み取る方法 (選択した DBMS によっては EXPLAIN)
- スループットを最適化し、マルチユーザー アプリケーションでのデッドロックを回避するためのロック戦略
- データセットの処理を処理するためのバッチ処理およびその他のトリックの重要性
- スペースとパフォーマンスのバランスを最適化するためのテーブルとインデックスの設計 (たとえば、インデックスをカバーする、インデックスを可能な限り小さく保つ、データ型を必要最小限のサイズに減らすなど)。
私が最もよく見つけ、パフォーマンスの面でかなりのコストがかかる可能性があるのは次の 2 つです。
セットベースの式の代わりにカーソルを使用します。これは、プログラマーが手順を追って考えているときによくあることだと思います。
相関サブクエリを使用して、派生テーブルへの結合がジョブを実行できる場合。
一時テーブルに何かを入れる、特に SQL Server から Oracle に切り替える人は、一時テーブルを過剰に使用する習慣があります。ネストされた select ステートメントを使用するだけです。
主キーをレコード アドレスの代理として使用し、外部キーをレコードに埋め込まれたポインタの代理として使用します。
アプリケーションの結合SQLの問題だけでなく、問題の説明を探してこの質問を見つけたところ、リストに載っていないことに驚きました。
使用されているフレーズを聞いたように、アプリケーション結合とは、2つ以上のテーブルのそれぞれから行のセットを引き出し、ネストされたforループのペアを使用して(Java)コードでそれらを結合することです。これは、クロス積全体を識別し、それを取得してアプリケーションに送信する必要があるため、システム(アプリとデータベース)に負担をかけます。アプリがクロス積をデータベースと同じ速さでフィルタリングできると仮定すると(疑わしい)、結果セットをより早く削減するだけで、データ転送が少なくなります。
SQL を美化された ISAM (Indexed Sequential Access Method) パッケージとして使用する。具体的には、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 の値の間の区切りを簡単に見つけられることです。これは、コントロール ブレーク レポートの要因になる可能性があります。
また、アプリケーションでの並べ替えは、通常はノーノーです。
私はあまりにも多くの人々IN (...)
がEXISTS
. 良い例については、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、code_value
あなたはあなたが10以上のカップルのコード、価値をいつ必要とするかもしれないかを決して知りません。
必要なカップルが1つだけの場合でも、ディスクスペースを無駄にすることはありません。
re:SCOPE_IDENTITY()の代わりに@@IDENTITYを使用
どちらも使用しないでください。代わりに出力を使用してください
次のようなクエリに冗長なテーブルを結合します。
select emp.empno, dept.deptno
from emp
join dept on dept.deptno = emp.deptno;
私のお気に入りの 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;
おそらくアンチパターンではないかもしれませんが、特定のDBのDBA(ここではOracleについて話しています)がOracleスタイルとコード規則を使用してSQL Serverコードを記述し、実行が非常に悪いと文句を言うと、私はイライラします。カーソルのオラクルの人々で十分です!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'
)