WebSurfer's Home

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

SQL Server の Order By での濁音の扱い

by WebSurfer 2017年12月15日 16:10

SQL Server のデフォルトでの照合順序 Japanese_CI_AS で、Order By 句による濁音の並び順がどうなるかについて書きます。元の話は teratail のスレッド半角の「濁音なし」「濁音あり」カナのソート順についてです。

照合順序 Japanese_CI_AS

照合順序 Japanese_CI_AS の場合の結果は上の画像の通りです。濁音はアクセントとして扱われ、'キ' と 'ギ' および 'キ' と 'ギ' は Order By 句では同じ順序となり、「クロギアイコ」は「クロキマユ」より前に、「クロギアイコ」は「クロキマユ」より前になっているのが分かるでしょうか。

半角カナの 'ギ' は、実際は 2 つの文字 'キ' (U+FF77) と '゙' (U+FF8D) を合わせたものなのですが、にもかかわらず「クロギアイコ」は「クロキマユ」より前になるのが不思議でした。

その理由は、MSDN Blogs の記事「日本語照合順序での 濁音、半濁音 の取り扱いについて」に書いてありますが、日本語照合順序を使用している場合 '半角文字' + '濁音' または '半濁音' が 1 文字として認識されるからだそうです。

つまり、Order By では濁点がないのと同じ扱いになり、照合順序に _WS の指定がないので全角・半角の区別をせず、上の画像の様な結果となるということのようです。

ちなみに、照合順序を Japanese_BIN2 にして Order By 句を適用すると以下の画像の順序になります。

照合順序 Japanese_BIN2

BIN2 というのは "すべての文字をコードポイントによる比較を行います" とのことです。詳しくは、MSDN Blogs の記事「照合順序 – 文字の比較と並び順 (その 1)」を見てください。

--------------------------------------------

以下にオマケで、Linq で OrderBy を使った時どうなるかという話を書いておきます。これも元は teratail の別スレッドでの話です。

単純に words.OrderBy(s => s); としたときは SQL Server で照合順序を Japanese_CI_AS とした時と同じになります。

カスタム Comparer を定義し、それを OrderBy の第 2 引数に使えば何とでもできるはずです。SQL Server の照合順序 Japanese_BIN2 と同じ結果になるようにするには、String.CompareOrdinal メソッド(それぞれの文字列の対応する Char オブジェクトの数値を評価することで、2 つの String を比較)が使えそうです。

以下のコードで検証した限りですが、望む結果が得られました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
    public class StringCompareOrdinal : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.CompareOrdinal(x, y);
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            string[] words = { "the", "quick", "brown", 
                "fox", "jumps","クロキマユ", "クロギアイコ", 
                "クロギトミオ", "テスト 項目", "クロキマユ", 
                "クロギアイコ", "クロギトミオ" };

            var query = words.OrderBy(s => s);

            foreach (string s in query)
                Console.WriteLine(s);
            Console.WriteLine("-------------------");

            var query2 = words.OrderBy(s => s, 
                               new StringCompareOrdinal());

            foreach (string s in query2)
                Console.WriteLine(s);

            /*
            結果は:
            brown
            fox
            jumps
            quick
            the
            クロギアイコ
            クロギアイコ
            クロギトミオ
            クロギトミオ
            クロキマユ
            クロキマユ
            テスト 項目
            -------------------
            brown
            fox
            jumps
            quick
            the
            クロキマユ
            クロギアイコ
            クロギトミオ
            テスト 項目
            クロキマユ
            クロギアイコ
            クロギトミオ
            */
        }
    }
}

Tags: , ,

SQL Server

異なるデータソースの結合と表示

by WebSurfer 2017年11月26日 13:34

異なるデータソース(例えば SQL Server と CSV ファイル)のレコードを内部結合または左外部結合して GridView などに一覧表示する例を書きます。

GridView に表示

上の画像はこの記事で紹介するサンプルの実行結果で、SQL Server と CSV ファイルをデータソースに使い、左側が内部結合、右側が左外部結合した結果一覧を ASP.NET Web Forms アプリの GridView に表示したものです。

この記事で使用したデータソースは、Microsoft が提供しているサンプルデータベース Northwind の Orders テーブルと、Customers テーブルから一部のフィールド / レコードを抜き出して作った以下の画像の CSV ファイルです。

CSV ファイル内容

データソースが両方とも SQL Server のサンプルデータベース Northwind にあれば、SELECT クエリで JOIN 句を使って結合し、その結果を DataTable などに取得するのが簡単ですが、一方が CSV ファイルではそうはいきません。

ではどうするかと言うと、SQL Server のテーブルと CSV ファイルそれぞれから List<T> 型のオブジェクトを作り、それを Linq で結合した結果を GridView のデータソースとしてバインドしてやるのがよさそうです。

