WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

PIVOT の使用

by WebSurfer 2010年8月7日 18:18

SQL Server の PIVOT 関係演算子を利用してデータをまとめ、クロス集計レポートを生成し、GridView に表示する例です。

ほとんどの処置はストアドプロシージャで行うので、ASP.NET というよりは SQL Server の機能になりますが。

ここの例では、Microsoft が提供しているサンプルデータベース Northwind の Orders テーブル使用しました。

Orders テーブルは、個々の注文の ID (OrderID, int)、注文した顧客の ID (CustomerID, nchar(5))、注文を取り扱った従業員の ID (EmployeeID, int)、注文を受けた年月日 (OrderDate, datetime) などのデータを約 800 レコード持っています。

DropDownList に顧客名一覧を表示し、その中から顧客を選択すると、その注文を取り扱った従業員と取り扱った注文数を、注文年月ごとに表示するようにしました。以下のような感じです。

PIVOT によるクロス集計レポート

上記のクロス集計を行うためのストアドプロシージャは以下の通りです。

ALTER PROCEDURE dbo.StoredProcedure1
(
@CustomerID nchar(5)
)
AS
DECLARE @sqlstr nvarchar(max), @sqldata nvarchar(max), @employee nvarchar(50); 
DECLARE cur CURSOR FOR 

SELECT DISTINCT EmployeeID 
  FROM Orders 
  WHERE EmployeeID IS NOT NULL AND [CustomerID]=@CustomerID 
  ORDER BY EmployeeID; 

SET @sqldata = N''; 
SET @sqlstr = N'SELECT Year, Month '; 

OPEN cur; 
FETCH NEXT FROM cur INTO @employee; 
WHILE (@@FETCH_STATUS <> - 1) 
  BEGIN 
    IF LEN(@sqldata) > 0 
      BEGIN
        SET @sqldata = @sqldata + N',' 
      END; 
    SET @sqldata = @sqldata + N'[' + @employee + N']'
    SET @sqlstr = @sqlstr + N',[' + @employee + N'] AS [Emp-' + @employee + N']'; 
    FETCH NEXT FROM cur INTO @employee; 
  END; 
CLOSE cur;
DEALLOCATE cur; 

SET @sqlstr = @sqlstr +
  N' FROM 
     (
       SELECT DATEPART(mm, OrderDate) AS Month, DATEPART(yyyy, OrderDate) AS Year, EmployeeID 
       FROM Orders 
       WHERE CustomerID=@ID
     ) AS p 
     PIVOT 
     (
       COUNT(EmployeeID) 
       FOR EmployeeID 
       IN '; 
SET @sqlstr = @sqlstr + N'(' + @sqldata + N')) AS pvt'; 
EXECUTE sp_executesql @sqlstr, N'@ID nchar(5)', @CustomerID

顧客一覧を DropDownList に、クロス集計結果を GridView に表示するための ASP.NET のコードは以下の通りです。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>無題のページ</title>
</head>
<body>
  <form id="form2" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>"             
      SelectCommand="
        SELECT DISTINCT Orders.CustomerID, Customers.CompanyName 
        FROM Orders 
        INNER JOIN Customers 
        ON Orders.CustomerID = Customers.CustomerID">
    </asp:SqlDataSource>

    <asp:DropDownList ID="DropDownList1" 
      runat="server" 
      DataSourceID="SqlDataSource1" 
      DataTextField="CompanyName" 
      DataValueField="CustomerID" 
      AutoPostBack="True">
    </asp:DropDownList>
        
    <asp:SqlDataSource ID="SqlDataSource2" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand="StoredProcedure1"
      SelectCommandType="StoredProcedure">
      <SelectParameters>
        <asp:ControlParameter 
          ControlID="DropDownList1" 
          Name="CustomerID" 
          PropertyName="SelectedValue" 
          Type="String" />
      </SelectParameters>        
    </asp:SqlDataSource>

    <asp:GridView ID="GridView1" 
      runat="server" 
      DataSourceID="SqlDataSource2">
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags: ,

SQL Server

GridView でクリックされたセル位置の取得

by WebSurfer 2010年8月7日 15:04

ユーザーがブラウザ上で GridView のセルをクリックすると、ポストバックされて、サーバ側のプログラムでクリックされたセルの位置を取得する方法です。

それだけでは面白くない(?)ので、データソースの列が動的に変化するとし、ヘッダを含めて LinkButton または Button で表示し、ページングを有効にするというシナリオです。

以下の画像のような感じです。これはページャーの 3 をクリックして 3 ページに移動し、ヘッダーを含めて上から 2 行目の 10000 をクリックした結果です。

GridView でクリックされたセルの位置の取得

アプリは以下のようにして作成します。こんなシナリオは普通ないので、役に立たないと言われるかもしれませんが(苦笑)

ページング

ページングのためのコードを自力で書くのはかなり大変です。ページングの機能を持つデータソースコントロールとデータバインドコントロールを組み合わせて使うのが現実的と思います。

ここでは、データソースコントロールに ObjectDataSource を使用し、DataTable を作成するメソッドと GridView(データバインドコントロール)の間を仲介してやることにします。

GridView の列の設定

データソースの列が動的に変化するという前提があります。

テキストで表示するなら、AutoGenerateColumns を true にすれば自動的に必要な列を生成してくれますが、LinkButton または Button を使って表示するという要件があるのでそうはいきません。

AutoGenerateColumns="False" としておき、Page.Load のイベントハンドラで DataTable から列情報を取得し、その情報をもとに ButtonField を初期化して、GridView に設定してやるという操作が必要です。

各行へのデータバインドは、ButtonField.DataTextField プロパティに DataColumn.Caption を設定しておけば、ObjectDataSource と GridView が自動で行ってくれます。

セル位置の設定・取得

クリックされたセルの列・行位置は、GridView.RowCommand イベントハンドラで、引数の GridViewCommandEventArgs オブジェクトの CommandName, CommandArgument プロパティから取得します。

列番号の取得には CommandName を利用します。そのためには、事前に CommandName に列番号を設定しておく必要があります。上記の「GridView の列の設定」の際に DataColumn.Ordinal プロパティから DataTable 内での列位置を取得し、ButtonField.CommandName プロパティに設定します。

行番号の取得には CommandArgument を使用します。CommandArgument にはデフォルトで行番号が設定されるので、事前の設定は不要です。

取得できる行番号は、ページングした場合、例えば1ページ目の n 行と、2ページ目の n 行とで同じになりますので注意してください。

ヘッダー行の変更

ここまでの処置では、まだヘッダーが普通のテキストのままです。これを LinkButton または Button で表示するようにします。

それには、GridView.RowCreated イベントのハンドラで、ヘ��ダーの Cell の内容を LinkButton または Button に書き換えてやります。

サンプルコード


<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Header)
    {
      TableCellCollection cells = e.Row.Cells;
      foreach (TableCell cell in cells)
      {
        string text = cell.Text;
        cell.Controls.Clear();
        //LinkButton button = new LinkButton();  // ハイパーリンク表示
        Button button = new Button();          // ボタン表示                
        button.Text = text;
        button.CommandArgument = "-1";
        button.CommandName = cells.GetCellIndex(cell).ToString();
        cell.Controls.Add(button);
      }
    }
  }

  protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
  {
    // ページャーがクリックされた場合
    if (e.CommandName == "Page")
    {
      Label1.Text = String.Empty;
    }
    else
    {
      Label1.Text = "Page: " + GridView1.PageIndex.ToString() + 
        ", Command Argument: " + e.CommandArgument.ToString() + 
        ", Command Name: " + e.CommandName;
    }
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!Page.IsPostBack)
    {
      DataView view = (DataView)ObjectDataSource1.Select();
      DataTable table = view.Table;
      foreach (DataColumn column in table.Columns)
      {
        ButtonField field = new ButtonField();
        //field.ButtonType = ButtonType.Link;     // ハイパーリンク表示
        field.ButtonType = ButtonType.Button;   // ボタン表示                
        field.HeaderText = column.Caption;
        field.DataTextField = column.Caption;
        field.CommandName = column.Ordinal.ToString();
        GridView1.Columns.Add(field);
      }
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h3>方法2: ButtonField を利用</h3>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      SelectMethod="CreateDataTable2" 
      TypeName="PagingTest">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AllowPaging="True" 
      DataSourceID="ObjectDataSource1" 
      onrowcreated="GridView1_RowCreated" 
      onrowcommand="GridView1_RowCommand"
      PageSize="5" 
      AutoGenerateColumns="False" 
      EnableViewState="True">
    </asp:GridView>
    <asp:Label ID="Label1" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

ついでに、ObjectDataSource の TypeName, SelectMethod が参照しているクラス/メソッドもアップしておきます。


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

public class PagingTest
{
  public static DataTable CreateDataTable2()
  {
    DataTable wktbl = new DataTable();
    DataRow wkrow;
        
    wktbl.Columns.Add("y/x");
    wktbl.Columns.Add("x1.min/.max");
    wktbl.Columns.Add("x2.min/.max");
    wktbl.Columns.Add("x3.min/.max");
    wktbl.Columns.Add("x4.min/.max");

    for (int i = 0; i < 20; i++)
    {
      wkrow = wktbl.NewRow();
      wkrow["y/x"] = "y1.min/.max";
      wkrow["x1.min/.max"] = "10000";
      wkrow["x2.min/.max"] = "20000";
      wkrow["x3.min/.max"] = "30000";
      wkrow["x4.min/.max"] = "40000";
      wktbl.Rows.Add(wkrow);
    }

    return wktbl;
  }
}

Tags: ,

ASP.NET

GridView の列名の取得

by WebSurfer 2010年8月2日 12:35

GridView の n 番目(スタートは n = 0)の列名は、通常、GridView1.HeaderRow.Cells[n].Text のようにして取得できますが、AllowSorting プロパティが true になっていると、それでは取得できません(結果は "" になります)。

何故なら、GridView1.HeaderRow.Cells[n] には LinkButton コントロールが配置され、LinkButton の Text プロパティに列名が設定されるからです。

その場合は、以下のようにして取得できます。

string headerText;
foreach (Control ctl in GridView1.HeaderRow.Cells[n].Controls)
{
  if (ctl is LinkButton)
  {
    headerText = ((LinkButton)ctl).Text; 
    break;
  }
}

たぶん、GridView1.HeaderRow.Cells[n].Controls[0] が LinkButton になると思いますが、保証の限りではないので、ちょっと冗長かもしれませんが、上記のようにループで回して取得するように考えました。

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar