T-SQL - BETWEENのメモ

BETWEENは自分ではあまり使わないのでたまに見かけるとあれ?含むの?含まないの?どっちだっけ?ってなります。

カップル専用アプリのことではないです。

between A and B

A以上かつB以下。AもBも含む。

select *
from (values(1), (2), (3), (4), (5)) as t(c)
where c between 2 and 4;

-- 結果
/*
c
-----------
2
3
4
*/

not between A and B

Aより小さいまたはBより大きい。

select *
from (values(1), (2), (3), (4), (5)) as t(c)
where c not between 2 and 4;

-- 結果
/*
c
-----------
1
5
*/

一度試しておくと記憶に残るかな?と。まあ忘れてたらこれを見る。

SQL Server - xmlデータ型のメソッド(query、value、nodes)を試す

そういえばT-SQLxmlを扱ったことがないなーと思ったので、とりあえず基本の基本を押さえるために簡単なサンプルを書いてみましたというお話です。

まずxmlデータ型のメソッドはこれだけあります。

そのうちよく使うんじゃないかなと勝手に思うqueryメソッド、valueメソッド、nodesメソッドを使ってみます。

構文 簡単な説明 詳細
query('XQuery') xmlからxmlを取得する query() メソッド (xml データ型)
value('XQuery', 'SQLType') xmlからスカラー値を取得する value() メソッド (xml データ型)
nodes('XQuery') as Table(Column) xmlからxmlの行セットを取得する nodes() メソッド (xml データ型)

それぞれのメソッドの引数にはXQueryというxmlに問い合わせる文字列を渡します。この文字列は要素の位置をさすパスになることが多いのかなと思います。またXQueryのパスには省略構文があります。省略構文はよく使うのかなと思います。

queryメソッド、valueメソッド、nodesメソッドを使ってみる

ドキュメントを読むよりクエリを見た方がわかりやすいので早速見ていきましょう。

-- xmlを変数に用意
declare @xml xml = N'
<root>
  <item id="1">abc</item>
  <item id="3">efg</item>
  <item id="2">xyz</item>
  <item id="5">lmn</item>
</root>';

-- 変数@xmlに各メソッドを使ってみる
select
    item.query('.') as Item,   -- item要素自身をxmlとして取得
    item.value('@id', 'tinyint') as Id,   -- item要素のid属性の値をtinyintとして取得
    item.value('.', 'nvarchar(3)') as Content -- item要素の中身をnvarchar(3)として取得
from
    -- root要素の子のitem要素をt(item)というテーブル(カラム)の行セットとして取得
    @xml.nodes('/root/item') as t(item);

メソッド引数の「.」がカレント要素、「@」が属性の省略構文ですね。

このクエリを実行すると次の結果になります。

f:id:ichiroku11:20160819180203p:plain

cross applyでnodesメソッド

上記ではxml型の変数に対してnodesメソッドを呼び出しました。テーブルのカラムにxml型のデータがある場合はどうしたらいいかというと、from句でcross applyを使ってnodesメソッドを呼び出すといいみたいです。

-- from句のcross applyでnodesメソッドを呼び出す
select
    item.query('.') as Item,
    item.value('@id', 'tinyint') as Id,
    item.value('.', 'nvarchar(3)') as Content
from (values(@xml)) as src(xml) -- srcテーブルのxmlカラムにxmlデータがあるとして
    cross apply xml.nodes('/root/item') as t(item);

このクエリを実行すると1つめのクエリと同じ結果が得られます。

これでとりあえずとっかかりができたかなと思います。

.NETで名前付きパイプを試す(4) - 複数のクライアントに対応したサーバにする

もう少しNamedPipeClientStreamとNamedPipeServerStreamを試してみます。

前回のサーバは1つのクライアントの1つのリクエストを処理するだけでプログラムが終了しています。

ichiroku11.hatenablog.jp

これではさすがにサーバとは言えないと思うので、もう少しサーバっぽくしたいと思います。具体的には次のことに対応していきます。

  • 1つのサーバで複数のクライアントからのリクエストを処理できるようにする
  • 複数のリクエストを並列処理できるようにする

また前回までは「プロセス間」通信ということを確認したかったのでプロジェクトを分けましたが、今回は実行結果がわかりやすいように1つのプロジェクトにしてスレッド間で試しています。

複数のクライアントに対応したサーバ

まずはサーバのプログラムを終了しないようにして、複数のクライアントからのリクエストを処理できるようにします。

Serverメソッド内の処理全体をwhile文で囲んでいます。当然なのかもしれませんが、クライアントの NamedPipeClientStreamで接続が閉じられるとサーバのNamedPipeServerStreamも接続が閉じられるようで、NamedPipeServerStreamのインスタンスはwhile文の中で都度生成しています。ストリームのインスタンスは使い回すものじゃないのかなと。

serverIdは識別用です。後半でサーバを複数作るときに使います。

// サーバ
private static async Task Server(int serverId, Func<Message, Message> process) {
    while (true) {
        using (var stream = new NamedPipeServerStream("testpipe")) {
            // クライアントからの接続を待つ
            Console.WriteLine($"Server#{serverId} waiting");
            await stream.WaitForConnectionAsync();
            Console.WriteLine($"Server#{serverId} connected");

            // クライアントからリクエストを受信する
            var request = default(Message);
            using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
                request = reader.ReadObject<Message>();
            }
            Console.WriteLine($"Server#{serverId} {nameof(request)}: {request}");

            // リクエストを処理してレスポンスを作る
            var response = process(request);

            // クライアントにレスポンスを送信する
            Console.WriteLine($"Server#{serverId} {nameof(response)}: {response}");
            using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
                writer.WriteObject(response);
            }
        }
    }
}

// サーバでの処理(Serverメソッドの引数に渡す)
// Contentの文字列を逆順にする処理
private static Message ServerProcess(Message message) {
    return new Message {
        Id = message.Id,
        Content = new string(message.Content.Reverse().ToArray()),
    };
}

複数のクライアントから接続

続いてクライアントです。clientIdは識別用です。

// クライアント
private static async Task<Message> Client(int clientId, Message request) {
    using (var stream = new NamedPipeClientStream("testpipe")) {
        // サーバに接続
        Console.WriteLine($"Client#{clientId} connecting");
        await stream.ConnectAsync();
        Console.WriteLine($"Client#{clientId} connected");

        // サーバにリクエストを送信する
        Console.WriteLine($"Client#{clientId} {nameof(request)}: {request}");
        using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
            writer.WriteObject(request);
        }

        // サーバからレスポンスを受信する
        var response = default(Message);
        using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
            response = reader.ReadObject<Message>();
        }
        Console.WriteLine($"Client#{clientId} {nameof(response)}: {response}");

        return response;
    }
}

実行する

クライアントを2つとサーバを1つ動かしてみます。

サーバが1つなのでクライアントは同時に接続できていないことが確認できます。サーバではリクエストを1つずつ順に処理していることもわかります。

static void Main(string[] args) {
    Task.WaitAll(new[] {
        // クライアント
        Client(1, new Message { Id = 10, Content = "あいうえお", }),
        Client(2, new Message { Id = 20, Content = "かきくけこ", }),
        // サーバ
        Server(1, ServerProcess),
    });
}

// 実行結果(実行するごとに変わる部分もありますが)
/*
Client#1 connecting
Client#2 connecting
Server#1 waiting
Client#2 connected // Client#2が接続(Client#1はまだ接続待ち)
Server#1 connected
Client#2 request: { Id = 20, Content = "かきくけこ" }
Server#1 request: { Id = 20, Content = "かきくけこ" }
Server#1 response: { Id = 20, Content = "こけくきか" }
Client#2 response: { Id = 20, Content = "こけくきか" }
Server#1 waiting
Server#1 connected
Client#1 connected // ここでやっとClient#1が接続できる
Client#1 request: { Id = 10, Content = "あいうえお" }
Server#1 request: { Id = 10, Content = "あいうえお" }
Server#1 response: { Id = 10, Content = "おえういあ" }
Client#1 response: { Id = 10, Content = "おえういあ" }
Server#1 waiting
*/

複数のリクエストを並列に処理するサーバ

さらにサーバでは複数のリクエストを並列に処理できるようにしてみます。

NamedPipeServerStreamのコンストラクタにはサーバインスタンスの最大数を指定できるオーバーロードがあります。

このコンストラクタを使って複数のサーバインスタンスを用意できるように修正します。今回は最大数を2にしてみます。(ちなみに上記のサーバはサーバインスタンスの最大数は1でした。)

// サーバ
private static async Task Server(int serverId, Func<Message, Message> process) {
    while (true) {
        // サーバインスタンスを2に
        // using (var stream = new NamedPipeServerStream("testpipe")) {
        using (var stream = new NamedPipeServerStream("testpipe", PipeDirection.InOut, 2)) {
            // この部分は上記のServerメソッドと同じなので省略
        }
    }
}

実行する

クライアントとサーバを2つずつ動かしてみます。それぞれのサーバでリクエストが処理されていることを確認できます。

