web-dev-qa-db-ja.com

最も一般的なSQLアンチパターンは何ですか?

リレーショナルデータベースを扱う私たちは皆、SQLが異なることを学んでいます(または学んでいます)。目的の結果を引き出し、効率的に行うには、なじみのないパラダイムを学習し、最も馴染みのあるプログラミングパターンの一部がここでは機能しないことを発見することを特徴とする退屈なプロセスを伴います。あなたが見た(またはコミットした)一般的なアンチパターンは何ですか?

223
dkretz

データアクセス層に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レイヤーに緊密に結合するため、非常に脆弱です。さらに、このスタイルのプログラミングは、ストアドプロシージャの再利用を徹底的に防ぎます。

148
Juliet

これが私のトップ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つのクエリ)を記述する方がはるかに優れています。

114
Amy B
  • 人間が読めるパスワードフィールド、egad。自明。

  • インデックス付きのLIKE列を使用し、一般的にLIKEとだけ言いたいと思います。

  • SQLで生成されたPK値のリサイクル。

  • 誰も言及していないサプライズ神のテーブルまだ。 100列のビットフラグ、大きな文字列、整数のような「オーガニック」というものはありません。

  • 次に、「。iniファイルが見つかりません」パターン:CSV、パイプ区切り文字列、またはその他の解析に必要なデータを大きなテキストフィールドに格納します。

  • また、MS SQLサーバーの場合は、カーソルをすべて使用します。特定のカーソルタスクを実行するより良い方法があります。

たくさんあるので編集しました!

68
annakata

深く掘り下げる必要はありません。準備済みステートメントを使用しないでください。

60
stesch

無意味なテーブルエイリアスを使用する:

from employee t1,
department t2,
job t3,
...

大きなSQLステートメントの読み取りが必要以上に難しくなります

56
Tony Andrews
var query = "select COUNT(*) from Users where UserName = '" 
            + tbUser.Text 
            + "' and Password = '" 
            + tbPassword.Text +"'";
  1. ユーザー入力を盲目的に信頼する
  2. パラメータ化されたクエリ を使用しない
  3. クリアテキストのパスワード
51
Will

私のバグベアは、マネージングディレクターの親友であるドッググルーマーの8歳の息子によってまとめられた450列のAccessテーブルと、データ構造を適切に正規化する方法がわからないためにのみ存在する危険なルックアップテーブルです。

通常、このルックアップテーブルは次のようになります。

 ID INT、
 Name NVARCHAR(132)、
 IntValue1 INT、
 IntValue2 INT、
 CharValue1 NVARCHAR(255)、
 CharValue2 NVARCHAR(255)、
 Date1 DATETIME、
 Date2 DATETIME 

私は、このような憎悪に依存するシステムを持っている人を見たことがありますが、その数を失いました。

46
Pete OHanlon

私が一番嫌いなのは

  1. テーブル、Sprocなどを作成するときにスペースを使用します。CamelCaseまたはunder_scoresと単数形または複数形および大文字または小文字で問題ありませんが、テーブルまたは列を参照する必要があります。私はこれに遭遇しました)本当に私をいらいらさせます。

  2. 非正規化データ。テーブルを完全に正規化する必要はありませんが、現在の評価スコアや主なものに関する情報を持っている従業員のテーブルに出くわすと、ある時点で別のテーブルを作成する必要があり、その後、それらの同期を維持してください。最初にデータを正規化し、次に非正規化が役立つ場所が見つかったらそれを検討します。

  3. ビューまたはカーソルの過剰使用。ビューには目的がありますが、各テーブルがビューでラップされている場合は多すぎます。私は数回カーソルを使用しなければなりませんでしたが、一般的にこれには他のメカニズムを使用できます。

  4. アクセス。プログラムはアンチパターンにできますか?私の職場にはSQL Serverがありますが、多くの人が、技術に詳しくないユーザーにとっての使いやすさ、使いやすさ、使いやすさからアクセスを使用しています。ここにはあまりにも多くの情報がありますが、似たような環境にいたことがあるなら知っています。

28
Jamal Hansen

ストアプロシージャ名のプレフィックスとしてSPを使用します。カスタムプロシージャではなく、システムプロシージャの場所を最初に検索するためです。

26
Oscar Cabrero

一時テーブルとカーソルの過剰使用。

25
Rockcoder

時間値を保存するには、UTCタイムゾーンのみを使用する必要があります。現地時間は使用しないでください。

24

SCOPE_IDENTITY()の代わりに@@ IDENTITYを使用

