|
ニュース検索
ピックアップ
今週のIT求人情報
|
Transact-SQLを使用したデータベース開発に役立つ13のヒントはじめにアプリケーション開発者の多くは、SQL Server 2000を使用する中で、厄介な問題に直面します。たとえば、大量のデータの取得とグループ化、結果セットの作成、データ変更の追跡などです。いずれも、無限に近い選択肢の中から、絶妙かつ妥当な戦略をとる必要があります。その際に、ヒントがあれば助けとなるはずです。ここでは、初級開発者と熟練開発者の両方に役立つT-SQLのヒントをいくつか紹介します。 目的本稿は「The Baker’s Dozen」シリーズの一記事です。「The Baker’s Dozen」シリーズの主目的の1つは、特定の技術についてもっと上達したいと考えている人に対してヒントを提供することです。経験豊富な方にも、何かの役に立つヒントが1つくらいあるのではないかと思います。本シリーズの名前はVan Amsterdamというパン屋の話に由来しますが、私もこの数か月間、開発者の皆さんのお口に合うようなヒント集を焼き上げるためにいろいろ努力してきました。 大部分のヒントの解説では、まず業務アプリケーションの要件を1つ提示し、それを解決するためのTransact-SQLのコードを紹介するという形を取ります。今回紹介するヒントは次のとおりです。
では、実際に13のヒントを見ていきましょう。 ヒント1:複数の結果セットを返す要件:ある顧客のすべての注文情報を返す簡単なストアドプロシージャを作成する必要があります。結果セットには、「注文ヘッダー」「注文詳細」「注文メモ」という3つのテーブルを含めなくてはなりません。 SQL Server 2000の単一のストアドプロシージャで複数の結果セットを返すのは簡単です。ストアドプロシージャのSQL リスト1に示すのは、1つの引数(顧客のアカウントに対応する整数キー)を持つ、基本的なストアドプロシージャです。このプロシージャでは、「注文ヘッダー」「注文詳細」「注文メモ」の各テーブルに対してクエリを行っています。このプロシージャでは、 リスト1:複数の結果セットを返す単純なストアドプロシージャ
CREATE PROCEDURE GetOrdersByAcct (@iAcctKey integer) AS -- Retrieve Orders for a single customer -- Since we’re querying the order table three times, -- let’s create a table variable (@tOrderList) of all -- order keys for the specific customer. That way, we -- can JOIN off the temporary order list DECLARE @tOrderList TABLE (OrderKey integer) INSERT INTO @tOrderList SELECT OrderKey FROM OrderHdr WHERE AcctKey = @iAcctKey SELECT OH.*, AM.AcctName FROM OrderHdr OH JOIN AcctMast AM ON AM.AcctKey = OH.AcctKey JOIN @tOrderList TMP ON TMP.OrderKey = OH.OrderKey SELECT OD.*, PM.ProdName FROM OrderDtl OD JOIN ProdMast PM ON PM.ProdKey = OD.ProdKey JOIN @tOrderList TMP ON TMP.OrderKey = OD.OrderKey SELECT * FROM OrderNotes ON JOIN @tOrderList TMP ON TMP.OrderKey = ON.OrderKey GO これら3つのクエリには出力先が指定されていないので、結果は呼び出し元のプロシージャに返ります。このストアドプロシージャはクエリアナライザのスクリプトから実行でき、3つの結果セットが表示されます。このストアドプロシージャをC#から呼び出す場合には、データアダプタを使用して、結果セット(データセット)に対して3つのデータテーブルを次のように格納します。 SqlDataAdapter oDa = new SqlDataAdapter(cSQLString, oMyConn); oDa.Fill(DsReturn,"MyResults"); // Table 0 will contain the order headers // Table 1 will contain the order details // Table 2 will contain the order notes ストアドプロシージャの呼び出しでは、何らかの種類のデータアクセス手法を用いるのが普通です。「The Baker’s Dozen」シリーズでも、分散コンピューティング環境におけるこれらの種類の開発手法についていずれ解説します。 ヒント2:ユーザー定義関数で値を返す要件:締め日に基づいて顧客の借方と貸方の和を求め、顧客残高として返す必要があります。この計算を、複数のクエリで使用したいと考えています。 SQL Server 2000では、開発者がユーザー定義関数を作成して呼び出すことができます。ユーザー定義関数を使用すると、ロジックと機能をカプセル化して、スカラー値を返すことができ、その値を複数のクエリで使用できます。リスト2は、顧客と締め日に応じて借方と貸方のテーブルから和を求めて結果を返す簡単なユーザー定義関数です。 リスト2:顧客残高を返すユーザー定義関数
CREATE FUNCTION dbo.GetCustomerBalance (@iAcctKey integer, @dCutOffDate DateTime) -- Calculate and return balance for a Customer, -- for a given point in time RETURNS Decimal AS BEGIN DECLARE @nCreditAmount Decimal DECLARE @nDebitAmount Decimal SET @nCreditAmount = (SELECT SUM(Amount) FROM Credits WHERE AcctKey = @iAcctKey AND CompDate <= @dCutOffDate) SET @nDebitAmount = (SELECT SUM(Amount) FROM Debits WHERE AcctKey = @iAcctKey AND CompDate <= @dCutOffDate) -- depending on how data is stored, you may want to -- handle the signs differently RETURN (ISNULL(@nCreditAmount,0) - ISNULL(@nDebitAmount,0)) END この関数はクエリ内で次のように使用できます。 DECLARE @dCutOffDate DATETIME SET @dCutOffDate = CAST(’11/30/2004’ AS DateTime) SELECT CustID, dbo.GetCustomerBalance(CustID, @dCutOffDate) AS CustBalance FROM... 顧客残高を必要とするクエリがアプリケーション内に5つある場合には、それぞれの場所でこのユーザー定義関数を使用すれば済みます。同じコードを各クエリに含める必要はありません。 もちろんこれは、ユーザー定義関数の基本的な使用例に過ぎません。次は少しひねりを加えてみましょう。今回の残高計算ユーザー定義関数では、 この残高計算ユーザー定義関数で、一連の リスト3:ステータスに基づいて日付を使用するユーザー定義関数
CREATE FUNCTION dbo.GetDate (@nStatus integer, @dDrDate DateTime @dWIPDate DateTime, @dCompDate DateTime) RETURNS DateTime AS BEGIN -- This returns one of three dates, based on a status DECLARE @dReturnDate DateTime SET @dReturnDate = (SELECT CASE WHEN @nStatus = 1 THEN @dDrDate WHEN @nStatus = 2 THEN @dWIPDate WHEN @nStatus = 3 THEN @dCompDate ELSE @dDrDate END) RETURN (ISNULL(@dReturnDate,CAST(’01-01-1901’ AS DateTime))) END これなら、次のように記述する代わりに、
AND CompDate <= @dCutOffDate
次のように記述することができます。
AND dbo.GetDate(Status,DrDate,WIPDate,CompDate)
<= @dCutOffDate
もちろん、この場合には、元々のクエリがユーザー定義関数を呼び出し、そこからさらに別のユーザー定義関数を呼び出すことになります。コードを切り分ける方がよいかどうかは、パフォーマンスに照らしたうえで考察する必要があります。このロジックを使用して日付を判断するクエリや関数が他にない場合には、この2番目のユーザー定義関数は必要ないかもしれません。この判断は、アプリケーションのすべての技術要件をはっきりさせたうえで行う方がよいでしょう。 SQL Server 2000のユーザー定義関数にはいくつかの制約があるという点は頭に入れておいてください。既存のテーブルに含まれているデータをユーザー定義関数の中で変更することや、ユーザー定義関数の中で一時テーブルを作成することはできません。ユーザー定義関数の一般的な目的は、コードを利用して値を返すことであり、データを変更することではないのです。 ヒント3:ユーザー定義関数でテーブルを返す要件:ヒント1で、単一のアカウントに対応する複数の結果セットを返すストアドプロシージャを実装しました。今度は、この要件を拡張する必要が生じたとしましょう。つまり、ユーザーが選択するアカウントが1個でも、10個でも、あるいは50個でも、それらのアカウントすべてに対応する注文情報を表示できるようにしたいとします。 開発者によっては、
リスト4:キーセットをテーブル変数に変換するユーザー定義関数
CREATE FUNCTION dbo.CsvToTable ( @cList varchar(8000)) -- Converts a comma-delimited list of integer keys to a TABLE -- Used to take a variable list of selections -- (accounts, products, etc.) and put them into a TABLE that can -- be used in a subsequent JOIN RETURNS @IntKeyTable TABLE (IntKey INT) AS BEGIN DECLARE @nPosition INT DECLARE @cTempValue VARCHAR(8000) DECLARE @nIntKey int SET @cList = RTRIM(@cList) + ’,’ -- So right now we might have ’1111,2222,’ -- (Careful if the CSV already ended with a comma, -- you’ll wind up with an extra 0 in the key table) -- see if comma exists in list -- (use PATINDEX to return pattern position within a string WHILE PATINDEX(’%,%’ , @cList) <> 0 BEGIN -- get the position of the comma SELECT @nPosition = PATINDEX(’%,%’ , @cList) -- get the key, from beginning of string to the comma SELECT @cTempValue = LEFT(@cList, @nPosition - 1) SET @nIntKey = CAST(@cTempValue AS INT) -- Write out to the Keys table (convert to integer) INSERT INTO @IntKeyTable VALUES (@nIntKey) -- wipe out the value we just inserted SELECT @cList = STUFF(@cList, 1, @nPosition, ’’) END RETURN END 開発者は、可変個のアカウントや製品、あるいはその他の可変個のキーに対してクエリを実行するときに、このユーザー定義関数をいつでも利用できます。リスト5は、ヒント1のストアドプロシージャを修正し、ユーザー定義関数を使用するようにしたバージョンです。 リスト5:テーブル値を返すユーザー定義関数を使用して複数のアカウントをクエリする
CREATE PROCEDURE GetOrdersByMultipleAccts @cAcctList varchar(8000) AS -- Retrieve Orders for a single customer DECLARE @tOrderList TABLE (OrderKey integer) INSERT INTO @tOrderList SELECT OrderKey FROM OrderHdr OH JOIN dbo.CsvToTable(@cAcctList) IK ON IK.IntKey = OH.AcctKey SELECT OH.*, AM.AcctName FROM OrderHdr OH JOIN AcctMast AM ON AM.AcctKey = OH.AcctKey JOIN @tOrderList TMP ON TMP.OrderKey = OH.OrderKey SELECT OD.*, PM.ProdName FROM OrderDtl OD JOIN ProdMast PM ON PM.ProdKey = OD.ProdKey JOIN @tOrderList TMP ON TMP.OrderKey = OD.OrderKey SELECT * FROM OrderNotes ON JOIN @tOrderList TMP ON TMP.OrderKey = ON.OrderKey GO なお、リスト4のユーザー定義関数が持つ ヒント4:LIKEを使用した検索の実行要件:求人情報アプリケーションのユーザーから、キーワード検索に合致した情報すべてを一覧表示する機能を追加してほしいという要望がありました。 T-SQLの -- Search anywhere in the column SELECT * FROM Applicants WHERE Skills LIKE ’%XP%’ -- Search where skills begins with XP SELECT * FROM Applicants WHERE Skills LIKE ’XP%’ -- Search where skills ends with XP SELECT * FROM Applicants WHERE Skills LIKE ’%XP’ また、単一のワイルドカード文字を表すアンダーバー( ヒント5:CASEの利用要件:顧客の口座収支を、標準の日付区分(1〜30日、31〜60日等)に基づいて取りまとめた口座年齢表を生成する必要があります。また、口座ごとや収支の種類ごとの集計も必要です。 ヒント2で、 開発ソリューションによっては、詳細な結果セットや一部のみが集計された結果セットを取得し、それ以上の集計はビジネスコンポーネントで行うという方法をとる場合があります。その方法でも間違いではないですが、実はSQL Server 2000だけでも最終的に必要な結果セットを構築できます。たとえば、ソリューションによっては、SQLから口座データを取得し、その結果を処理して、適切な日付区分に取りまとめるという方法をとっている場合があります。リスト6は、口座の各収支を、対応する日付区分に分類する例です。 これにより、顧客の口座収支を日付区分で分類するという最初の要件は達成されます。また、 リスト6:CASEを何回も使用して結果セットを作成する
CREATE PROCEDURE GetAgingResults (@cAcctList varchar(8000), @dAgeDate DateTime, @nGroupOption int) AS SELECT CASE WHEN @nGroupOption = 1 THEN CT.Description WHEN @nGroupOption = 2 THEN AM.Description END AS GroupDesc, -- Set up CASE statements for the aging brackets SUM(CASE WHEN CDate BETWEEN @dAgeDate-30 AND @dAgeDate THEN CBalance ELSE 0 END) AS Age30 , SUM(CASE WHEN CDate BETWEEN @dAgeDate-60 AND @dAgeDate-31 THEN CBalance ELSE 0 END) AS Age60 , SUM(CASE WHEN CDate BETWEEN @dAgeDate-90 AND @dAgeDate-61 THEN CBalance ELSE 0 END) AS Age90 , SUM(CASE WHEN CDate BETWEEN @dAgeDate-120 AND @dAgeDate-91 THEN CBalance ELSE 0 END) AS Age120 , SUM(CASE WHEN CDate < @dAgeDate - 120 THEN CBalance ELSE 0 END) AS AgeGT120, SUM(CASE WHEN CDate > @dAgeDate THEN CBalance ELSE 0 END) AS NotAged, SUM(CBalance) AS TotBalance FROM OpenBalances OB JOIN dbo.CsvToTable(@cAcctList) IK ON IK.IntKey = OB.AcctKey JOIN Category CT ON CT.CatCode = OB.CatCode JOIN AcctMast AM ON AM.AcctKey = OB.AcctKey GROUP BY CASE WHEN @nGroupOption = 1 THEN CT.Description WHEN @nGroupOption = 2 THEN AM.Description END ORDER BY TotBalance DESC ヒント6:サブクエリと派生テーブル要件:ある建築会社のデータベースには、建築ジョブのテーブル、ジョブあたりの作業時間のテーブル、ジョブで購入した資材のテーブルがそれぞれ1つずつあります。これらから、各ジョブ、そのジョブの総作業時間、そのジョブで購入した資材の合計コストを取りまとめた簡単なリストを生成する必要があります。なお、ジョブによっては、作業時間はあるが資材コストはない場合や、資材コストはあるが作業時間はない場合があります。 これは一見、かなり簡単な要件のように思われるかもしれません。しかし実際には、なかなか難しい問題であり、クエリの手法についても意見が分かれる可能性があります。 開発者によっては、この問題に対処するときに、次のように SELECT J.JobID, SUM(H.Hours) AS TotHours, SUM(C.Costs) AS TotCosts FROM JobMast J LEFT JOIN JobHours H ON H.JobID = J.JobID LEFT JOIN JobCosts C ON C.JobID = J.JobID GROUP BY J.JobID あいにく、このクエリでは、正しい結果は生成されません。このデータベースでは、単一のジョブに対応する作業時間の行が2つあったり、資材コストの行が3つあったりする可能性があります。そのため、上記の集計方法では、作業時間やコストが二重三重にカウントされて、非常に高い値になってしまう可能性があるのです。 幸い、SQL-92の標準では、スカラー値を返すサブクエリを SELECT J.JobID, (SELECT SUM(H.Hours) FROM JobHours H WHERE H.JobID = H.JobID) AS TotHours, (SELECT SUM(C.Costs) FROM JobCosts C WHERE C.JobID = H.JobID) AS TotCosts FROM JobMast J GROUP BY J.JobID 上記のコードでは、相関サブクエリを使用しています。相関サブクエリは、外部クエリの結果に依存します。したがって、単独で実行することはできません。 この方法では、外部クエリの各レコードに対してサブクエリが実行されます。人によっては、これでは効率が悪いと考え、代わりに派生テーブルを使うことがあります。 SELECT J.JobID, H.TotHours, C.TotCost FROM JobMast J LEFT JOIN (SELECT JobID, SUM(Hours) AS TotHours FROM JobHours GROUP BY JobID) H ON H.JobID = J.JobID LEFT JOIN (SELECT JobID, SUM(Costs) AS TotCosts FROM JobCosts GROUP BY JobID) C ON C.JobID = J.JobID 上記のクエリでは、かっこで囲まれた2つのサブクエリで派生テーブルが生成されます。派生テーブルとは要するに、そのクエリの間だけ存在する、一時的な臨時のビューです。派生テーブルの内容は、他のテーブルと同様に、外部クエリで参照できます(なお、派生テーブルを参照するときには、必ず別名を使用する必要があります)。また、派生テーブルは、FROM句の中のサブクエリでも作成できます。派生テーブルは主に、当該のクエリのみで一時的な結果セットが必要な場合に使用します。 「エコノミストを10人集めれば、経済について10通りの見解が得られる」という古い戯言があります。これはいくぶん誇張した言い方ですが、相関サブクエリと派生テーブルについても、どちらの方法が好ましいかを10人の開発者に聞いてみたら、意見はきっと分かれるはずです。つまり、読みやすいという理由で相関サブクエリを選ぶ人もいれば、パフォーマンス面で優位かもしれないという理由で派生テーブルを選ぶ人もおり、さらにはもっと別の方法を選ぶ人も必ずいるはずです。 技能や経験が同程度の開発者であっても、パフォーマンス(の有望性)に重きを置くか、それともメンテナンスや読みやすさに重きを置くかで、意見はきっと分かれます。時には、正解は1つでないこともあります。鍵となるのは、この種の状況に対処するときに一貫性を保つということです。 話を先へ進める前に、要件をもう1つ追加してみましょう。私が作成した結果セットには、各ジョブの作業時間の集計が含まれていました。ここで、時間を集計するのではなく、作業時間に従業員の時給を掛けたものを合計して、労働賃金の総額を求めるには、どうすればいいでしょうか。 さらに複雑なことに、1つのジョブの中で時給が変わることもあるものとします(たとえば、あるジョブの半分の作業までの時給より、残りの半分の時給の方が高い場合があります)。リスト7は、上記のクエリを拡張して、作業日に対応する作業時間とその日の有効な時給とをマッチさせる方法を示したものです。追加したロジックでは、当該の作業日以前の有効な日付の最大値(MAX)における時給を検索しています。追加したこのサブクエリは、前述のクエリのどちらでも使用できます。 リスト7:作業日と適切な時給を対応付けるためのサブクエリ
-- JobMast (JobID) -- JobHours (JobID, EmpKey, Hours, DateWorked) -- JobCosts (JobID, Costs) -- EmpRate (EmpKey, EffDate, WorkRate) -- Use subquery to create derived tables, to sum/group hours -- and costs -- Inside the subquery for hours, perform another subquery -- to match up an employee’s work date with the rate for that day -- Uses EmpRate twice (ER1 and ER2, once to apply the correct rate -- and a second time in a subquery to get the rate based on date SELECT J.JobID, H.TotLabor, H.TotHours, C.TotCost FROM JobMast J LEFT JOIN (SELECT JobID, SUM(JH.Hours*ER1.WorkRate) AS TotLabor, SUM(JH.Hours) AS TotHours FROM JobHours JH JOIN EmpRate ER1 ON ER1.EmpKey = JH.EmpKey WHERE ER1.EffDate = (SELECT MAX(EffDate) FROM EmpRate ER2 WHERE EffDate <= DateWorked AND ER2.EmpKey = JH.EmpKey) AND DateWorked >= EffDate GROUP BY JobID) H ON H.JobID = J.JobID LEFT JOIN (SELECT JobID, SUM(Costs) AS TotCosts FROM JobCosts GROUP BY JobID) C ON C.JobID = J.JobID ヒント7:テーブル変数と一時テーブルヒント1および3のリストでは、一時的な結果セットを保持してストアドプロシージャ内で繰り返し利用するために、テーブル変数を導入しました。これらの例では、テーブル値を返すユーザー定義関数(アカウントキーのリストに対して処理を行うもの)の結果を、注文ヘッダーテーブルに対して直接結合しました。アカウントキーが格納されているテーブルを別の結合で使用したい場合なら、次のような方法が可能です。 DECLARE @tAcctKeys TABLE (IntKey int) INSERT INTO @tAcctKeys SELECT IntKey = IntKey FROM CsvToTable(@cAcctKeyList) -- now you can use @tAcctKeys in any subsequent -- joins in the stored procedure Microsoftのドキュメントによると、テーブル変数を使用すると、一時テーブルと同様のメリットがある程度得られるのに加え、パフォーマンスも若干向上します。テーブル変数の一般的なスコープは、他の変数と同じです。つまり、プロシージャまたは関数の最後で自動的にクリアされます。SQL Serverでは、ストアドプロシージャの再コンパイルの回数は、テーブル変数を使用している場合の方が、一時テーブルを使用している場合より少なくなります。加えて、テーブル変数が絡んでいるトランザクションの存続期間は、テーブル変数の更新の間だけです。したがって、テーブル変数の方が、リソースのロックやログ記録の必要性が少なくなります。テーブル変数のスコープは限定されており、永続的なデータベースの一部ではないので、トランザクションのロールバックによる影響を受けません。 確かに、テーブル変数のパフォーマンスは多少優れているかもしれませんが、その内容がシステムデータベースではなくメモリに格納されるおかげで一時テーブルよりパフォーマンスが優れているというのは、若干の誤解があります。Microsoftによると、テーブル変数はメモリのみの構造ではありません。テーブル変数には、メモリに収まりきらないデータを保持することも不可能ではありません。したがって、データを格納するための場所がディスク上に必要です。テーブル変数は、一時テーブルと同様に「tempdb」データベースに作成されます。メモリでまかなえる場合は、テーブル変数と一時テーブルのいずれも、メモリ(データキャッシュ)内で作成および処理されます。 この事実をふまえても、前述のような理由から、開発者は一時テーブルよりテーブル変数の方を好むのが一般的です。しかし、テーブル変数には、見過ごせない制約がいくつかあります。テーブル変数の -- neither line will work with table variables INSERT @tTable EXEC <sp_mystoredproc> SELECT * INTO @tTable FROM <mytable> ヒント8:日付関数要件:日別の注文情報を取得して、週ごとに取りまとめ、週報や週単位のグラフを作成する必要があります。 次に示す2つの短いコードは、SQL Serverの CREATE FUNCTION dbo.GetEndOfWeek (@dDate DateTime) -- Converts date to the Saturday date for the week RETURNS DateTime AS BEGIN DECLARE @dRetDate DateTime SET @dRetDate = @dDate + ( 7-DATEPART(weekday,@dDate)) RETURN @dRetDate END SELECT dbo.GetEndOfWeek(OrderDate) AS WeekEnding, SUM(Amount) AS WeekAmount FROM OrderHdr GROUP BY dbo.GetEndOfWeek(OrderDate) -- Change first day of week from default of -- Sunday (7) to Monday (1) SET DATEFIRST 1 日付について1つ申し沿えておきます。日付を比較するクエリを作成するときに、日付列の値には午前0時以外の時刻が設定されているのに対し、比較対象の日付変数の日付には時刻が定義されていない、というケースがよくあります。 たとえば、 ヒント9:CASTとCONVERTによるデータ変換要件:経理処理の結果を伝えるメッセージを、通常の英文に近い形で生成する必要があります。たとえば、"100 Employee Checks were generated on 11/15/2004 at 5:07:02 PM, for a total dollar value of $134,123.11"といったメッセージです。 SQL Serverに備わっている リスト8:CAST関数とCONVERT関数を使用して異なるデータ型を併用する
DECLARE @nRowCount integer DECLARE @dReportDate DATETIME DECLARE @nDollars decimal(13,2) SET @nRowCount = 156 SET @dReportDate = GETDATE() SET @nDollars = 156134.11 DECLARE @cDateString char(20) DECLARE @cRowCount char(20) DECLARE @cMessage varchar(8000) SET @cRowCount = LTRIM(RTRIM(CONVERT(int,@nRowCount,20))) SET @cDateString = LTRIM(RTRIM(CONVERT(datetime,@dReportDate,20))) SET @cMessage = ’Generated ’ + @cRowCount + ’ Employee Checks on ’ + @cDateString + ’ for a total amount of $’ + CAST(@nDollars AS Varchar(20)) SELECT @cMessage ヒント10:更新トリガ 要件:テーブルに対して CREATE TRIGGER Upd_Client ON dbo.Client FOR UPDATE AS UPDATE Client SET LastUpdate = GETDATE() FROM Client C JOIN Inserted I ON I.PrimaryKey = C.PrimaryKey ヒント11:UPDATEトリガを使用して監査証跡の更新を実装する要件:特定の列に対する変更を、監査証跡ログに記録する必要があります。ログに含める必要があるのは、変更されたテーブル、変更された行の主キー、変更された列の名前、変更前および変更後の値(古い値/新しい値)、更新の日付/時刻です。 特定のデータベース項目に対する変更を追跡したいと考えている顧客は多くいます。 リスト9では、製品マスターテーブルに対して リスト9:基本的な監査証跡機能を実装する
CREATE TRIGGER Upd_Price ON dbo.Price FOR UPDATE AS DECLARE @dLastUpdate DATETIME SET @dLastUpdate = GETDATE() -- Set the last Update for rows updated UPDATE Price SET LastUpdate = @dLastUpdate FROM Price P JOIN Inserted I ON I.PrimaryKey = P.PrimaryKey -- Write out to the Audit Log, for rows where Price changed INSERT INTO AuditLog (TableName, PrimaryKey, LastUpdate, ColName, OldValue, NewValue, UserKey) SELECT ’PRICE’ AS TableName, I.PrimaryKey, @dLastUpdate AS LastUpdate, ’Price’ AS ColName, CONVERT(CHAR(20),D.Price) AS OldValue, CONVERT(CHAR(20),I.Price) AS NewValue, I.UserKey FROM Inserted I JOIN Deleted D ON D.PrimaryKey = I.PrimaryKey WHERE I.Price <> D.Price -- Now check for any changes to the description INSERT INTO AuditLog (TableName, PrimaryKey, LastUpdate, ColName, OldValue, NewValue, UserKey) SELECT ’PRICE’ AS TableName, I.PrimaryKey, @dLastUpdate AS LastUpdate, ’Descr’ AS ColName, D.Descr AS OldValue, I.Descr AS NewValue, I.UserKey FROM Inserted I JOIN Deleted D ON D.PrimaryKey = I.PrimaryKey WHERE I.Descr <> D.Descr メモ:このヒントでは、監査証跡ログの基本機能を示しています。完全に自動化された監査証跡ソリューションを構築するには、ログへの記録が必要なテーブル/列に対して 自動化された監査証跡ソリューションの構築に役立つ製品は、いくつかの会社から出ています。私の個人的なおすすめは、Red Matrix TechnologiesのSQLAuditという製品です。上記のような機能やその他の機能が備わっています。包括的な監査証跡ソリューションの開発に要する苦労を考えると、SQLAuditなどのサードパーティ製ツールを購入するのは、経済面から見て賢い選択かもしれません。 ヒント12:動的SQLデータドリブンアプリケーションにおいて、重要なクエリ構文を実行時まで確定できない場合に、動的SQLステートメントを使用して値を渡せることがあります。ただしこれは賛否両論がある方法です。リスト10は、動的SQLの使用例を2つ示したものです。1つ目の例は、テーブル名が変数となっている簡単なクエリです。2つ目の例は、1つの行を返すクエリの結果を出力変数に設定しています。 リスト10:動的SQLの基本的な使用例
-- Must use Unicode data DECLARE @cSQLSyntax nvarchar(2000) DECLARE @cTableName varchar(20) SET @cTableName = ’EMPLOYEE’ SET @cSQLSyntax = N’SELECT * FROM ’ + @cTableName + ’ WHERE EmployeeID = 1’ EXEC sp_executesql @cSQLSyntax -- Return a column into an output variable -- Query must only contain one row, else an error will occur -- (query could also be a SUM that returns a scalar value) DECLARE @cValue nVarChar(30) DECLARE @cColumnName varchar(20) SET @cColumnName = ’firstname’ SET @cSQLSyntax = N’ SELECT @cValue = ’ + @cColumnName + ’ FROM Employee where employeeid = 1’ EXECUTE SP_EXECUTESQL @cSQLSyntax, N’@cValue nVarChar(30) OUTPUT’, @cValue OUTPUT SELECT @cValue メモ:リスト10の動的SQLではUnicodeデータ( Googleで「dynamic SQL」(動的SQL)と検索してみると、動的SQLについての優れた説明がいくつも見つかります。ネット上には、動的SQLのさまざまな手法を説明した文章がたくさんあり、動的SQLとの関連でSQLインジェクションについて説明したものまであります。 ヒント13:データベースのテーブルと列の一覧を取得する初心者がよく抱く疑問の1つに、データベースのテーブルと列の一覧を取得するにはどうすればいいかというものがあります。次のように、情報スキーマをクエリすれば、一覧を簡単に取得できます。 -- Return a list of table names SELECT DISTINCT Table_Name FROM Northwind.INFORMATION_SCHEMA.COLUMNS ORDER BY Table_Name -- Return a list of columns -- (perform a SELECT * to see a full list) SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH FROM Northwind.INFORMATION_SCHEMA.COLUMNS ORDER BY Table_Name -- You could combine the capability of LIKE -- to find columns with a particular search -- pattern まとめ私のWebサイトでは、SQL Server 2000とTransact-SQLについての良書をいくつか紹介しています。また、SQL Server、Transact-SQL、SQL-92などについてのさまざまなオンラインリソースも紹介しています。 また、これは現在も進行中のプロジェクトですので、折に触れて加筆を行う場合があります。 著者紹介Kevin S. Goff(Kevin S. Goff)
.NET、Visual FoxPro、SQL Server、Crystal Reportsによる独自のWebソリューション/デスクトップソリューションを提供するコンサルティンググループ「Common Ground Solutions」の創業者兼主任コンサルタント。ソフトウェアアプリケーションの開発経験は17年に及ぶ。米農務省からシステムオートメーション関連の賞をいくつか受賞。また、6桁の投資見返りを実現するソリューションを開発したことによりFortune 500企業から特別表彰を受ける。保険、会計、環境衛生、不動産、出版、広告、製造、金融、日用品、貿易振興など多様な業界に携わった経験を持ち、さまざまな形態で独自のトレーニングも行っている。
関連テーマ
新着ニュース・コラム ホワイトペーパー
|
注目のトピックス 話題の記事
企業の約4割がいまでも IE 6 以前のブラウザを利用 ― Web 広告研究会調査
SNS「非モテ+」、バレンタイン関連ワード投稿を禁止に
Android アプリを美しくみせる UI デザイン10のヒント
新聞広告が動く!--11日の読売新聞朝刊に、AR を応用したドコモなどの広告
Google Chrome のシェア、2012年6月に IE を上回る?
⇒一覧を見る
アクセスランキング
最新コラム一覧
|
||||||||||||||||||||