web-dev-qa-db-ja.com

製品コードでのSELECT *の適切な使用例は何ですか?

習慣から、本番コードでSELECT *を使用することはありません(通常、オブジェクトのスキーマを学習するときに、アドホックスクラップクエリでのみ使用します)。しかし、私は今それを使いたくてたまらないケースに出くわしました。

私の使用例は、ストアドプロシージャが実行されるときはいつでも、それを作成するために使用される基になるテーブルと常に一致する必要があるローカル一時テーブルが作成されるストアドプロシージャ内です。一時テーブルにはかなり後からデータが入力されるため、特に何百もの列があるテーブルでは、冗長にならずに一時テーブルを作成する簡単なハックはSELECT * INTO #TempTable FROM RealTable WHERE 1 = 0です。

ストアドプロシージャの利用者が動的な結果セットにとらわれない場合、サービスをSELECT *に販売することに問題はありますか?

33
J.D.

私は一般にSELECT *を製品コードで嫌い、その使用が後で大量のやり直しにつながる状況にありました。あなたのケースはそれの公正使用のように見えます。

SELECT *が必須であり、その邪悪ないとこ "INSERT INTO tbl"が列リストなしであることがわかった場所は、同じ構造を持つ必要がある別のテーブルに行が移動されるアーカイブ状況です。

INSERT INTO SalesOrderArchive  -- Note no column list
SELECT *
  FROM SalesOrder
 WHERE OrderDate < @OneYearAgo

DELETE FROM SalesOrder
 WHERE OrderDate < @OneYearAgo

新しい列が将来SalesOrderに追加され、SalesOrderArchiveには追加されない場合、INSERTは失敗します。それは悪いように聞こえますが、実際には本当に良いことです!なぜなら代替案はずっと悪いからです。すべての列がINSERTおよびSELECTにリストされている場合、INSERTは成功し、次のDELETEも成功します(これは事実上「DELETE *」です)。成功するプロダクションコードには注意が向けられず、新しい列がアーカイブされていないことに気づくまでに長い時間がかかる可能性がありますが、完全にサイレントに削除されます。

48
Rusty

本番環境でのselect *の良い使い方は何ですか?

次のようなIMOのみのもの:

例えば

create table #foo(a int, b int, c int, d int)
...
select * from #foo

または

with q as
(
  select a, b, c
  from ...
)
select *
from q

つまり、*は、同じバッチで宣言されている明示的な列リストにバインドされており、列リストが複数回繰り返されることを回避するためにのみ使用されます。

*はテーブルまたはビューを参照します。たとえば here のように、キャッシュされたメタデータには厄介な複雑性があります。また、SSMSでは列リスト全体をドラッグアンドドロップできるため、実際に入力する手間が省けます。

EXISTSによって導入された場合、SELECT *の有効な使用が発生します。

WHERE EXISTS (SELECT * FROM ...

SELECT *のすべての使用と同様に、これは、コードが内部にある場合にWITH SCHEMABINDINGが追加されないようにするという歓迎されない(ここでは不要)副作用があります。機能。

関連するQ&A: EXISTS(SELECT 1 ...)vs EXISTS(SELECT * ...)One or other?

以下も参照してください。

20
user195591

太字にして言います:本番コードにはSELECT *の使用例があります。「テーブルへの変更を結果の変更にすぐに反映させたい」と言うときはいつでも出力」はそれを行う場合です。

いくつか例を挙げましょう。

ユースケース#1-重要な企業専用データを除外することを除いて、メインテーブルをミラーリングするビューを作成します。

この場合、ビューでSELECT *を使用します。出力はsupposedであり、メインテーブルを反映しています。出力レイアウトをテーブルレイアウトと一致させることは機能であり、バグではありません。

...

ユースケース#2-プロセスで問題が発生した場合に備えて、更新しようとしているレコードを一時保持テーブルにコピーするストアドプロシージャを作成します(これは少し不安定です)。

この場合、出力がテーブル自体と一致しないのはひどいです。つまり、一時保持テーブルのデータを使用して、元の場所に戻ることができない可能性があります。

7
Kevin

本番環境でのselect *の良い使い方は何ですか?

本当にありません。あなたが述べた方法で開発する場合(上位n *を選択)は理にかなっていますが、それ以外の場合は悪い習慣を発達させ、問題を引き起こす可能性があります。

すべきではないそれを使用する理由の要点に触れましたが、それは実際のテーブルの構造が変わる可能性があるということです。あなたが述べたような一時テーブルを作成することはすべて良いですが、それがあなたを後の手順で得ることができる場所は、いくつかのINSERTコマンドで一時テーブルを作成した後、SELECT *で結果セットを返します。これで、エンドアプリケーションは予期しない列を取得します。

SELECT ... INTO #TEMP FROM ...を使用して、その場で一時テーブルを作成することもできます。どちらがあなたの状況に最適であるかを知るのはちょっと難しいです。

SELECT *に関する他のほとんどの問題は、ユースケースに関係ないと思われます

  • ビューの作成(テーブルが変更されると壊れる可能性があります)
  • バインドの問題
  • テーブルスキャン(実際にはすべての列が必要なため)
  • 不要な列(すべてが再び必要になるため)とそれらの序数の位置を取得する
6
scsimon

あなたの質問に対する私の答えは「ほとんど/多くのユースケース」です

ユースケースでは、*が恐ろしいものではない理由が強調されています。スキーマが変更されます。フィールドを追加する必要があるため、なぜすべてのSPをタッチするように設定するのですか?

フィールドを追加する場合、なぜ私のUIが気になるのですか?フィールドを削除する場合、*またはCSVのどちらを使用しても問題ありません。これは重大な変更を導入することになります。

「私のストアドプロシージャのコンシューマが動的な結果セットにとらわれない場合、...」

ここに、select *を使用した場合の真の問題があります。コンシューマーの記述が不十分な場合、問題が発生する可能性があります。たとえば、Fields [13]を日付としてフォーマットする場合...

select *:を避けたい場合

レイテンシの緩和-私の消費者は遠く、彼らはテーブル内のフィールドの比較的小さなサブセットのみを必要とします

Joins-複数のテーブルを含む複雑なクエリ。おそらく、すべてのテーブルのすべてのフィールドが必要なわけではありません。実際には、おそらく省略されるべき重複キー列があります。

コンシューマーが下手なコードを書いていることを知っています-パブリックAPIを開発している場合、OpenCloseを尊重し、フィールドセットが変更されるたびに新しいapiメソッドを作成することができます。私にとって、これは上記の問題に対する悪いアプローチのようです...

プログラミングのあるものと同じように、時間と場所があります。象牙の塔のセットが常にあります。彼らの考えに耳を傾けてください。その後、あなた自身の決心をしてください。

1
greg

それはあなたが好む故障メカニズムに依存します。

新しい列が追加されたときに常に安全に無視できる場合(例:select firstname、lastname from customer where id = @ x)、決してselect *を使用しないでください-タッチする列が多いほど、役立つインデックスになる可能性が低くなります見つける/作成することができます。

新しい列が追加されたときに、その列のサイレント破棄が悪い(すべてのタイプのデータ複製)場合は、指定されていない選択を使用します(isOld = trueであるtheTableからarchiveTable select *に挿入)ここでのコードの失敗はバグではありません。これは機能です-特にデータベースとテーブルを読み取るコードの両方を制御する場合。

ここで失敗のメカニズムを逆にするのは悪いことです。人に新しいお気に入りの色を追加した場合に名前の検索が失敗するのは望ましくありません。

アーカイブコードを記述した後に列が追加された場合、その人の好きな色のアーカイブを無視したくないでしょう。

結合に関する注意-SQLサーバーは後で使用されない列の読み取りをスキップします

select firtname,email from (select * from profile where isArchived=false)p join (select * from emailaddresses where mostRecentSend < ago(6days))e on p.id=e.profileId

firstname、id、emailの列以外には触れません。

0
Andrew Hill