static void Main(string[] args) {
    Task.WaitAll(new[] {
        // クライアント
        Client(1, new Message { Id = 10, Content = "あいうえお", }),
        Client(2, new Message { Id = 20, Content = "かきくけこ", }),
        // サーバ
        Server(1, ServerProcess),
        Server(2, ServerProcess),
    });
}

// 実行結果(実行するごとに変わる部分もありますが)
/*
Client#1 connecting
Client#2 connecting
Server#1 waiting
Client#1 connected // Client#1が接続
Server#1 connected
Server#2 waiting
Server#2 connected
Client#2 connected // Client#2が接続
Client#2 request: { Id = 20, Content = "かきくけこ" }
Server#2 request: { Id = 20, Content = "かきくけこ" }
Server#2 response: { Id = 20, Content = "こけくきか" }
Client#2 response: { Id = 20, Content = "こけくきか" }
Server#2 waiting
Client#1 request: { Id = 10, Content = "あいうえお" }
Server#1 request: { Id = 10, Content = "あいうえお" }
Server#1 response: { Id = 10, Content = "おえういあ" }
Client#1 response: { Id = 10, Content = "おえういあ" }
Server#1 waiting
*/

これでNamedPipeClientStreamとNamedPipeServerStreamの基本的な使い方は把握できたかなーと思います。

ソース一式はこちらに。

.NETで名前付きパイプを試す · GitHub

おしまい。

.NETで名前付きパイプを試す(3) - クライアントサーバ間で送受信する

前回に続いてもう少しNamedPipeClientStreamとNamedPipeServerStreamを使った名前付きパイプを試します。

ichiroku11.hatenablog.jp

今回はクライアント側のプログラムからサーバ側のプログラムにリクエストを送ってレスポンスを受け取るようにしてみます。クライアント側、サーバ側それぞれのプログラムの動きは次のようにしたいと思います。

クライアント側のプログラムの動き

  1. サーバに接続
  2. サーバにリクエストを送信する
  3. サーバからレスポンスを受信する

サーバ側のプログラムの動き

  1. クライアントからの接続を待つ
  2. クライアントからリクエストを受信する
  3. リクエストを処理してレスポンスを作る
  4. クライアントにレスポンスを送信する

これでちょっとはクライアントサーバっぽくなってくるかなと。

クライアント側のプログラム

まずはクライアント側からです。ReadObjectとWriteObject、Messageは前回のエントリで使ったものと同じです。またBinaryWriter、BinaryReaderのusingの部分でパイプのストリームを閉じないようにしています。

// クライアント側
class Program {
    private static async Task<Message> Client(Message request) {
        using (var stream = new NamedPipeClientStream("testpipe")) {
            // 1. サーバに接続
            await stream.ConnectAsync();

            // 2. サーバにリクエストを送信する
            Console.WriteLine($"{nameof(request)}: {request}");
            using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
                writer.WriteObject(request);
            }

            // 3. サーバからレスポンスを受信する
            var response = default(Message);
            using(var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
                response = reader.ReadObject<Message>();
            }
            Console.WriteLine($"{nameof(response)}: {response}");

            return response;
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Client");

        Client(new Message {
            Id = 1,
            Content = "あいうえお",
        }).Wait();
    }
}

サーバ側のプログラム

続いてサーバ側です。リクエストからレスポンスを作る部分は、受け取ったリクエストの文字列をそのまま返すのも寂しいので文字列を逆順にしてレスポンスを作っています。

// サーバ側
class Program {
    private static async Task Server(Func<Message, Message> process) {
        using (var stream = new NamedPipeServerStream("testpipe")) {
            // 1. クライアントからの接続を待つ
            await stream.WaitForConnectionAsync();

            // 2. クライアントからリクエストを受信する
            var request = default(Message);
            using(var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
                request = reader.ReadObject<Message>();
            }
            Console.WriteLine($"{nameof(request)}: {request}");

            // 3. リクエストを処理してレスポンスを作る
            var response = process(request);

            // 4. クライアントにレスポンスを送信する
            Console.WriteLine($"{nameof(response)}: {response}");
            using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
                writer.WriteObject(response);
            }
        }
    }

    // Contentの文字列を逆順にする処理
    private static Message Process(Message message) {
        return new Message {
            Id = message.Id,
            Content = new string(message.Content.Reverse().ToArray()),
        };
    }

    static void Main(string[] args) {
        Console.WriteLine("Server");

        Server(Process).Wait();
    }
}

実行する

2つのプログラムを実行すると送受信できることを確認できます。

// クライアント側の実行結果
Client
request: { Id = 1, Content = "あいうえお" }
response: { Id = 1, Content = "おえういあ" }

// サーバ側の実行結果
Server
request: { Id = 1, Content = "あいうえお" }
response: { Id = 1, Content = "おえういあ" }