Linq を使って結合する例は Microsoft の文書「join 句 (C# リファレンス)」やそれからリンクが張ってある記事が参考になりました。

上の画像を表示したサンプルコードは以下の通りです。説明はコメントとして書きましたので、それを見てください。(手抜きでスミマセン)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

// SQL Server の Northwind サンプルデータベース Orders
// テーブルのレコードを格納するクラス定義
public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public int? EmployeeID { get; set; }
    public DateTime? OrderDate { get; set; }
    public DateTime? RequiredDate { get; set; }
    public DateTime? ShippedDate { get; set; }
    public int? ShipVia { get; set; }
    public decimal? Freight { get; set; }
    public string ShipName { get; set; }
    public string ShipAddress { get; set; }
    public string ShipCity { get; set; }
    public string ShipRegion { get; set; }
    public string ShipPostalCode { get; set; }
    public string ShipCountry { get; set; }
}

// CSV ファイルのレコードを格納するためのクラス定義
public class Customer
{
    public string CustomerID { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
}

// 結合後の結果を格納するためのクラス定義
public class Result
{
    public int OrderID { get; set; }
    public string CompanyName { get; set; }
    public DateTime? OrderDate { get; set; }
    public decimal? Freight { get; set; }
}

public partial class _0019_GridViewJoinedList : 
    System.Web.UI.Page
{
  // SQL Server のサンプルデータベース Northwind の
  // Orders テーブルからデータを取得して List<Order>
  // オブジェクトを生成。Entity Framework を使う方が簡単
  // だが、ここではプリミティブに ADO.NET の SqlDataReader 
  // を使用した。
  protected List<Order> CreateOrderList()
  {
    List<Order> orders = new List<Order>();

    string connString = WebConfigurationManager.
            ConnectionStrings["NORTHWINDConnectionString"].
            ConnectionString;

    string query = "SELECT [OrderID], [CustomerID]," +
            "[EmployeeID], [OrderDate], [RequiredDate]," +
            "[ShippedDate], [ShipVia], [Freight]," +
            "[ShipName], [ShipAddress], [ShipCity]," +
            "[ShipRegion], [ShipPostalCode], [shipCountry]" +
            "FROM [Orders]";

    using (SqlConnection conn = new SqlConnection(connString))
    {
      conn.Open();
      using (SqlCommand cmd = new SqlCommand(query, conn))
      {
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
          if (reader != null)
          {
            while (reader.Read())
            {
              Order record = new Order();

              record.OrderID = reader.GetInt32(0);
              record.CustomerID = reader.IsDBNull(1) ?
                      null : reader.GetString(1);
              record.EmployeeID = reader.IsDBNull(2) ?
                      null : (int?)reader.GetInt32(2);
              record.OrderDate = reader.IsDBNull(3) ?
                      null : (DateTime?)reader.GetDateTime(3);
              record.RequiredDate = reader.IsDBNull(4) ?
                      null : (DateTime?)reader.GetDateTime(4);
              record.ShippedDate = reader.IsDBNull(5) ?
                      null : (DateTime?)reader.GetDateTime(5);
              record.ShipVia = reader.IsDBNull(6) ?
                      null : (int?)reader.GetInt32(6);
              record.Freight = reader.IsDBNull(7) ?
                      null : (decimal?)reader.GetDecimal(7);
              record.ShipName = reader.IsDBNull(8) ?
                      null : reader.GetString(8);
              record.ShipAddress = reader.IsDBNull(9) ?
                      null : reader.GetString(9);
              record.ShipCity = reader.IsDBNull(10) ?
                      null : reader.GetString(10);
              record.ShipRegion = reader.IsDBNull(11) ?
                      null : reader.GetString(11);
              record.ShipPostalCode = reader.IsDBNull(12) ?
                      null : reader.GetString(12);
              record.ShipCountry = reader.IsDBNull(13) ?
                      null : reader.GetString(13);

              orders.Add(record);
            }
          }
        }
      }
    }
    return orders;
  }

  // CSV ファイルからデータを取得して List<Customer> オブ
  // ジェクトを生成。
  protected List<Customer> CreateCustomerList()
  {
    List<Customer> customers = new List<Customer>();

    string csvFile = Server.MapPath("~/App_Data/TextFile.csv");

    using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp =
      new Microsoft.VisualBasic.FileIO.TextFieldParser(
        csvFile,
        System.Text.Encoding.GetEncoding("Shift_JIS")))
    {
      //フィールドがデリミタで区切られている
      tfp.TextFieldType =
        Microsoft.VisualBasic.FileIO.FieldType.Delimited;
      // デリミタを , とする
      tfp.Delimiters = new string[] { "," };
      // フィールドを " で囲み、改行文字、デリミタを
      // 含めることができるか
      tfp.HasFieldsEnclosedInQuotes = true;
      // フィールドの前後からスペースを削除
      tfp.TrimWhiteSpace = true;

      while (!tfp.EndOfData)
      {
        string[] fields = tfp.ReadFields();

        Customer customer = new Customer()
        {
          CustomerID = fields[0],
          CompanyName = fields[1],
          ContactName = fields[2],
          ContactTitle = fields[3]
        };
        customers.Add(customer);
      }
    }
    return customers;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      List<Order> orders = CreateOrderList();
      List<Customer> customers = CreateCustomerList();