この回答 から引用:

  • @@ IDENTITY は、すべてのスコープにわたって、現在のセッションのテーブルに対して生成された最後のID値を返します。ここで注意する必要があります、それはスコープを超えているからです。現在のステートメントの代わりに、トリガーから値を取得できます。
  • SCOPE_IDENTITY は、現在のセッションおよび現在のスコープ内のテーブルに対して生成された最後のID値を返します。一般的に使用するもの。
  • IDENT_CURRENT は、セッションおよびスコープの特定のテーブルに対して生成された最後のID値を返します。これにより、上記の2つが必要なものではない場合(非常にまれ)に、値を取得するテーブルを指定できます。これは、レコードを挿入していないテーブルの現在のIDENTITY値を取得する場合に使用できます。
23
Brann

「デッド」フィールドを意図していないものに再利用する(例:「ファクス」フィールドにユーザーデータを保存する)

23
FruitBreak
select some_column, ...
from some_table
group by some_column

そして、結果がsome_columnでソートされると仮定します。仮定が当てはまるSybaseでこれを少し見ました(今のところ)。

21
Adrian Pronk
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行に詰め込みます。

20
Jasper Bekkers
  • FROM TableA, TableB WHEREではなく、JOINSのFROM TableA INNER JOIN TableB ON構文

  • クエリツールでのテスト中に表示されたという理由だけで、ORDER BY句を挿入せずに、クエリが特定の方法で並べ替えられて返されることを前提としています。

17
Joel Coehoorn

キャリアの最初の6か月でSQLを学習し、今後10年間は​​他に何も学習しない。特に、ウィンドウ/分析SQL機能を学習していないか、効果的に使用していない。特に、over()とpartition byの使用。

集計関数のようなウィンドウ関数は、定義された行のセット(グループ)で集計を実行しますが、ウィンドウ関数はグループごとに1つの値を返すのではなく、グループごとに複数の値を返すことができます。

ウィンドウ関数の素晴らしい概要については O'Reilly SQL Cookbook Appendix A をご覧ください。

14
Brian

リストを完成させるために、ここに自分の現在のお気に入りを入れる必要があります。私の好きなアンチパターンは クエリをテストしない

これは次の場合に適用されます。

  1. クエリには複数のテーブルが含まれます。
  2. クエリの最適な設計があると思いますが、仮定をテストすることはありません。
  3. 動作する最初のクエリを受け入れますが、最適化に近いかどうかはわかりません。

また、非定型または不十分なデータに対して実行されるテストはカウントされません。ストアドプロシージャの場合は、テストステートメントをコメントに入れて、結果とともに保存します。それ以外の場合は、結果と共にコード内のコメントに入れてください。

12
dkretz

一時テーブルの乱用。

具体的にはこの種のもの:

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で見ました。

10
geofftnz

反対の見解:正規化への過度の執着。

ほとんどのSQL/RBDBシステムは、正規化されていないデータであっても非常に役立つ1つの多くの機能(トランザクション、レプリケーション)を提供します。ディスク容量は安価であり、1NFスキーマを作成し、その中のすべての面倒な処理(複雑な結合、厄介な副選択)を処理するよりも、フェッチしたデータを操作/フィルター/検索する方が簡単な場合があります(コードがより簡単で、開発時間が短縮されます)など)。

過剰に正規化されたシステムは、特に開発の初期段階で、しばしば時期尚早な最適化であることがわかりました。

(それについてのさらなる考察... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/

9
Gregg Lind

SOのSQL応答のいくつかに基づいて、これをまとめました。

イベントハンドラはOOPに対するものであり、トリガーはデータベースに対するものであると考えるのは深刻なアンチパターンです。トランザクション(イベント)がテーブルで発生したときに起動される古いロジックだけをトリガーに入れることができるという認識があります。

違います。大きな違いの1つは、トリガーが同期であるということです。トリガーは行操作ではなくセット操作で同期するため、復を伴います。 OOP側では、まったく逆です-イベントは非同期トランザクションを実装する効率的な方法です。

9
dkretz

コメントなしのストアドプロシージャまたは関数...

8
Bliek
  • 変更されたビュー-頻繁に変更され、通知や理由なく変更されたビュー。変更は、最も不適切な時間に通知されるか、さらに悪いことに、誤って通知されることはありません。誰かがその列のより良い名前を考えたために、アプリケーションが壊れる可能性があります。原則として、ビューは、消費者との契約を維持しながら、ベーステーブルの有用性を拡張する必要があります。問題を修正しますが、機能を追加したり動作を悪化させたりしないでください。そのため、新しいビューが作成されます。軽減するには、他のプロジェクトとビューを共有しないようにし、プラットフォームで許可されている場合は 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  
    
7
jason saldo

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())

フォーマット識別子なし

7
devio

クエリ内の同一のサブクエリ。

7
EvilTeach

私が最も見つけ、パフォーマンスの面でかなりのコストがかかる可能性のある2つは次のとおりです。

  • セットベースの式の代わりにカーソルを使用します。これは、プログラマーが手順を踏んで考えているときに頻繁に起こると思います。

  • 派生テーブルへの結合がジョブを実行できる場合、相関サブクエリを使用します。

5
Mitch Wheat

一時テーブル、特にSQL ServerからOracleに切り替える人は、一時テーブルを使いすぎる傾向があります。ネストされた選択ステートメントを使用するだけです。

5
tuinstoel

SQLアプリケーション(個々のクエリとマルチユーザーシステムの両方)を高速化または低速化する理由をよく理解せずにクエリを記述する開発者。これには、以下に関する無知が含まれます。

  • ほとんどのクエリのボトルネックはCPUではなくI/Oであるため、物理I/O最小化戦略
  • さまざまな種類の物理ストレージアクセスのパフォーマンスへの影響(たとえば、大量のシーケンシャルI/Oは、多数の小さなランダムI/Oよりも高速になりますが、物理ストレージがSSDの場合はそうではありません!)
  • dBMSが不適切なクエリプランを生成する場合にクエリを手動で調整する方法
  • データベースのパフォーマンスの低下を診断する方法、遅いクエリを「デバッグ」する方法、クエリプラン(または選択したDBMSに応じてEXPLAIN)を読み取る方法
  • マルチユーザーアプリケーションでスループットを最適化し、デッドロックを回避するためのロック戦略
  • データセットの処理を処理するバッチ処理およびその他のトリックの重要性
  • スペースとパフォーマンスのバランスを最適化するためのテーブルとインデックスの設計(例:インデックスのカバー、可能な限りインデックスのサイズを小さくする、必要なデータサイズを最小限に抑えるなど)
5
Justin Grant

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の値間のブレークを簡単に見つけることができることです。これは、コントロールブレークレポートの要因になる可能性があります。

また、通常、アプリケーションでの並べ替えは不可です。

3

アプリケーションの結合SQLの問題だけでなく、問題の説明を探してこの質問を見つけると、リストに載っていないことに驚きました。

使用されるフレーズを聞いたように、アプリケーション結合とは、2つ以上のテーブルのそれぞれから行のセットを引き出し、ネストされたforループのペアを使用して(Java)コードでそれらを結合することです。これにより、システム(アプリとデータベース)がクロスプロダクト全体を識別し、それを取得してアプリケーションに送信する必要があります。アプリがデータベースと同じ速さでクロス積をフィルター処理できると仮定すると(疑わしい)、結果セットをより早くカットするだけでデータ転送が少なくなります。

3
Chris

EXISTSを完全に気づかずに、IN (...)に愛情を込めて持ちこたえている人が多すぎます。良い例については、Symfony Propel ORMを参照してください。

3
sayap

私はこのようなビュー定義に出会いました:

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個程度の列があります。一部の開発者は、列のエイリアスを提供しないことで他の人を苦しめているため、ビューのどの列が対応するかを把握するために、両方の場所で列のオフセットをカウントする必要があります。

3
Tegiri Nenashi

レコードアドレスの代理として主キーを使用し、レコードに埋め込まれたポインターの代理として外部キーを使用します。

3
Walter Mitty
2
kristof

1つのテーブルを持っている

code_1
value_1
code_2
value_2
...
code_10
value_10

3つのテーブルを持つ代わりに

コード、値、およびcode_value

10組以上のコード、値が必要になる場合があります。

必要なカップルが1つだけの場合、ディスク容量を無駄にしません。

2
Luc M

私のお気に入りの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;
2
Iwo Banas

re:SCOPE_IDENTITY()の代わりに@@ IDENTITYを使用

どちらも使用しないでください。代わりに出力を使用する

cf. https://connect.Microsoft.com/SQLServer/feedback/details/328811/scope-identity-sometimes-returns-incorrect-value

2
Denis Valeev

冗長テーブルを次のようなクエリに結合します。

select emp.empno, dept.deptno
from emp
join dept on dept.deptno = emp.deptno;
2
Tony Andrews

アンチパターンではないかもしれませんが、特定のDBのDBA(ここでOracleについて話しているのですが)がOracleスタイルとコード規則を使用してSQL Serverコードを記述し、それが非常に悪いときに文句を言うとき、私を困らせます。カーソルOracleの人々で十分です! SQLは設定ベースになっています。

1
Craig

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'
                    )
0
Brian