今回も目的をクリアできたのでこのへんで。

.NETで名前付きパイプを試す(2) - クライアントからサーバにオブジェクトを送る

前回はクライアントからサーバに文字列を送ってみましたが、文字列だけでは少し寂しいので今回はオブジェクトを送信したいと思います。それでもまだ寂しいけど。

ichiroku11.hatenablog.jp

下準備

目的をクリアするために必要なクラスを準備していきます。

オブジェクトはバイト配列で送信することにして、まずはオブジェクトとバイト配列を変換するクラスを用意します。といってもヘルパー的なメソッドを持っている程度です。

// オブジェクトとバイト配列の変換
public class ObjectConverter<TObject> {
    private readonly IFormatter _formatter;

    public ObjectConverter(IFormatter formatter = null) {
        _formatter = formatter ?? new BinaryFormatter();
    }

    // オブジェクト=>バイト配列
    public byte[] ToByteArray(TObject obj) {
        using (var stream = new MemoryStream()) {
            _formatter.Serialize(stream, obj);
            return stream.ToArray();
        }
    }

    // バイト配列=>オブジェクト
    public TObject FromByteArray(byte[] bytes) {
        using (var stream = new MemoryStream(bytes)) {
            return (TObject)_formatter.Deserialize(stream);
        }
    }
}

バイト配列をストリームに読み書きするためにBinaryWriterとBinaryReaderを使いますが、ちょっとした拡張メソッドも用意しておきます。以下の方法がベストなのかわかりませんが、バイト配列の長さとバイト配列の順で書き込み、バイト配列の長さとバイト配列の順で読み込むようにしています。

// BinaryWriterの拡張メソッドを定義
public static class BinaryWriterExtensions {
    // オブジェクトの書き込み
    public static void WriteObject<TObject>(this BinaryWriter writer, TObject obj) {
        // オブジェクトをバイト配列に変換
        var converter = new ObjectConverter<TObject>();
        var bytes = converter.ToByteArray(obj);

        // 長さを書き込んでからバイト配列を書き込む
        writer.Write(bytes.Length);
        writer.Write(bytes);
    }
}

// BinaryReaderの拡張メソッドを定義
public static class BinaryReaderExtensions {
    // オブジェクトの読み込み
    public static TObject ReadObject<TObject>(this BinaryReader reader) {
        // 長さを読み込んでから、その長さ分のバイト配列を読み込む
        var length = reader.ReadInt32();
        var bytes = reader.ReadBytes(length);

        // バイト配列をオブジェクトに変換
        var converter = new ObjectConverter<TObject>();
        return converter.FromByteArray(bytes);
    }
}

送信する適当なオブジェクトを用意しておきます。結局は文字列という・・・サンプル力が弱いなと。

// メッセージ
[Serializable]
public class Message {
    public int Id { get; set; }
    public string Content { get; set; }

    public override string ToString() {
        return $@"{{ {nameof(Id)} = {Id}, {nameof(Content)} = ""{Content}"" }}";
    }
}

クライアント側

クライアントです。前回のサンプルで内側のusingの部分でStreamを閉じてしまっていることに気づきました。今回は閉じないようにしています。

// クライアント側
class Program {
    private static async Task Client() {
        using (var stream = new NamedPipeClientStream("testpipe")) {
            // サーバに接続
            await stream.ConnectAsync();

            // オブジェクトを送信(書き込み)
            // このusingでStreamを閉じないようにしておく
            using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) {
                Console.WriteLine($"Send");
                var message = new Message {
                    Id = 16,
                    Content = "あいうえお",
                };
                writer.WriteObject(message);
                Console.WriteLine($"Sent: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Client");

        Client().Wait();
    }
}

// 実行結果
/*
Client
Send
Sent: { Id = 16, Content = "あいうえお" }
*/

サーバ側

サーバです。

// サーバ側
class Program {
    private static async Task Server() {
        using (var stream = new NamedPipeServerStream("testpipe")) {
            // クライアントからの接続を待つ
            await stream.WaitForConnectionAsync();

            // オブジェクトを受信(読み込み)
            using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) {
                Console.WriteLine("Receive");
                var message = reader.ReadObject<Message>();
                Console.WriteLine($"Received: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Server");

        Server().Wait();
    }
}

// 実行結果
/*
Server
Receive
Received: { Id = 16, Content = "あいうえお" }
*/

2つとも実行するとクライアント側からサーバ側にオブジェクトを送信できていることが確認できます。

名前付きパイプというよりオブジェクトのストリーム読み書きのサンプルみたいになった気もしますが、今回はこんなところで。