      // 内部結合
      var innerJoin = from o in orders
                      join c in customers
                      on o.CustomerID equals c.CustomerID
                      select new Result
                      {
                        OrderID = o.OrderID,
                        CompanyName = c.CompanyName,
                        OrderDate = o.OrderDate,
                        Freight = o.Freight
                      };

      // シーケンスが空の場合に返すデフォルト値
      // 下の DefaultIfEmpty メソッドの引数に設定する
      Customer defaultValue = new Customer() {
                CustomerID = string.Empty,
                CompanyName = string.Empty,
                ContactName = string.Empty,
                ContactTitle = string.Empty };

      // 左外部結合
      var leftOuterJoin = 
          from o in orders
          join c in customers
          on o.CustomerID equals c.CustomerID into cGroup
          from item in cGroup.DefaultIfEmpty(defaultValue)
          select new Result
          {
              OrderID = o.OrderID,
              CompanyName = item.CompanyName,
              OrderDate = o.OrderDate,
              Freight = o.Freight
          };

      // 上の画像の左側の GridView(内部結合)
      GridView1.DataSource = innerJoin;
      GridView1.DataBind();

      // 上の画像の右側の GridView(左外部結合)
      GridView2.DataSource = leftOuterJoin;
      GridView2.DataBind();
    }
  }
}

Tags: , ,

ASP.NET

MonthCalendar の Size

by WebSurfer 2017年11月4日 14:32

MonthCalendar

Windows Forms アプリケーション用に、カレンダーを表示してユーザーが日付を選択できる MonthCalendar コントロールがあります。

その Size プロパティから MonthCalendar のサイズを取得する際、タイミングによっては正しいサイズが取得できない、その場合でもデバッガで MonthCalendar を開いてその中身を見ると正しいサイズになるという不可解なことがありました。

具体的には、下のコードの Button_Click メソッドをデバッガでステップ実行させ、コメント (3) の行で止めて Size プロパティを見ると 178 x 155 となっているが、calendar の中身をデバッガで開いて見た後で Size プロパティを見ると 199 x 162 と正しい値になるというものです。

その理由を調べたので備忘録として書いておきます。なお、元の話は Teratail のスレッド「VisualStudioでデバッグ中にプロパティの値が変化する」です。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      Button button = new Button();
      button.Click += Button_Click;
      this.Controls.Add(button);
    }

    private void Button_Click(object sender, EventArgs e)
    {
      Form f = new Form();
      Size size1 = f.Size;    // (1) W: 300, H: 300
      f.Size = new Size(200, 200);
      size1 = f.Size;         // (2) W: 200, H: 200

      MonthCalendar calendar = new MonthCalendar();
      Size size2 = calendar.Size;   // (3) W: 178, H: 155
      calendar.Size = new Size(200, 200);
      size2 = calendar.Size;        // (4) W: 178, H: 155
      f.Controls.Add(calendar);
      size2 = calendar.Size;        // (5) W: 178, H: 155
      f.Show();
      size2 = calendar.Size;        // (6) W: 199, H: 162

      f.ClientSize = calendar.Size;
    }
  }
}

上に書いた「不可解なこと」の理由は、多少想像が入っていますが、以下のようなことだと思われます。

  1. MonthCalendar のサイズを決定するのは使用されるフォントのみ。MonthCalendar.Size プロパティの設定では変えられない・・・コメント (4) 参照。
  2. new MonthCalendar() の時点ではフォントが不明なので MonthCalendar のサイズは未定。
  3. コメント (3) の時点で Size プロパティを見ると 178 x 155 となっているが、それはデフォルト値でフォントを反映した正しいサイズではない。
  4. コメント (3) の時点で calendar.Size の calendar にマウスカーソルを当てて開くと、その時点で calendar が初期化され、使用されるフォントに応じて正しいサイズが Size プロパティに設定される。
  5. その後で calendar.Size の Size にマウスカーソルを当てると正しいサイズ 199 x 162 が取得できる。
  6. デバッガで calendar を開いて見るということをしなければ、使用されるフォントに応じて正しいサイズが Size プロパティに設定されるのは、上記のコードでは f.Show(); の時点。
  7. その後であれば、calendar.Size で正しいサイズを取得でき、それを Form の ClientSize に設定してやれば上の画像の通り calendar がフォーム内にぴったり収まる。

上のことを書いた Microsoft の公式文書などは見つからないのですが、コードで検証した結果が上記の想像は正しいことを裏付けていると思います。

Tags: ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2018年7月  >>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar