.NET データ処理に役立つ26のヒント(後編)はじめに使用する言語がC#にせよVB.NETにせよ、また、アプリケーションのフロントエンドとバックエンドのどちらを主に担当するにせよ、データ処理の技能は、プロジェクトの中で自分がどれだけ力となれるかを直接左右する要因です。 .NETの新しいジェネリッククラスでは、シンプルなコードで多くの処理を実行できるため、効率が飛躍的に向上します。また、ASP.NET 2.0のObjectDataSourceクラスでは、中間層のデータ処理クラスとWebページ上のデータバインドコントロールを簡単に連係できます。さらに、前編でも取り上げたように、T-SQL 2005には、データベース開発者の生産性向上に寄与する新しい言語機能が備わっています(今回の記事にも登場します)。 データ処理をテーマとした全2回の後編となる今回は、.NETのジェネリック、ASP.NET 2.0のObjectDataSource、前回取り上げた以外のT-SQL 2005の新機能について解説します。 過去の記事目的:データ処理の包括的なヒント集私は、太陽がさんさんと降り注ぐ自宅のテラスでこの文章を書いています。ちょうど、コミュニティイベントやカンファレンスでの講演回りの長旅を終えたばかりです。その経験で得た大量のエネルギーをこの記事に注入し、これまでになくホットなVisual Studio 2005とT-SQL 2005のデータ処理機能について解説したいと思います。 では、プレイボールといきましょう。「The Baker’s Dozen」のダブルヘッダー第2戦、今回の打順を紹介します(もちろん、野球のメンバーは9人ですが、この記事のヒントは13個です)。
ヒント1:.NETのジェネリックの概要多くの場合、.NETのジェネリックは、Visual Studio .NET 2003で開発者が目指した処理に照らしてみるとわかりやすくなります。 Visual Studio 2003では、オブジェクトをリストに格納するときに、ArrayListコレクションクラスを使用することがよくありました。次のコードのような形です。この例では、整数のリストを格納しているだけですが、フォームオブジェクトやデータテーブルなど、その他のオブジェクトのコレクションの場合も、同じように簡単に格納できます。 // Visual Studio 2003 code ArrayList list = new ArrayList(); // implicit boxing, must convert value // type to reference type list.Add(3); list.Add(4); list.Add(5.0); int nFirst = (int)list[0]; int total = 0; foreach (int val in list) total = total + val; このコードには、次のような3つの問題が潜んでいます。
Visual Studio 2005では、System.Collection.Generic名前空間にある、新しいListクラスを使用できます。Listクラスでは、ArrayListクラスが持つ問題に対処できます。クラスのインスタンスに格納する型を具体的に指定できるからです。 // Visual Studio 2005 code // define the stored data type in the placeholder List<int> aList = new List<int>(); aList.Add(3); aList.Add(4); // The following line generates a compile error aList.Add(5.0); // no need for unboxing, value type stored in // List<int> as an int, not an object int nFirst = aList[0]; int total = 0; foreach (int val in aList) total = total + val; Listクラスのインスタンスを作成するときに、格納する型を指定しているので、.NETがボックス化やボックス化解除を実行する必要がなくなります。整数を格納するということを判断できるからです。また、List内の特定のアイテムをキャストする必要もありません。 インスタンス化の回数がよほど多くない限り、パフォーマンスに差は出ないでしょうが、格納の効率やコンパイル時のタイプセーフ性が高まるという点だけでも、ArrayListクラスとListクラスの違いとしては十分すぎるほどです。 とはいえ、ArrayListクラスの方がパフォーマンスが若干低いという点も、きちんと押さえておくことにしましょう。次のコードでは、ArrayListを作成し、100万個の整数を格納したうえで、リスト内の各項目に対して反復処理を実行します。このコードの実行には、約0.2秒を要します。 // Visual Studio 2003 code - takes .2 seconds ArrayList aBigList = new ArrayList(); DateTime dt1, dt2; dt1 = DateTime.Now; for (int nCtr = 0; nCtr < 1000000; nCtr++) aBigList.Add(nCtr); int nSum = 0; foreach (int nn in aBigList) nSum += nn; dt2 = DateTime.Now; TimeSpan ts = dt2.Subtract(dt1); MessageBox.Show(ts.TotalSeconds.ToString()); 一方、新しいListクラスを使う形でこのコードを書き直すと、実行に要する時間は0.04秒になります。つまり実行速度は5倍になります。 // Visual Studio 2005 code - takes .04 seconds List<INT> aBigList = new List<INT>(); DateTime dt1, dt2; dt1 = DateTime.Now; for (int nCtr = 0; nCtr < 1000000; nCtr++) aBigList.Add(nCtr); int nSum = 0; foreach (int nn in aBigList) nSum += nn; dt2 = DateTime.Now; TimeSpan ts = dt2.Subtract(dt1); MessageBox.Show(ts.TotalSeconds.ToString()); 次に、ジェネリックメソッドを見てみましょう。これは非常に柔軟なコードを実現できる機能です。昨年のMSDN CodeCampのイベントでCarl Franklin(.NET Rocks!で有名)が話していたことですが、ジェネリックの優れた利用法の1つに、型のみが異なる複数のクラスでの使用があります。 ここでは、2つの値を比較して、大きい方の値を返すというコードで考えてみることにしましょう。このコードは、文字列、日付、整数など、さまざまな値に対して使えるものとします。この場合、複数のメソッドを作成する方法、1つのメソッドをさまざまにオーバーロードさせる方法、何らかの裏技を使う方法などが考えられます。しかし、.NETのジェネリックを利用すれば、メソッドを1つ作成するだけで複数のデータ型に対応できるのです。これこそ、.NETのジェネリックの本領発揮です。 次のコードでは、 public T CalcMax<T> ( T compVal1, T compVal2) where T : IComparable { T returnValue = compVal2; if (compVal2.CompareTo(compVal1) < 0) returnValue = compVal1; return returnValue; } この double dMax = CalcMax<DOUBLE>(111.11,333.23); int intMax = CalcMax<INT>(2, 3); string cMax = CalcMax<STRING>("Kevin", "Steven"); DateTime dtMax = CalcMax<DATETIME> (DateTime.Today, DateTime.Today.AddDays(1)); ヒント2:ジェネリックによるカスタムクラスの格納 Listクラスはカスタムクラスの格納にも使用できます。次のコードは using System; using System.Collections.Generic; using System.Text; namespace GenericsDemo{ public class BaseballClass { private string name; public string Name { get { return name; }} private string team; public string Team { get { return team; } } public BaseballClass(string name, string team) { this.name = name; this.team = team; } public override string ToString() { return team + ", " + name; } } } 次のコードに示す関数(
List<BaseballClass> PopulateBaseballClass()
{
List<BaseballClass> oBaseball = new List<BaseballClass>();
oBaseball.Add(new BaseballClass("Kenny Rogers", "Tigers"));
oBaseball.Add(new BaseballClass("Michael Young", "Rangers"));
oBaseball.Add(new BaseballClass("Ken Griffey, Jr.", "Reds"));
oBaseball.Add(new BaseballClass("Tom Glavine", "Mets"));
oBaseball.Add(new BaseballClass("David Ortiz", "Red Sox"));
oBaseball.Add(new BaseballClass("Derek Jeter", "Yankees"));
oBaseball.Add(new BaseballClass("Roger Clemens", "Astros"));
oBaseball.Add(new BaseballClass("Roy Oswalt", "Astros"));
return oBaseball;
}
そして次のコードでは、 private void TestBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball = this.PopulateBaseballClass(); string cResults = ""; foreach (BaseballClass oRecord in oBaseball) cResults += oRecord + " "; MessageBox.Show(cResults); } ヒント3:.NETのジェネリックによる並べ替えListジェネリッククラスには、並べ替え処理と検索処理を行うためのメソッドがあります。また、独自の並べ替えや検索のロジックを実装することもできます。それぞれ見てみることにしましょう。 次のコードは、選手名による並べ替えと、チームごとの選手名による並べ替えの例です。いずれも、「匿名メソッド」というC#の新しい言語機能を使用して、並べ替えのロジックをインラインで指定しています。 private void TestSortBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball = this.PopulateBaseballClass(); string cResults = ""; // Anonymous delegate to sort on name oBaseball.Sort (delegate(BaseballClass f1, BaseballClass f2) { return f1.Name.CompareTo(f2.Name); } // Anonymous delegate to sort on Team, then Name oBaseball.Sort (delegate(BaseballClass f1, BaseballClass f2) { return f1.Name.Insert(0, f1.Team).CompareTo( f2.Name.Insert(0, f2.Team)); } } 匿名メソッドを使用すると、コードブロックをデリゲートにカプセル化でき、通常ならメソッド名を指定する部分に、インラインでコードを記述できます。メソッドを別途作成する必要がないため、デリゲートをインスタンス化するときのコーディングの手間を減らすことができます。デリゲートの代わりにコードブロックを指定する方法は、メソッドを作成するのが無駄な手間に感じられる場合に有効です。 匿名メソッドの構文は、最初は扱いづらいように思えるかもしれません。しかし次の例では、匿名メソッドを使うことによって、クラスを余分に追加する手間が省けます。 // In-line anonymous method that compares 2 // incoming values oBaseball.Sort(delegate(BaseballClass f1, BaseballClass f2) { return f1.Name.CompareTo(f2.Name); }); // A second anonymous method, inserts team // in front of name, to sort on two columns oBaseball.Sort(delegate(BaseballClass f1, BaseballClass f2) { return f1.Name.Insert(0, f1.Team).CompareTo( f2.Name.Insert(0, f2.Team)); }); ヒント4:.NETのジェネリックでの検索と匿名メソッド(オプション) .NETのListジェネリッククラスには、検索を実行するためのメソッドとして、 private void TestFindBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball = this.PopulateBaseballClass(); string cSearch = "Astros"; BaseballClass oSingleFind = oBaseball.Find( delegate(BaseballClass f1) { return f1.Team == cSearch; }); MessageBox.Show("Single find is " + oSingleFind); string cResults = ""; foreach (BaseballClass oRecord in oBaseball.FindAll( (delegate(BaseballClass f1) { return f1.Team == cSearch; }))) cResults += oRecord + " "; MessageBox.Show("Multiple find is " + oSingleFind); } 上のコードでは、その前の例と同じ手法を使っています。つまり、 Listクラスの string cSearch = "Astros"; // Perform a single search on the first hit BaseballClass oSingleFind = oBaseball.Find( delegate(BaseballClass f1) { return f1.Team == cSearch; }); // Perform a find all (almost like a filter) string cResults = ""; foreach (BaseballClass oRecord in oBaseball.FindAll((delegate(BaseballClass f1) { return f1.Team == cSearch; }))) cResults += oRecord + " "; 独立したクラスとして作成したい場合(あるいはVisual Studio 2005で匿名メソッドをサポートしていないVB.NETを使用している場合)ために、独立したFindクラスの例とその使用例をリスト1とリスト2に示しておきます。 リスト1 ブール型メソッドを含む検索クラス
using System; using System.Collections.Generic; using System.Text; namespace GenericsDemo { class FindBaseballClass { private string team; public FindBaseballClass(string team) { this.team = team; } // Implement specific search logic - return true or false public bool TeamFind(BaseballClass oBaseball) { return oBaseball.Team == team; } } } リスト2 検索クラスの使用例(匿名メソッドなし)
private void TestFindBaseballClass() { List<BaseballClass> oBaseball = new List<BaseballClass>(); oBaseball = this.PopulateBaseballClass(); FindBaseballClass oFind = new FindBaseballClass("Astros"); BaseballClass oSingleFind = oBaseball.Find( new Predicate<BaseballClass>(oFind.TeamFind)); MessageBox.Show("Single find is " + oSingleFind); string cResults = ""; foreach (BaseballClass oRecord in oBaseball.FindAll( new Predicate<BaseballClass>(oFind.TeamFind))) cResults += oRecord + " "; MessageBox.Show("Multiple find is " + oSingleFind); } ヒント5:Listクラスへの他オブジェクトの格納 前述のとおり、新しいListクラスには、.NETのどんな型でも格納できます。リスト3は、特定のインターフェイスを実装するクラスを格納する例です。たとえば、複数のレポートクラスがあり、それらはすべて
List<SimpleInterface.IDocument> oList =
new List<SimpleInterface.IDocument>();
foreach(SimpleInterface.IDocument oClass in oList) oClass.GenerateDocument(); この例については、GE HealthcareのRob Haleに感謝します。Robは、Boston CodeCampで私が開催したC# 2.0セッションの参加者で、このコードのアイデアの発案者です。本当にありがとう、Rob! リスト3 インターフェイスを実装するオブジェクトのコレクション
public class SimpleInterface { public interface IDocument { void GenerateDocument (); } } class SimpleClass1 : SimpleInterface.IDocument { public SimpleClass1() { } public void GenerateDocument() { // Method code goes here! } } class SimpleClass2 : SimpleInterface.IDocument { public SimpleClass2() { } public void GenerateDocument() { // Method code goes here! } } List<SimpleInterface.IDocument> oList = new List<SimpleInterface.IDocument>(); SimpleClass1 oClass1 = new SimpleClass1(); SimpleClass2 oClass2 = new SimpleClass2(); oList.Add(oClass1); oList.Add(oClass2); foreach (SimpleInterface.IDocument oClass in oList) oClass.GenerateDocument(); ヒント6:Generics名前空間のその他のクラス ここまでは、Visual Studio 2005の新しいListクラスについて説明してきました。ここでは、System.Collections.Generics名前空間で目を引くその他の3つのクラス、 Visual Studio 2005には、 // Create a new dictionary object with two strings Dictionary<string, string> oDict = new Dictionary<string, string>(); oDict.Add("secure", "true"); oDict.Add("password", "kevin"); TcpChannel oChannel = new TcpChannel(oDict, null, null); ディクショナリのエントリをキーによって並べ替える必要がある場合は、キーによる並べ替え順序が維持される、新しい また、ここまでは、新しいListクラスを使用してきましたが、Visual Studio 2005には、軽量な Collection<int> oCol = new Collection<int>(); oCol.Add(2); oCol.Add(3); oCol.Insert(0,1);
Collection<BaseballClass> oBaseballCollection =
new Collection<BaseballClass>();
oBaseballCollection.Add(new BaseballClass("Tom Glavine",
"Mets"));
oBaseballCollection.Add(new BaseballClass("Derek Jeter",
"Yankees"));
foreach (BaseballClass oBaseball in
oBaseballCollection)
MessageBox.Show(oBaseball.ToString());
また、.NETのジェネリックには、リンクリストデータを扱うための ヒント7:.NETのジェネリックを使用して型指定されたデータセットにレコードを読み込むデータアクセスクラスの作成私はストアドプロシージャと型指定されたデータセットを多用します。私にとって、大いなる目標の1つだったのが、型指定されたデータセットに対して、ストアドプロシージャから直接レコードを読み込むということでした。 長いこと私が用いてきたのは、データアクセスクラスのベースメソッドを使用して、パラメータを持つストアドプロシージャから普通のデータセットにレコードを読み込むという方法でした。そのデータセットを、型指定されたデータセットに後でマージします。その方法でも確かに動作しますが、余分なコードと余分な処理が必要です。私がしたかったのは、型指定されたデータセットのインスタンスをベースメソッドに渡すと、そのベースメソッドがファクトリの役割を果たし、中身がセットされたデータセットが返ってくる、という方法でした。 .NETのジェネリックを利用すると、そのようなクラスを作成し、使用することができます(リスト4とリスト5を参照)。そのようなクラスを作成するには、次の手順に従います。
public T ReadIntoDS<T> (T dsTypedDs, string cStoredProc, List<SqlParameter> oParmList) where T : DataSet リスト4 ジェネリックデータアクセスメソッド
public class SimpleDataAccess { // Pass the following: // 1) An instance of a typed dataset you wish to fill // 2) The name of the stored procedure // 3) A List of SqlParameters // Function will execute the stored procedure, // fill the typed dataset with the results, and then return it public T ReadIntoDs<T> (T oTypedDs, string cSP, List<SqlParameter> oParms) where T:DataSet { SqlConnection oSqlConn = this.GetConnection(); SqlDataAdapter oSqlAdapt = new SqlDataAdapter(cSP, oSqlConn); oSqlAdapt.SelectCommand.CommandType = CommandType.StoredProcedure; foreach (SqlParameter oParm in oParmList) oSqlAdapter.SelectCommand.Parameters.Add(oParm); // A little bit of "elbow grease… // SQL Server returns tablenames as "Table", "Table1", etc // We need to map in the tablenames from the typed DS // So loop through the tables in the Typed DS, get the name, // and map it to the corresponding table that SQL will // use as the default name int nTableCtr = 0; foreach (DataTable Dt in dsTypedDs.Tables) { string cSource = ""; if (nTableCtr == 0) cSource = "Table"; else cSource = "Table" + nTableCtr.ToString().Trim(); oSqlAdapter.TableMappings.Add( cSource, Dt.TableName.ToString()); nTableCtr++; } // End of "elbow grease" - we can fill the dataset // from the adapter, now that we’ve mapped our tablenames oSqlAdapter.Fill(dsTypedDs); return dsTypedDs; } public SqlConnection GetConnection() { // New SqlConnection StringBuilder class SqlConnectionStringBuilder oStringBuilder = new SqlConnectionStringBuilder(); oStringBuilder.UserID = "UserID"; oStringBuilder.Password = "PassWord"; oStringBuilder.InitialCatalog = "InitialCatalog"; oStringBuilder.DataSource = "DataSource"; return new SqlConnection(oStringBuilder.ConnectionString); } } リスト5 リスト4のジェネリックデータアクセスメソッドの使用例
// Create an instance of a typed DataSet dsAgingReport odsAgingReport = new dsAgingReport(); // Create an instance of our Data Access Method SimpleDataAccess oDataAccess = new SimpleDataAccess(); // Use the list class to create a collection of sql parameters List<SqlParameter> oParms = new List<SqlParameter>(); oParms.Add(new SqlParameter("@dAgingDate", DateTime.Today)); oParms.Add(new SqlParameter("@lShowDetails", true)); // Call ReadIntoDs, passing the typed DS, name of the stored proc // and the list of parameters odsAgingReport = oDataAccess.ReadIntoDs(odsAgingReport, "[dbo].[GetAgingReceivables]",oParms); ヒント8:ASP.NET 2.0のObjectDataSourceの概要本記事の前編で、ASP.NET 2.0のGridViewについて取り上げ、データセットにバインドする方法や、並べ替えとページングの処理方法について解説しました。もう1つ使用できるのが、ASP.NET 2.0のObjectDataSourceです。データを処理するビジネスオブジェクト/クラスを、GridViewなどのデータバインドコントロールに対して公開できます。 ここでは、ObjectDataSourceの使用方法について、ステップバイステップ方式で解説します。最初の例では、「保留」になっている注文が格納された、型指定されたデータセットを返すビジネスオブジェクトを作成します。その後で、GridViewを配置したWebページを作成し、そのGridViewの直接のDataSourceとして、前述のビジネスオブジェクトを指定します。
図5 GridViewを作成し、DataSourceIDやその他のプロパティを設定する ![]() 注記
重要な補足を1つ。図7を見るとわかるように、このページには、別のページに移動するためのリンクが表示されています。また、列見出しは、クリックして並べ替えを実行できるようになっています。ObjectDataSourceにバインドすると、並べ替えとページングが自動的に処理されます。コードを追加する必要はありません。
このヒントで紹介したのは、ObjectDataSourceクラスの機能のほんの一部です。ObjectDataSourceでは、次のような処理も実現できます。
それからもう1つ、ObjectDataSourceが返したデータを「捕捉」するにはどうすればいいのか、疑問に思う人もいるかもしれません。たとえば、グリッドのキャプションに行数を表示したい場合などです。そのときは、ObjectDataSourceの protected void ObjectDataSource1_Selected( object sender, ObjectDataSourceStatusEventArgs e) { bzOrders.dsOrders odsOrders = (dsOrders)e.ReturnValue; int nRows = odsOrders.dtOrders.Rows.Count; this.grdOrdersOnHold.Caption = "Number of rows: " + nRows.ToString(); } ヒント9:SQL ServerのCOALESCE関数の使用いくつかの検索条件(姓、名、住所、都市名、郵便番号など)をユーザーが入力すると、それに合致する顧客を検索できるというフォーム(またはWebページ)があるとしましょう。ユーザーが入力するのは、1つのフィールドでも複数のフィールドでもよいものとします。この場合、取り得るパラメータすべてをチェックしたうえで、ユーザーが入力したパラメータのみを使用してクエリするようなストアドプロシージャを作成する必要があります。 この場合、各パラメータをチェックし、ユーザーが入力したパラメータに基づいてSQLのSELECT文字列を組み立て、動的SQLを使用してその文字列を実行する、という形でストアドプロシージャを作成する方法もあります。多くのSQL開発者が選択するのはそちらの方法です。 ここでは別の方法を紹介します。次のコードのように、SQL Serverの -- This will work in both SQL2000 and SQL2005 -- You can use COALESCE to query on only those search values -- that are not NULL DECLARE @city varchar(50), @state varchar(50), @zip varchar(50), @FirstName varchar(50), @LastName varchar(50), @Address varchar(50) SET @FirstName = ’Kevin’ SET @State = ’NY’ SELECT * FROM CUSTOMERS WHERE FirstName = COALESCE(@FirstName,FirstName) AND LastName = COALESCE(@LastName,LastName) AND Address = COALESCE(@Address,Address) AND City = COALESCE(@City,City) AND State = COALESCE(@State,State) AND Zip = COALESCE(@Zip,Zip) ヒント10:SQL 2005でのグループ内のランク付け SQL Server 2005には、 リスト6は、NorthwindのOrdersデータベースに対して、500ドルを超える注文をクエリする方法を示します。ランキングの番号は、顧客別の注文ごとに割り当てます。 ROW_NUMBER() OVER (PARTITION BY CUSTOMERID ORDER BY (UnitPrice * Quantity) DESC) AS OrderRank リスト6 ROW_NUMBERとPARTITIONを使用してレベル内でランクを付ける
SELECT CustomerID, OH.OrderID, OrderDate, (UnitPrice * Quantity) as Orderamount, ROW_NUMBER() OVER (PARTITION BY CUSTOMERID ORDER BY (UnitPrice * Quantity) desc ) AS OrderRank FROM Orders OH JOIN [dbo].[Order Details] OD ON OH.OrderID = OD.OrderID WHERE (UnitPrice * Quantity) > 500 ORDER BY CUSTOMERID, OrderAmount DESC -- Results, OrderRank partitioned (reset) on each Customer ALFKI 10692 1997-10-03 00:00:00.000 878.00 1 ALFKI 10835 1998-01-15 00:00:00.000 825.00 2 ANTON 10535 1997-05-13 00:00:00.000 1050.00 1 ANTON 10677 1997-09-22 00:00:00.000 936.90 2 ANTON 10535 1997-05-13 00:00:00.000 825.00 3 ANTON 10573 1997-06-19 00:00:00.000 820.00 4 ANTON 10573 1997-06-19 00:00:00.000 702.00 5 AROUT 10953 1998-03-16 00:00:00.000 4050.00 1 AROUT 10558 1997-06-04 00:00:00.000 1060.00 2 AROUT 10707 1997-10-16 00:00:00.000 780.00 3 ヒント11:T-SQL 2005のその他の話題(APPLY、ユーザー定義関数、相関サブクエリでのテーブル値ユーザー定義関数)T-SQL 2000では、テーブル値ユーザー定義関数をクエリに組み込む機能について、やや欠けている面がありました。開発者は、ユーザー定義関数の実行結果を使用するときに、まず一時テーブルに取り込んだうえで、その一時テーブルを使用するという方法を使わざるを得ないことがよくありました。 T-SQL 2005では、テーブル値ユーザー定義関数とクエリの連係がしやすくなっています。この記事の前編では、新しい 今回は、テーブル値ユーザー定義関数をもっと直接的に使う別の方法を紹介します。T-SQL 2005では、テーブル値ユーザー定義関数をサブクエリで使用でき、外部クエリの列をユーザー定義関数の引数として渡せるのです。 Northwindデータベースで、5,000ドルを超える注文が2件以上ある顧客や、1,000ドルを超える注文が5件以上ある顧客などを知りたいとします。最初の手順は、 次の手順では、データベースの各顧客に対してそのユーザー定義関数を実行し、注文が2件以上ある顧客を判断します。できれば、サブクエリを作成して、各顧客をパラメータとしてユーザー定義関数に渡せれば理想的です。ここで、T-SQL 2005の機能を活用できます。 SQL Server 2000の場合、相関サブクエリ内のテーブル値関数では、外部クエリの列を参照できませんでした。うれしいことに、SQL Server 2005ではその制約がなくなりました。ユーザー定義関数を使用するサブクエリを作成して、外部クエリの列をユーザー定義関数に引数として渡すことができます(リスト7の後半部分を参照)。 リスト7 サブクエリ内でユーザー定義関数を使用する
CREATE FUNCTION [dbo].[GetCustOrders_GT_X] (@CustomerID AS varchar(10), @nThreshold AS decimal(14,2)) RETURNS @tOrders TABLE (OrderID int, CustomerID varchar(10), OrderDate datetime, OrderAmount decimal(14,2)) AS BEGIN INSERT INTO @tOrders SELECT OH.OrderID, CustomerID, OrderDate, (UnitPrice * Quantity) as Orderamount FROM Orders OH JOIN [dbo].[Order Details] OD ON OH.OrderID = OD.OrderID WHERE CustomerID = @CustomerID AND (UnitPrice * Quantity) > @nThreshold ORDER BY OrderAmount DESC RETURN END GO DECLARE @nNumOrders int, @nMinAmt decimal(14,2) SET @nNumOrders = 2 SET @nMinAmt = 5000.00 SELECT CustomerID FROM Customers WHERE (SELECT COUNT(*) FROM DBO.GetCustOrders_GT_X(CustomerID,@nMinAmt)) >=@nNumOrders -- ResultsHUNGO QUICK ERNSH SAVEA RATTC -- To get the actual orders > 5000 for these 5 customers, -- we can turn the query above into a derived table, and then -- use a CROSS APPLY SELECT MaxOrders.* FROM (SELECT CustomerID FROM Customers WHERE (SELECT COUNT(*) FROM dbo.GetCustOrders_GT_X(CustomerID,@nThresholdAmt)) >= @nNumOrders) as CustList CROSS APPLY GetCustOrders_GT_X(CustomerID,@nThresholdAmt) AS MaxOrders ヒント12:SQL 2005のXML機能の拡張SQL Server 2000には、XMLを処理するための多種多様な機能があります。SQL Server 2005では、新しいXMLデータ型によって、XML機能が拡張されています。 リスト8とリスト9は、XMLデータを扱ういくつかの機能の例です。リスト8では、複数の異なる方法で、標準の列にXMLデータを挿入しています。リスト9では、XML列の中で文字列を検索しています(この構文については、Microsoft SQL Serverニュースグループの常連さんたちにご協力いただきました。ありがとうございました)。 リスト8 標準の列にXMLデータを挿入する
-- First Example of inserting XML into a table of columns DECLARE @cXMLDoc XML declare @hdoc int SET @cXMLDoc = ’ <AddressType > <AddressRecord AccountID = "1" Street="31 Main Dr" City="Philly" State="PA" Zip="12345"/> <AddressRecord AccountID = "2" Street="1 Wilson Dr" City="Newark" State="NJ" Zip="22222"/> </AddressType>’ EXEC sp_xml_preparedocument @hdoc OUTPUT, @cXMLDoc SELECT * FROM OPENXML (@hdoc, ’/AddressType/AddressRecord’,1) WITH (AccountID int,Street varchar(100), City varchar(100), State varchar(10), ZipCode varchar(13)) -- Second example of inserting XML into a table of columns -- uses the Address tag to specify nested columns DECLARE @cXMLDoc XML declare @hdoc int SET @doc = ’ <customer> <Customernum>48456</Customernum> <Firstname>Kevin</Firstname> <Lastname>Goff</Lastname> <Address> <city>Allentown</city> <state>PA</state> </Address> </customer>’ EXEC sp_xml_preparedocument @idoc OUTPUT, @doc DECLARE @tTemp TABLE (Customernum int, Firstname char(50), Lastname char(50), City char(50), State Char(10)) INSERT INTO @ttemp SELECT Customernum,firstname,lastname,city,state FROM OPENXML (@idoc, ’/customer’,2) WITH (Customernum int,Firstname varchar(50),Lastname varchar(50), city varchar(50) ’./Address/city’, state varchar(50) ’./Address/state’) リスト9 XMLデータ型内で検索を行う
-- Performing a partial text search inside an XML column declare @tTest table (address xml) insert into @ttest values (’ <Address > <AddrRecord AccountID = "1" Street="31 Main Dr" City="Newark" State="NJ" Zip="11111" /> </Address>’ ) insert into @ttest values (’ <Address > <AddrRecord AccountID = "2" Street="1 Wilson Rd" City="Philly" State="PA" Zip="22222"/> </Address>’ ) SELECT * FROM @ttest WHERE Address.exist(’/Address/AddrRecord [contains(@City,"hil")]’) = 1 ヒント13:SQL 2005でのテーブル変数に関する制限の緩和テーブル変数は、SQL Server 2000で導入され、多くのデータベース開発者から歓迎されました。しかし、テーブル変数にはいくつかの制限がありました。その1つが、ストアドプロシージャの結果をテーブル変数に直接挿入できないという点でした。したがって、次のコードはT-SQL 2000では使えません。 INSERT @tTable EXEC <sp_mystoredproc> うれしいことに、T-SQL 2005ではその制約がなくなりました。ストアドプロシージャを実行して、テーブル変数に直接挿入できます(リスト10を参照)。 リスト10 ストアドプロシージャの結果をテーブル変数に直接挿入する
DECLARE @tUser TABLE (UserName varchar(100),Status char(10),UserPK int) INSERT INTO @tUser exec [dbo].[cgsValidateUserID] ’KGOFF’,’KGOFF’ SELECT * FROM @tUser データ処理についての雑感これまで何度も述べたことですが、結局のところ、開発者の仕事の主軸となるのはデータ処理です。この記事で2回にわたって見てきたように、Visual Studio 2005とSQL Server 2005では、データ処理が扱いやすくなっています。その中には、ジェネリックやSQLのPIVOTコマンドのように、大幅な機能拡張もあれば、ADO.NETでの個別の行ごとのフィルタのように、細かいながら重要な変更もあります。 Visual Studio 2003のコードをVisual Studio 2005の環境でメンテナンスしている方なら、こうした新しいデータ処理機能をすぐには活用できないかもしれません。しかし、時間の許す限り、新機能について学び、その機能を使ったプロトタイプを作成して、いざというときのために備えておきましょう。 参考文献ばっちり空調の効いた部屋に1週間ほど閉じこもって、こうした新機能についてひたすら掘り下げてみたいと思ったことはありませんか。あいにく、現実世界にはさまざまな要件や要求があるため、その実現は通常は困難です。しかし、数時間の余裕があるのなら、次に挙げるような優れた参考文献で学ぶことが可能です。 1つ目は、『CoDe Magazine 』誌の記事です。同誌には、SQL Server 2005でのT-SQLとXMLについて、すばらしい記事が掲載されています。2005年1月/2月号には、Jim Duffyの「SQL Server 2005 T-SQL Enhancements」という記事があります。Jimは話が上手で、テクノロジ全般に精通しています。 『CoDe Magazine』誌の2006年5月/6月号には、Shawn Wildermuthによる「Making Sense of the XML DataType in SQL Server 2005」というすばらしい記事が掲載されています。もし「年間最優秀記事賞」のようなものがあったとしたら、私はこの記事に1票を投じたいと思います。Shawnの名前をGoogleで検索してみると、ADO.NETに関する有益な記事がネット上で多数見つかります。 2つ目は、Manuel Abadiaがネット上で公開している、ObjectDataSourceに関する文章です。ExtendedObjectDataSourceという独自クラスも公開されています。Manuelのコンテンツはここから参照できます。 最後に紹介するのは書籍です。Tod Goldingが書いた『Professional .NET 2.0 Generics』(Tod Golding 著、Wiley Publishing、2005年10月)はたいへん優れた本で、ジェネリックについて詳しく解説しています。コードはC#とVB.NETの両方で示されています。 最後に文章、書類、コードなどを提出したときに、その後になって、もっと良いアイデアが浮かんだという経験はありませんか。実のところ、私は後知恵の帝王です。幸い、そうした場合の対応にはブログがうってつけです。「The Baker’s Dozen」に関連する補足情報や追加のヒントについては、私のブログ(www.TheBakersDozen.net)をぜひチェックしてください。他にも、お得な情報が載っているかもしれませんよ。 過去の記事著者紹介Kevin S. Goff(Kevin S. Goff)
.NET、Visual FoxPro、SQL Server、Crystal Reportsによる独自のWebソリューション/デスクトップソリューションを提供するコンサルティンググループ「Common Ground Solutions」の創業者兼主任コンサルタント。ソフトウェアアプリケーションの開発経験は17年に及ぶ。米農務省からシステムオートメーション関連の賞をいくつか受賞。また、6桁の投資見返りを実現するソリューションを開発したことによりFortune 500企業から特別表彰を受ける。保険、会計、環境衛生、不動産、出版、広告、製造、金融、日用品、貿易振興など多様な業界に携わった経験を持ち、さまざまな形態で独自のトレーニングも行っている。
|