Quantcast
Channel: ささいなことですが。
Viewing all 104 articles
Browse latest View live

LambdicSql - α 0.0.14リリース - Nullable 対応

$
0
0

Nullableに対応しました。

www.nuget.org

publicclass RemunerationNullable
{
    publicint id { get; set; }
    publicint? staff_id { get; set; }
    public DateTime? payment_date { get; set; }
    publicdecimal? money { get; set; }
}
publicclass DBNullable
{
    public Staff tbl_staff { get; set; }
    public RemunerationNullable tbl_remuneration { get; set; }
}

publicclass SelectDataNullable
{
    publicstring name { get; set; }
    public DateTime? payment_date { get; set; }
    publicdecimal? money { get; set; }
}

publicvoid Nullable()
{
    var query = Sql<DBNullable>.Create(db =>
        Select(new SelectDataNullable()
        {
            name = db.tbl_staff.name,
            payment_date = db.tbl_remuneration.payment_date,
            money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
            Where(db.tbl_remuneration.money != null));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS name,
	tbl_remuneration.payment_date AS payment_date,
	tbl_remuneration.money AS money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE (tbl_remuneration.money) <> (NULL)

履歴

2016/09/02 β版対応


LambdicSql - 速度はまだまだでした・・・ -

$
0
0

※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。

@yone64さんと@neueccさんにご指摘いただき、タイトルを含め修正しました。


結果から書くと、負け負けのようですね。
これはパフォーマンス改善を先にやるかなー。

テーブル

対象はこんなテーブルで1万件データが入っています。

IntValFloatValDoubleValDecimalValStringVal
00000
11111
22222
33333
44444
55555

スコア

スコアです。単位はミリ秒です。

(msec)LambdicSqlDapper
1359335
22927
32622
43130
52526
62625
73329
82929
92425
103229
平均61.457.7

まあ、でも一万件でこれなら誤差なんじゃないかなー。

と思っておりましたが、

@neueccさんから
「いや、全然誤差じゃないですよ」
ってご指摘いただきました。
で、測り方も、適当すぎると。

「DBって割合としてはwhereで絞って1件とか数件とかってのを対象にするのがほとんどで、良質なネットワーク・良質なデータベースを相手にしていると1ms以下で結果取れたりします。そこで一回のクエリに2msプラスで乗っかると、なんともいえない気持ちにはなります:)」

で、再計測。下のコードでデータをWhereで1件に絞っている方ですね。本来はマッピングの処理だけを測るべきなのですが、一旦。

(msec)LambdicSqlDapper
13.3552.1567
22.39420.7323
35.1360.7556
41.87240.7031
52.10950.585
61.83910.72
71.87360.7003
81.83420.7007
93.15610.7081
平均2.61890.862422222

コード

こんなコードで計測しました。念のためリリースビルドでやってます。

using Dapper;
using LambdicSql;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

namespace Performance
{
    class TableValues
    {
        publicint IntVal { get; set; }
        publicfloat FloatVal { get; set; }
        publicdouble DoubleVal { get; set; }
        publicdecimal DecimalVal { get; set; }
        publicstring StringVal { get; set; }
    }
    class DB
    {
        public TableValues TableValues { get; set; }
    }

    [TestClass]
    publicclass SelectTime
    {
        //1万件取得
        [TestMethod]
        publicvoid CheckLambdicSql()
        {
            var times = new List<double>();
            using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
            {
                connection.Open();
                for (int i = 0; i < 10; i++)
                {
                    var watch = new Stopwatch();
                    watch.Start();
                    var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues).ToExecutor(connection).Read().ToList();
                    watch.Stop();
                    times.Add(watch.Elapsed.TotalMilliseconds);
                }
            }
            ShowTime(times);
        }

        [TestMethod]
        publicvoid CheckDapper()
        {
            var times = new List<double>();
            using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
            {
                connection.Open();
                for (int i = 0; i < 10; i++)
                {
                    var watch = new Stopwatch();
                    watch.Start();
                    var datas = connection.Query<TableValues>("select IntVal, FloatVal, DoubleVal, DecimalVal, StringVal from TableValues;").ToList();
                    watch.Stop();
                    times.Add(watch.Elapsed.TotalMilliseconds);
                }
            }
            ShowTime(times);
        }

        //以下はwhereで1件に絞ったもの
        [TestMethod]
        publicvoid CheckLambdicSqlCondition()
        {
            var times = new List<double>();
            using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
            {
                connection.Open();
                for (int i = 0; i < 10; i++)
                {
                    var watch = new Stopwatch();
                    watch.Start();
                    var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues).Where(db => db.TableValues.IntVal == 1).ToExecutor(connection).Read().ToList();
                    watch.Stop();
                    times.Add(watch.Elapsed.TotalMilliseconds);
                }
            }
            ShowTime(times);
        }

        [TestMethod]
        publicvoid CheckDapperCondition()
        {
            var times = new List<double>();
            using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
            {
                connection.Open();
                for (int i = 0; i < 10; i++)
                {
                    var watch = new Stopwatch();
                    watch.Start();
                    var datas = connection.Query<TableValues>("select IntVal, FloatVal, DoubleVal, DecimalVal, StringVal from TableValues where IntVal = 1;").ToList();
                    watch.Stop();
                    times.Add(watch.Elapsed.TotalMilliseconds);
                }
            }
            ShowTime(times);
        }

        staticvoid ShowTime(List<double> times)
        {
            MessageBox.Show(string.Join(Environment.NewLine, times.Select(e => e.ToString())) +
                Environment.NewLine + times.Average().ToString());
        }
    }
}

lambdicSql - パフォーマンス改善せねば -

$
0
0

※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。

前回ので、Dapperに完敗を喫したわけですが、ここで勝負を投げるわけにはいかないですね。改善策を考えてみました。

遅いのは、コンパイル

他にも要因はあるかもしれませんが、まずは分かりやすいところからつぶしていきましょう。

マッピングする型の生成

これは素直にキャッシュすることにしました。一回はコンパイルしますけどね。

パラメータ

※2016/07/12 この仕様やっぱりやめました。
次回へ続く。


今までの仕様だと、どうやってもコンパイルなしには取れないんじゃないかなー。(←いや取れるかも)なので少し面倒になりますが、パラメータを別途渡せるようにしました。pの型を第二引数で決定するというところが微妙。あまり速度が気にならない人は、今まで通り書けます。ミリ秒を争う場合はこれを使うってことでどうでしょう?
α0.0.20
で一旦Whereだけに実験的に実装してみました。

[TestMethod]
publicvoid CheckLambdicSqlCondition()
{
    var times = new List<double>();
    using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
    {
        connection.Open();
        for (int i = 0; i < 10; i++)
        {
            var watch = new Stopwatch();
            watch.Start();
            var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues).
            //★Dapperと同じようにパラメータを渡す
            Where((db, p) => db.TableValues.IntVal == p.val, new { val = 1 }).
            ToExecutor(connection).Read().ToList();
            watch.Stop();
            times.Add(watch.Elapsed.TotalMilliseconds);
        }
    }
    ShowTime(times);
}

それで、このDapperのコードと速度を比べてみます。

[TestMethod]
publicvoid CheckDapperCondition()
{
    var times = new List<double>();
    using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
    {
        connection.Open();
        for (int i = 0; i < 10; i++)
        {
            var watch = new Stopwatch();
            watch.Start();
            var datas = connection.Query<TableValues>("select IntVal, FloatVal, DoubleVal, DecimalVal, StringVal from TableValues  where IntVal = @Id;", new { Id = 1 }).ToList();
            watch.Stop();
            times.Add(watch.Elapsed.TotalMilliseconds);
        }
    }
    ShowTime(times);
}

結果は・・・

良くなってきたのですが、LambdicSqlの方は、たまに重い時がある。Dapperの方は何回かやってみても、これがないんですよねー。

(msec)LambdicSqlDapper
12.41312.1083初回はいいとして
20.84550.7581
30.82990.745
40.83810.7671
50.82540.7499
60.83150.7466
70.81680.7483
83.01120.7429※なにこれ?
90.91520.7503
平均1.2585222220.901833333


そろそろプロファイラ使うか。

もっと真面目な計測は?

とりあえず、ここを超えたら、もう少し真面目にやります。
(まだお手本のDapperのテストプロジェクトのビルド通せてない)

lambdicSql - パフォーマンス改善せねば② -

$
0
0

※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。

やっぱ、昨日の仕様なし

前回コンパイルを減らすためにパラメータを渡すという仕様にしたのですが、やめます。
www.nuget.org

変数から値を取得するラムダを一回コンパイルしてキャッシュすることにしました。

これもキャッシュできました。こんな感じ。
ここではメンバ取得処理をインターフェイスでキャッシュしています。
Type Erasure っていう手法ですね。

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace LambdicSql.Inside
{
    staticclass ExpressionToObject
    {
        static Dictionary<string, IGetter> _memberGet = new Dictionary<string, IGetter>();

        internalstaticbool GetMemberObject(MemberExpression exp, outobject obj)
        {
            //ConstantExpressionを探す
            obj = null;
            var member = exp;
            var names = new List<string>();
            ConstantExpression constant = null;
            while (member != null)
            {
                names.Add(member.Member.Name);
                constant = member.Expression as ConstantExpression;
                if (constant != null)
                {
                    break;
                }
                member = member.Expression as MemberExpression;
            }
            if (constant == null)
            {
                returnfalse;
            }

            //キャッシュの名前はこれで一意に決まるんじゃないかな。
            var getterName = constant.Type.FullName + "@" + string.Join("@", names.ToArray());
            IGetter getter;
            lock (_memberGet)
            {
                //ヒット!高速に値が取得できるif (_memberGet.TryGetValue(getterName, out getter))
                {
                    obj = getter.GetMemberObject(constant.Value);
                    returntrue;
                }
            }

            //最初の一回は仕方ない//パラメータは毎回変わるので、そこは引数で渡せるラムダを生成
            var param = Expression.Parameter(constant.Type, "param");
            Expression target = param;
            names.Reverse();
            names.ForEach(e => target = Expression.PropertyOrField(target, e));
            getter = Activator.CreateInstance(typeof(GetterCore<>).MakeGenericType(constant.Type), true) as IGetter;
            getter.Init(Expression.Convert(target, typeof(object)), param);
            lock (_memberGet)
            {
                if (!_memberGet.ContainsKey(getterName))
                {
                    _memberGet.Add(getterName, getter);
                }
            }
            obj = getter.GetMemberObject(constant.Value);
            returntrue;
        }

        //ジェネリック型で生成してインターフェイスで型を消して使うinterface IGetter
        {
            void Init(Expression exp, ParameterExpression param);
            object GetMemberObject(object target);
        }

        class GetterCore<T> : IGetter
        {
            Func<T, object> _func;

            publicvoid Init(Expression exp, ParameterExpression param)
            {
                _func = Expression.Lambda<Func<T, object>>(exp, new[] { param }).Compile();
            }

            publicobject GetMemberObject(object target) => _func((T)target);
        }
    }
}

こんな感じでめでたく、普通に書くことができました。
フィールドやプロパティーから取得するのはこの手法が使えてキャッシュされるので速いですね。ただラムダ式にメソッド呼び出し入ると、今のところコンパイル入りますね。余裕があればそこも頑張ってみるかなー。

[TestMethod]
publicvoid CheckLambdicSqlCondition()
{
    int x = 1;
    var times = new List<double>();
    using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
    {
        connection.Open();
        for (int i = 0; i < 10; i++)
        {
            var watch = new Stopwatch();
            watch.Start();
            var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues).
               Where(db => db.TableValues.IntVal == x).ToExecutor(connection).Read().ToList();
            watch.Stop();
            times.Add(watch.Elapsed.TotalMilliseconds);
        }
    }
    ShowTime(times);
}

キャッシュされてるから二回目以降は速いですね。
ちょっと変なの混じるからコンソールアプリに変えて100回の平均を出してみました。
0.15ミリ差がありますね。
もう少し細かく見てみよっと。

(msec)LambdicSqlDapper
11.42192.1768
20.98911.088
30.78760.8783
40.73470.6551
50.7290.6662
62.34990.6391
1000.70680.8274
平均0.9080616160.758524242

lambdicSql - パフォーマンス改善せねば ③計測 -

$
0
0

※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。

もう少し詳しく計測してみることにします。
VisualStudioのパフォーマンスプロファイラが便利ですね。
VSTestではイマイチやりにくいのでコンソールアプリでやります。

using System;

namespace Performance
{
    class Program
    {
        staticvoid Main(string[] args)
        {
            //ユーザー入力を待つ
            Console.ReadLine();
            
            //テスト実行
            SelectTime.CheckLambdicSqlCondition();
            Console.WriteLine("Finish");
            Console.ReadLine();
        }
    }
}

起動時間とかでノイズを拾うと結果が見づらくなるので、一時停止して開始を選びます。
f:id:ishikawa-tatsuya:20160713005547p:plain
で、起動が終わると、再開させます。
f:id:ishikawa-tatsuya:20160713005737p:plain
そして何かキー入力をして、本当に計測したい処理を実行。
CheckLambdicSqlConditionの中では1000回クエリを発行しています。

internalstaticvoid CheckLambdicSqlCondition()
{
    CheckTimeCore(connection =>
    {
        int x = 1;
        var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues).
               Where(db => db.TableValues.IntVal == x).ToExecutor(connection).Read().ToList();
    });
}
staticvoid CheckTimeCore(Action<SqlConnection> action)
{
    using (var connection = new SqlConnection(TestEnvironment.ConnectionString))
    {
        connection.Open();
        for (int i = 0; i < 1000; i++)
        {
            action(connection);
        }
    }
}

終わったら停止を押します。

こんな感じでログが出ます。
f:id:ishikawa-tatsuya:20160713010308p:plain
ファイアスポットが分かりますね。
で、クリックするとさらに詳細な情報が分かります。
f:id:ishikawa-tatsuya:20160713010403p:plain

Expressionの解析も重いですが、しょうもないところでも時間を無駄にしていることが分かりますね。
まだまだ改善できそうです!

lambdicSql - パフォーマンス改善せねば④ SQLite -

$
0
0

プロファイラで見てわかりやすかったところを対応しました。
www.nuget.org

こっから先は難しいですね。
で、もう少しノイズを減らすためにDBをSQLサーバーからSQLiteに変えました。ファイルアクセスなんで余計なブレはすくないでしょう。ちなみに計測はSurfacePro4のCorei5でやってます。
SQLiteはDBアプリとか必要なくて、Nugetから落としてくるだけで使えます。
f:id:ishikawa-tatsuya:20160713233349p:plain
あれ?EF入るの?
EFないバージョンと分けてくれればいいのに。

SQLiteは方言対応とかあるのですが、まあ、今回の計測程度は使えます。で、計測。SQLは一件だけ取得するものです。

SELECT
        IntVal,
        FloatVal,
        DoubleVal,
        DecimalVal,
        StringVal
FROM
        TableValues
WHERE
        ((TableValues.IntVal) = (@p_0));
(msec)LambdicDapper
10.52430.2986
20.16820.0964
30.1370.0824
40.14070.0779
50.13410.0734
60.12260.0746
70.12340.0717
......
......
5000.10010.0648
平均0.133005210.075227455←差 0.057777756

速えー!
500回の平均で57µまで詰めました!
(でも、Dapperの1.76倍かかっているのか・・・)
このケースでは実使用上は問題ないレベルなんじゃないかなー。
まあ、ボトルネックはクエリ作成だから、もう少し色んなパターンで計測しないとダメでしょうけど。

ていうか、SQLite対応先にやろう。

LambdicSql -DistinctとAll

$
0
0

α0.0.36をリリースしました。
www.nuget.org

DistinctとAll

enumで指定できるように変更しました。

Select文

[TestMethod]
publicvoid Distinct()
{
    var query = Sql<DB>.Create(db =>
        Select(AggregatePredicate.Distinct, new
        {
            id = db.tbl_remuneration.staff_id
        }).
        From(db.tbl_remuneration));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECTDISTINCT
tbl_remuneration.staff_id AS id
FROM tbl_remuneration

集計関数

[TestMethod]
publicvoid GroupByPredicateDistinct()
{
    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            Count = (int)Count(AggregatePredicate.Distinct, db.tbl_remuneration.money),
            Total = Sum(AggregatePredicate.Distinct, db.tbl_remuneration.money)
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        GroupBy(db.tbl_staff.id, db.tbl_staff.name));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);
            
    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	COUNT(DISTINCT tbl_remuneration.money) AS Count,
	SUM(DISTINCT tbl_remuneration.money) AS Total
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
GROUPBY tbl_staff.id, tbl_staff.name

lambdicSql - CASE式に対応しました -

$
0
0

LabdicSql_α0.0.39をリリースしました。CASEに対応しています。
www.nuget.org
迷いましたがWHENとTHENはC#上ではWhenThenで書くようにしました。それからENDは書かなくても良いようにしました。
SELECT句への合成はサブクエリと同じ書き方で書くようにしました。

検索ケース式

[TestMethod]
publicvoid Case1()
{
    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Type = Case().
                        When(db.tbl_staff.id == 3).Then("x").
                        When(db.tbl_staff.id == 4).Then("y").
                        Else("z").
                    End()
        }).
        From(db.tbl_staff));
            
    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}

SQLです。ちょっと頑張って綺麗にタブが入るようにしました。この改善でサブクエリにも同様に改善されています。

SELECT
	CASE
		WHEN (tbl_staff.id) = (@p_0) THEN @p_1
		WHEN (tbl_staff.id) = (@p_2) THEN @p_3
		ELSE @p_4
	ENDASTypeFROM tbl_staff

単純ケース式

publicvoid Case2()
{
    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Type = Case(db.tbl_staff.id).
                        When(3).Then("x").
                        When(4).Then("y").
                        Else("z").
                    End()
        }).
        From(db.tbl_staff));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
	CASE tbl_staff.id
		WHEN @p_0 THEN @p_1
		WHEN @p_2 THEN @p_3
		ELSE @p_4
	ENDASTypeFROM tbl_staff

履歴

2016/09/02 β版対応


lambdicSql - Queryの自由な組み立て -

$
0
0

LambdicSqlα_0.0.40 をリリースしました。
Queryの組み立てに関して仕様追加と、既存の仕様を変更しました。α版なんで、破壊的な変更もバンバンいれます。
www.nuget.org

Queryは組み合わせることが重要

以前、おださんからこのような資料を紹介してもらいました。

www.slideshare.net
この中では、クエリを分解、構築、抽象化できると便利ですよ。って書かれてました。ということで、これをLambdicSqlに取り入れてみました。

LambdicSqlで可能な分解、構築

  1. 句単位で分解、構築
  2. Expression単位で分解、構築
  3. サブクエリ

サブクエリは前からありました。
今回は1,2を追加しました。サンプルコードが長くなるので、3回に分けて書きます。

lambdicSql - Queryの自由な組み立て - ①句単位 -

$
0
0

Queryの組み立てシリーズです。まずは、クエリ単位で分割、構築する方法です。Concatでクエリを連結できるようにしました。Concatは型に関してわざと緩くしました。使っているDBの型が違っても結合できます。最終的にSQL文字列になったときに意味が通っていたらOKです。(ダメならSQL実行で例外が発生します。)

publicvoid QueryConcat()
{
    //句単位で別々に記述
    var select = Sql<DB>.Create(db =>
        Select( new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }));

    var from = Sql<DB>.Create(db =>
            From( db.tbl_remuneration).
        Join( db.tbl_staff,  db.tbl_remuneration.staff_id == db.tbl_staff.id));

    var where = Sql<DB>.Create(db =>
        Where( 3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    var orderby = Sql<DB>.Create(db =>
        OrderBy(new Asc(db.tbl_staff.name)));

    //クエリ構築
    var query = select.Concat(from).Concat(where).Concat(orderby);

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	tbl_remuneration.payment_date AS PaymentDate,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))
ORDERBY
	tbl_staff.name ASC

例えば、Where句以外は使いまわしとかの場合は、外の句はstatic readonlyにしておくと便利ですね。

staticreadonly SqlQuery<SelectedData> select = Sql<DB>.Create(db =>
    Select(new SelectedData()
    {
        Name = db.tbl_staff.name,
        PaymentDate = db.tbl_remuneration.payment_date,
        Money = db.tbl_remuneration.money,
    }));

staticreadonly ISqlQuery from = Sql<DB>.Create(db =>
        From(db.tbl_remuneration).
    Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id));

staticreadonly ISqlQuery orderby = Sql<DB>.Create(db =>
    OrderBy(new Asc(db.tbl_staff.name)));
        
public IEnumerable<SelectedData> Execute(decimal min, decimal max)
{
    //Where句だけここで作成
    var where = Sql<DB>.Create(db =>
        Where( 3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //クエリ構築
    var query = select.Concat(from).Concat(where).Concat(orderby);

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}

履歴

2016/09/02

lambdicSql - Queryの自由な組み立て - ②Expression単位 -

$
0
0

LambdicSqlではExpression単位で分解、構築できるようにしました。
Castっていうキーワードがポイントです。Sql.Query().ExpressionはISqlExpressionという型を返します。これを文中に埋め込むとその文字列になります。でもISqlExpressionではコンパイル通らないので、それがコンパイルできる型にキャストするわけです。

publicvoid SqlExtension()
{
    var expMoneyAdd = Sql<DB>.Create(db => db.tbl_remuneration.money + 100);
    var expWhereMin = Sql<DB>.Create(db => 3000< db.tbl_remuneration.money);
    var expWhereMax = Sql<DB>.Create(db => db.tbl_remuneration.money < 4000);

    var query = Sql<DB>.Create(db =>
        Select(new SelectedData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            //作っておいたExpressionを組み込み//最終的にはdecimalで扱いたい
            Money = expMoneyAdd.Cast<decimal>(),
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        //組み合わせることも可能です。
        Where(expWhereMin && expWhereMax).
        OrderBy(new Asc(db.tbl_staff.name)));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS Name,
	tbl_remuneration.payment_date AS PaymentDate,
	(tbl_remuneration.money) + (@p_0) AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((@p_1) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_2))
ORDERBY
	tbl_staff.name ASC

条件文を構築しやすくるためのヘルパ

条件文も上の書き方で、連結していけるわけですが、ちょっと面倒です。

//こんな感じで連結可能
var expWhereMin = Sql<DB>.Create(db => 3000< db.tbl_remuneration.money);
var expWhereMax = Sql<DB>.Create(db => db.tbl_remuneration.money < 4000);
var expWhere = Sql<DB>.Create(db => expWhereMin && expWhereMax.Cast);

これはよくやるので、簡単にかけるようにしました。
Whereの組み立てで、その条件を有効にするか否かは頻繁にあり、そこで便利に使うことができます。ちなみに全部無効でExpressionが空になった場合はWhere句自体が消えます。

publicvoid ContinueConditions(bool minEnable, bool maxEnable)
{
    //その条件を有効にするかどうかを指定することができる
    var exp = Sql<DB>.Create(db =>
        Condition(minEnable, 3000< db.tbl_remuneration.money) &&
        Condition(maxEnable, db.tbl_remuneration.money < 4000) &&
        db.tbl_staff.id == 1);

    var query = Sql<DB>.Create(db =>
        Select(Asterisk(db.tbl_remuneration)).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        //特殊仕様で渡されたExpressionが空ならWhere句は消える。Havingも同様。
        Where(exp));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}

履歴

2016/09/02

lambdicSql - Queryの自由な組み立て - ③サブクエリ -

$
0
0

サブクエリは前からある機能ですが、おさらいで。
これも、クエリを先に作っておいて、Castで文中に入れ込みます。入れたところで展開されます。

Select句

publicvoid SelectSubQuery()
{
    //moneyの全合計
    var sub = Sql<DB>.Create(db=>
        Select(new
        {
            total = Sum(db.tbl_remuneration.money)
        }).
        From(db.tbl_remuneration));

    //スタッフごとにグルーピングされたmoneyと全体の合計を同時に表示する
    var query = Sql<DB>.Create(db =>
        Select(new
        {
            name = db.tbl_staff.name,
            personalTotal = Sum(db.tbl_remuneration.money),
            total = sub.Cast<decimal>()//★サブクエリ 代入したかのようなコードが書ける
        }).
        From(db.tbl_staff).
        Join(db.tbl_remuneration, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        GroupBy(db.tbl_remuneration.staff_id, db.tbl_staff.name));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
tbl_staff.name AS name,
	SUM(tbl_remuneration.money) AS personalTotal,
	(SELECT
	SUM(tbl_remuneration.money) AS total
	FROM tbl_remuneration) AS total
FROM tbl_staff
	JOIN tbl_remuneration ON (tbl_remuneration.staff_id) = (tbl_staff.id)
GROUPBY tbl_remuneration.staff_id, tbl_staff.name

Where句

publicvoid WhereInSubQuery()
{
    //サブクエリ作成
    var sub = Sql<Data>.Create(db =>
        Select(new
        {
            money = db.tbl_remuneration.money
        }).
        From(db.tbl_remuneration).
        Join(db.tbl_staff, db => db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //Inにサブクエリを入れる
    var query = Sql<Data>.Create(db =>
    Select(new Asterisk()).
    From(db.tbl_remuneration).

    //条件指定
    Where(In(db.tbl_remuneration.money, sub.Cast<int>()));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlConnection);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT *
FROM tbl_remuneration
WHERE tbl_remuneration.money IN(
	(SELECT
	tbl_remuneration.money AS money
	FROM tbl_remuneration
		JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
	WHERE ((@p_0) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))))

From句

From句で使うときは少しコツがあって、DB定義の部分で使っておきます。ここに入ってないと、続くクエリ構築で使えないのです。その後、From句で(JoinでもOK)で使います。

publicvoid FromSubQuery()
{
    //サブクエリ作成
    var sub = Sql<DB>.Create(db =>
        Select(new
        {
            name = db.tbl_staff.name,
            payment_date = db.tbl_remuneration.payment_date,
            money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    var query = Sql<DB>.Create(db =>
        Select(new
        {
            name = sub.Body.name
        }).
        //From句でサブクエリのテーブル(的なもの)を指定//View的な使い方
        From(sub));

    //文字列化
    Debug.Print(query.ToSqlInfo(typeof(SqlConnection)).SqlText);

    //Dapperを使っているなら、以下のように実行できます
    var datas = _connection.Query(query).ToList();
}
SELECT
sub.name AS name
FROM 
	(SELECT
	tbl_staff.name AS name,
		tbl_remuneration.payment_date AS payment_date,
		tbl_remuneration.money AS money
	FROM tbl_remuneration
		JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
	WHERE ((@p_2) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_3))) sub

履歴

2016/09/02 β版対応

LambdicSql - 生まれ変わりました -

$
0
0

※注) 以前のブログもβ版に合わせてコードを修正しているので、前のブログ記事と比べても変化は感じられません。

LambdicSql_α0.0.51 をリリースしました。コードのほとんどを書き直すほどの破壊的大変更を入れました。まさにRebornなのです!
www.nuget.org
変更内容は盛りだくさんです。
先日、総監督と打ち合わせして、その内容をかなり取り込みました。ボリューミーなので何回かに分けて書きます。手っ取り早く全貌を見たい方は、こちらのページを参照お願いします。
github.com

変更一覧

  1. 書き心地100%UP
  2. 断捨離
  3. 文字列との組み合わせ
  4. 2WaySQL
  5. パラメータ名称
  6. Window関数対応

今回は1.を書きます。

書き心地 100%Up(当社比)

大きくはこれです。以前比べると、かなり書きやすく、そして読みやすくなりました。

using Dapper;
using LambdicSql;
//★重要 using static.usingstatic LambdicSql.Keywords;
usingstatic LambdicSql.Funcs;
usingstatic LambdicSql.Utils;

publicvoid TestStandard()
{
    //ラムダでSQLを作成
    var query = Sql<DB>.Create(db =>
    Select(new SelectData
    {
        Name = db.tbl_staff.name,
        Count = Count(db.tbl_remuneration.money),
        Total = Sum(db.tbl_remuneration.money),
        Average = Avg(db.tbl_remuneration.money),
        Minimum = Min(db.tbl_remuneration.money),
        Maximum = Max(db.tbl_remuneration.money),
    }).
    From(db.tbl_remuneration).
        Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
    GroupBy(db.tbl_staff.id, db.tbl_staff.name));

    //文字列とパラメータに変更
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);
}
publicvoid TestCase()
{
   //case文がこんなにすっきり書ける
    var query = Sql<DB>.Create(db =>
        Select(new SelectData()
        {
            Type = Case().
                      When(db.tbl_staff.id == 3).Then("x").
                      When(db.tbl_staff.id == 4).Then("y").
                      Else("z").
                   End()
        }).
        From(db.tbl_staff));
}

Sql.Create()でまとめて書けるようになった

以前は句ごとに分かれていました。そのため、毎回 db=>て書く必要がありました。それが今回の変更で一つのExpressionとして表現するようになったので、db=>は最初の一回でよくなりました。

using staticを使いやすく変更

それから、これはVisualStudio2015を使ってないと無理なのですが(そろそろ、みんな使ってますよね?)メソッドをstaticにすることによって、using staticを使って唐突にメソッドを使えるようにしました。SelectとかSumとか唐突に呼べるようになって、さらにSQLっぽくなったのではないでしょうか。残念ながら、2013までの人はメソッドの前にクラス名を書く必要があります。

組み合わせ自由自在

以前までもできましたが、さらに自由になりました。暗黙の変換の導入でキャストの手間が減りました。まあ、キャストはコンパイルを通すためだけなので。

publicvoid TestSqlExpression()
{
    //式単位で扱うときも統一的に扱える
    var expMoneyAdd = Sql<DB>.Create(db => db.tbl_remuneration.money + 100);
    var expWhereMin = Sql<DB>.Create(db => 3000< db.tbl_remuneration.money);
    var expWhereMax = Sql<DB>.Create(db => db.tbl_remuneration.money < 4000);

    //式の型を覚えていて、暗黙に変換される//上手く合わないときだけCast<>()メソッドを使う    
    var query = Sql< DB>.Create(db =>
        Select(new SelectData1()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            //decimalに変換される
            Money = expMoneyAdd,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        //boolに変換され、それらを&&や||で演算できる
        Where(expWhereMin && expWhereMax).
        OrderBy(new Asc(db.tbl_staff.name)));
}

条件式作成も書きやすく、直感的に

publicvoid TestWhereEx(bool minCondition, bool maxCondition)
{
    //Conditionメソッドの第一引数は、その条件が有効か否かを設定    //無効なら消える//||演算も当然できるし、()を使った複雑な式にも対応
    var exp = Sql<DB>.Create(db =>
        Condition(minCondition, 3000< db.tbl_remuneration.money) &&
        Condition(maxCondition, db.tbl_remuneration.money < 4000));

    //条件がなくなったらWhereは消えます
    var query = Sql<DB>.Create(db => SelectFrom(db.tbl_remuneration).Where(exp));
}

LambdicSql - 断捨離 O/Rマッパー機能 捨てました。-

$
0
0

LambdicSqlもSQLを実行して、その結果をオブジェクトにマッピングする機能がありました。Dapperよりも便利なところもあって、匿名クラスにマッピングすることもできてました。ではなぜやめたか。
※Dapperでも匿名クラスにマッピングでる場合もあります。でも、失敗するケースもあり。DateTimeが入っていると大抵失敗する。詳細は追っていません。

//便利なとこもあるけど、この機能は捨てたpublicvoid Test()
{
    //以前は匿名クラスにマッピングすることも可能だった。
    var query = Sql.Query<DB>().
    Select(db => new
    {
        name = db.tbl_staff.name,
        payment_date = db.tbl_remuneration.payment_date,
        money = db.tbl_remuneration.money,
    }).
    From(db => db.tbl_remuneration).
        Join(db => db.tbl_staff, db => db.tbl_remuneration.staff_id == db.tbl_staff.id);

    //読み取り専用なはずの匿名クラスにマッピング
    var data = query.ToExecutor(new SqlConnection(sqlConnectionString)).Read();
}

特徴を際立たせる

LambdicSqlはラムダからSQLを作成するためのライブラリです。DBにはアクセスしたり、マッピングする機能ははオマケだったのです。でも両方あると「新種のO/Rマッパーね」くらいに捉えられて特徴が伝わりづらいのではないかなーと。

Dapper or LambdicSqlだとDapper使うよね。

折角つくったのだから、広く多くの人に使ってもらいたいですよね。でもこの分野はEntityFrameworkやDapperがすでにあり、そんな信頼性の高いライブラリと張り合てもなかなかねー。総監督からも、「Dapperの利用者が二択せまられたら、Dapper使うよねー」とのご意見をいただきました。それはそうですね。

LambdicSql feat.Dapper

Hey yo yo yeah!オレたちDapperマジリスペクト!
ってわけでフィーチャリングします。実際DapperとLambdicSqlでは注力している部分が違うのです。LambdicSqlでSQLを組み立てて、実行はDapperでやってもらえばよいかなーって思います。ということでサンプルコードです。

publicvoid TestStandard()
{
    var min = 3000;

    //LambdicSqlのお仕事//SQLを組み立てる
    var query = Sql<DB>.Create(db =>
        Select(new SelectData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_staff.id == db.tbl_remuneration.staff_id).
        Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //テキストとパラメータに落とす
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //ここから先はDapperにお任せ//パラメータはDictionary<string, object>で渡す
    var datas = _connection.Query<SelectData1>(info.SqlText, info.Parameters).ToList();
}

というわけでDapper使いの皆さま、合わせてLambdicSqlのご利用もお願いします!

LambdicSql - 文字列連携、そして2WaySQL -

$
0
0

LambdicSqlはLambdaでSQLを表現することを目的にしています。でも、やっぱり文字列も使えた方が安心感がありますよね。てわけで文字列を埋め込める機能を追加しました。

文字列を式に変換

こんな感じで文字列を途中に埋めれます。ただ文字列を入れるだけではなく、そこにラムダで表現した情報を入れれるのです。

publicvoid TestFormatText()
{
    var query = Sql<DB>.Create(db =>
        Select(new SelectData()
        {
            name = db.tbl_staff.name,
            payment_date = db.tbl_remuneration.payment_date,
            //好きな文字列を入れることができる
            money = Text<decimal>("{0} + {1}", db.tbl_remuneration.money, 1000),
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id).
        Where(3000< db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //to string and params.
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //dapper
    var datas = _connection.Query<SelectData1>(info.SqlText, info.Parameters).ToList();
}
SELECT
	tbl_staff.name AS"name",
	tbl_remuneration.payment_date AS"payment_date",
	/*↓こんな感じで合成される*/
	tbl_remuneration.money + @p_0 AS"money"FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)
WHERE ((@p_1) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_2))

2WaySQL

これは総監督から強い要望があった機能です。2WaySQLという考え方があるようです。Javaの界隈ではDomaというライブラリが有名なようです。

  1. テキストでベースのSQLを書く
  2. 動的に変えたいところにコメントで印をつける
  3. プログラムで動的に置換

ポイントは2.の時点でコメントで印をつけただけなので、そのままDBMSで実行することができるという点です。基本のSQLだけでも確認取れてた方が安心感が増しますよね。LambdicSqlではシンプルに/*no*/~/**/の間を置き換えるというルールにしました。

public void TestFormat2WaySql(bool minCondition, bool maxCondition, decimal bonus)
{
    var sql = @"SELECTtbl_staff.name AS name,tbl_remuneration.payment_date AS payment_date,tbl_remuneration.money + /*0*/1000/**/ AS moneyFROM tbl_remuneration JOIN tbl_staff ON tbl_staff.id = tbl_remuneration.staff_id/*1*/WHERE tbl_remuneration.money = 100/**/";

    var query = Sql<DB>.Create(db => TwoWaySql(sql,
        bonus,
        Where(
            Condition(minCondition, 3000< db.tbl_remuneration.money) &&
            Condition(maxCondition, db.tbl_remuneration.money < 4000))
        ));
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //Dapperを使っているなら以下で実行できます。
    //型はこの時点で指定してください
    var datas = _connection.Query<SelectedData>(query).ToList();
}
SELECT
    tbl_staff.name AS name,
    tbl_remuneration.payment_date AS payment_date,
    /*ここは置き換えられた*/
    tbl_remuneration.money + @bonus AS money
FROM tbl_remuneration 
    JOIN tbl_staff ON tbl_staff.id = tbl_remuneration.staff_id
/*ここも置き換えられた*//*最小の条件はfalseだったので消えた*//*条件がなくなった場合はWhereも消えます*/WHERE (tbl_remuneration.money) < (@p_0)

Where句とかを動的に作って差し込めるのは便利ですね。まあ、好みで使い分けていただけたらと思います。それに、場合によってはラムダで書けないみたいなケースも出てきそうですし、そんな時にテキストも使えると心強いですよね!

履歴

2016/09/02 β版対応


LambdicSql - α51 その他

$
0
0

パラメータ名称

@p_0って感じで連番にしていました。折角変数名を使っているのだから、それを変数名に使った方が良いとの意見をいただそのようにしました。

publicvoid ParamName()
{
    var min = 3000;
    var max = 4000;

    var query = Sql<Data>.Create(db =>
        Select(new
        {
            money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
        Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < max));

    var info = query.ToSqlInfo(cnn.GetType());
    Debug.Print(info.SqlText);
}
SELECT
	tbl_remuneration.money AS"money"FROM tbl_remuneration
WHERE ((@min) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@max))

ただ、一つの関数内で完結していれば、問題ないのですが、LambdicSqlは式を組み合わせることができるので、変数名も重複する可能性があります。
その場合は、_がついていくことになります。

publicvoid ParamName2()
{
    var min = 3000;
    var max = 4000;

    var query = Sql<Data>.Create(db =>
        Select(new
        {
            money = MoneyExp(5, 10).Cast<decimal>(),
        }).
        From(db.tbl_remuneration).
        Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < max));

    var info = query.ToSqlInfo(cnn.GetType());
    Debug.Print(info.SqlText);
}
public ISqlExpression<decimal> MoneyExp(int min, int max)
    => Sql<Data>.Create(db => db.tbl_remuneration.money + min + max);
SELECT
	((tbl_remuneration.money) + (@min)) + (@max) AS"money"FROM tbl_remuneration
WHERE ((@min_) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@max_))

Window関数対応

Window関数に対応しました。こんな感じで使えます。

publicvoid TestWindow()
{
    var query = Sql<DB>.Create(db =>
        Select(new SelectData()
        {
            Avg = Window.Avg(db.tbl_remuneration.money).
                    Over<decimal>(null,
                        new OrderBy(new Asc(db.tbl_remuneration.payment_date)),
                        null),
            PaymentDate = db.tbl_remuneration.payment_date
        }).
        From(db.tbl_remuneration));

    //to string and params.
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //dapper
    var datas = _connection.Query<SelectData>(info.SqlText, info.Parameters).ToList();
}
SELECT
	AVG(tbl_remuneration.money)OVER(
	ORDERBY
		tbl_remuneration.payment_date ASC) AS"Avg",
	tbl_remuneration.payment_date AS"PaymentDate"FROM tbl_remuneration

一気に書いても良いのですが、以下のように分けて書くことも可能です。

var avg = Sql<DB>.Create(db => Window.Avg(db.tbl_remuneration.money).
                Over<decimal>(null,
                    new OrderBy(new Asc(db.tbl_remuneration.payment_date)),
                    null));
//make sql.
var query = Sql<DB>.Create(db =>
    Select(new SelectData()
    {
        Avg = avg,
        PaymentDate = db.tbl_remuneration.payment_date
    }).
    From(db.tbl_remuneration));

それでWindow関数の書式なのですが、以下のようなものになっています。Window関数はいくつかは普通の集約関数と同名ですので、using staticはしない方が良いですね。

var avg = Sql<DB>.Create(db =>
        //関数
        Window.Avg(db.tbl_remuneration.money).
        //不要な部分はnullを入れる
        Over<decimal>(
            //Partitionを指定 複数指定可能new PartitionBy(db.tbl_staff.name, db.tbl_remuneration.payment_date),
            //OrderByを指定 複数指定可能new OrderBy(new Asc(db.tbl_remuneration.money), new Desc(db.tbl_remuneration.payment_date)),
            //Rowsを指定 引数は一つの場合はPRECEDINGのみの指定となるnew Rows(1, 5)));

LambdicSql - TableAttributeとColumnAttributeに対応しました。

$
0
0

LambdicSql_α0.0.53をリリースしました。
www.nuget.org

テーブル名とカラム名のルール

LambdicSqlのテーブル名とカラム名は、通常は変数名で表します。

publicclass Staff
{
    //変数名がカラム名になるpublicint id { get; set; }
    publicstring name { get; set; }
}
publicclass Remuneration
{
    publicint id { get; set; }
    publicint staff_id { get; set; }
    public DateTime payment_date { get; set; }
    publicdecimal money { get; set; }
}
publicclass DB
{
    //テーブル名も変数名で表すpublic Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}

スキーマを使う場合も変数名で表します。(もちろんスキーマを書く必要がない場合はこれは省略できます)

publicclass DBO
{
    public Staff tbl_staff { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}
publicclass DB_EX
{
    //スキーマも変数名で表すpublic DBO dbo { get; set;}
}
//スキーマまで書きたい場合
var query = Sql<DB_EX>.Create(db =>
    Select(new
    {
        Name = db.dbo.tbl_staff.name,
        PaymentDate = db.dbo.tbl_remuneration.payment_date,
        Money = db.dbo.tbl_remuneration.money,
    }).
    From(db.dbo.tbl_remuneration).
        Join(db.dbo.tbl_staff, db.dbo.tbl_staff.id == db.dbo.tbl_remuneration.staff_id));

var info = query.ToSqlInfo(_connection.GetType());
Debug.Print(info.SqlText);
SELECT
	dbo.tbl_staff.name AS"Name",
	dbo.tbl_remuneration.payment_date AS"PaymentDate",
	dbo.tbl_remuneration.money AS"Money"FROM dbo.tbl_remuneration
	JOIN dbo.tbl_staff ON (dbo.tbl_staff.id) = (dbo.tbl_remuneration.staff_id)

TableAttributeとColumnAttributeに対応しました。

それに加えてTableAttribute、ColumnAttributeでも名前を指定できるようにしました。
TableAttribute、ColumnAttributeは System.ComponentModel.DataAnnotations.dll に定義されている標準のものです。
テーブル名とか変数名をラムダ中で短く書きたい場合や、スキーマを一段クラスで表現するのが面倒な場合、それからテーブルやカラムの名前がC#の変数名で使えないものだったりする場合に利用できると思います。

//テーブル名をクラス定義時に指定できる
[Table("tbl_staff")]
publicclass StaffX
{
    //属性の方が優先される
    [Column("id")]
    publicint idx { get; set; }
    [Column("name")]
    publicstring namex { get; set; }
}
publicclass DB
{
    //StaffXは属性でテーブル名を持っているのでそちらが優先されるpublic StaffX xxx { get; set; }
    public Remuneration tbl_remuneration { get; set; }
}
var query = Sql<DataAttr>.Create(db =>
    Select(new
    {
        name = db.xxx.namex,
        payment_date = db.tbl_remuneration.payment_date,
        money = db.tbl_remuneration.money,
    }).
    From(db.tbl_remuneration).
        Join(db.xxx, db.tbl_remuneration.staff_id == db.xxx.idx);

var info = query.ToSqlInfo(_connection.GetType());
Debug.Print(info.SqlText);
SELECT
	tbl_staff.name AS"name",
	tbl_remuneration.payment_date AS"payment_date",
	tbl_remuneration.money AS"money"FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)

LambdicSql - EntityFrameworkと仲良くなりました。

$
0
0

EntityFrameworkを使っているときでも、たまにSQLを直に書きたくなる場合がありますよね。そんな時はLambdicSqlを使うと便利です。そして、EntityFrameworkを使っている環境からLambdicSqlを使いやすい工夫を入れました。しかもそれでいてLambdicSql自体はDLL的にEntityFrameworkに依存したりはしていません。

DataContextクラスを指定できるようにしました。

EntityFrameworkでDBからモデルクラスを生成すると、こんな感じのものが生成されます。(若干省略してます)

publicpartialclass tbl_remuneration
{
    publicint? staff_id { get; set; }
    publicstring payment_date { get; set; }
    publicint id { get; set; }
    publicdecimal? money { get; set; }
}

publicpartialclass tbl_staff
{
    publicint id { get; set; }
    publicstring name { get; set; }
}

publicpartialclass ModelLambdicSqlTestDB : DbContext
{
    publicvirtual DbSet<tbl_remuneration> tbl_remuneration { get; set; }
    publicvirtual DbSet<tbl_staff> tbl_staff { get; set; }
}

これはLambdicSqlの定義に似ています。これをそのまま使えるようにしました。

publicvoid TestEFAndLambdic()
{
    var query = Sql<ModelLambdicSqlTestDB>.Create(db =>
        Select(new
        {
            name = db.tbl_staff.T().name,
            payment_date = db.tbl_remuneration.T().payment_date,
            money = db.tbl_remuneration.T().money,
        }).
        From(db.tbl_remuneration.T()).
            Join(db.tbl_staff, db.tbl_remuneration.T().staff_id == db.tbl_staff.T().id);

    var info = query.ToSqlInfo(_connection.GetType());
    var datas = _connection.Query<SelectData1>(info.SqlText, info.Parameters).ToList();
}

T()がポイントですね。DbSet<>をスルーするものです。次のようなSQLになります。

SELECT
	tbl_staff.name AS"name",
	tbl_remuneration.payment_date AS"payment_date",
	tbl_remuneration.money AS"money"FROM  tbl_remuneration
	JOIN tbl_staff ON (tbl_remuneration.staff_id) = (tbl_staff.id)

上記の例では素直にEFで書けばよいのですが、例えばOR結合を条件によって組み立てるときとか、Window関数を使うときとか、EFでは書きづらいもので利用してもらえばよいと思います。

//ORを任意の条件で組み立てるときとか
var exp = Sql<DB>.Create(db =>
    Condition(minCondition, 3000< db.tbl_remuneration.T().money) ||
    Condition(maxCondition, db.tbl_remuneration.T().money < 4000));
var query1 = Sql<DB>.Create(db => SelectFrom(db.tbl_remuneration.T()).Where(exp));

//Window関数を使いたいときとか
var query2 = Sql<DB>.Create(db =>
    Select(new SelectData()
    {
        Average = Window.Avg(db.tbl_remuneration.T().money).
                Over<decimal>(null,
                    new OrderBy(new Asc(db.tbl_remuneration.T().payment_date)),
                    null)
    }).
    From(db.tbl_remuneration.T()));

名前解決のルールは若干違うので気を付けてください。

LambdicSqlのルールは、こちらです。EntityFrameworkとはテーブル名のルールが違っていますね。EFの場合はクラス名とテーブル名を対応させるルールで、しかも複数形と単数形の解決とか面倒なことをやってますね。とは言え、普通に生成するとEFも変数名とテーブル名は一致します。TableAttributeで指定している場合はLambdicSqlも同じルールで解決するので問題ないですね。問題ある命名している場合は、すみませんがテーブル定義を並べるクラスだけ別途作成お願いします。

是非EntityFrameworkユーザーの方もご利用お願いします!

LambdicSqlはメインでも使えて、脇役に回ってもいい仕事をするライブラリを目指しております。

LambdicSql -主要DBゆるふわ対応-

$
0
0

LambdicSql_α0.0.63をリリースしました。β版間近です!
www.nuget.org

マルチDB対応

なんと主要6DBで動作確認しています。Surfaceに全部インストールしましたが、意外と入るものですね。(SQLiteはインストール不要)

DataBase type動作確認
SQL Server
SQLite
PostgreSQL
Oracle
MySQL
DB2

とは言え、同じ書き方でOKなわけではないです。

この辺がゆるふわ。LambdicSqlは基本はそのままSQLのテキストになります。つまり、普通にSQL書く時と同じように使える句や関数だけ使うという方針です。

publicvoid TestWindow()
{
    //SQLiteとMySqlはWindow関数使えないよ。if (_connection.GetType().FullName == "System.Data.SQLite.SQLiteConnection") return;
    if (_connection.GetType().FullName == "MySql.Data.MySqlClient.MySqlConnection") return;

    //make sql.
    var query = Sql<DB>.Create(db =>
        Select(new SelectData()
        {
            Avg = Window.Avg(db.tbl_remuneration.money).
                    Over<decimal>(new PartitionBy(db.tbl_staff.name, db.tbl_remuneration.payment_date),
                        new OrderBy(new Asc(db.tbl_remuneration.money), new Desc(db.tbl_remuneration.payment_date)),
                        new Rows(1, 5)),
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
        Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id));

    //to string and params.
    var info = query.ToSqlInfo(_connection.GetType());
    Debug.Print(info.SqlText);

    //dapper
    var datas = _connection.Query<SelectData>(info.SqlText, info.Params).ToList();
}

オプションで、以下を切り替えれるようにしました。

じゃあ、何がマルチDB対応なんだよってことなのですが、以下の二つだけオプションで切り替えれるようにしています。こればっかりは書き分けられないので・・・。

  • 文字列の+演算
  • パラメータのプレフィックス
[TestMethod]
publicvoid Test()
{
    var query = Sql<Data>.Create(db =>
        Select(new
        {
            Name = db.tbl_staff.name + "★",
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_staff.id == db.tbl_remuneration.staff_id).
        Where(1000000< db.tbl_remuneration.money));

    //文字列化するときにオプションを指定する
    var info = query.ToSqlInfo(new SqlConvertOption() { ParameterPrefix = ":", StringAddOperator = "||" });
    Debug.Print(info.SqlText);
}
SELECT
	/*文字列結合の演算子に||を指定*/
	(tbl_staff.name) || (:p_0) AS Name,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_staff.id) = (tbl_remuneration.staff_id)
/*オラクルは@使えない*/
WHERE (:p_1) < (tbl_remuneration.money)

なんで、こんなとこに差だすかなー。オラクルで@使ったらダメとか、わからなくて結構悩みました。

DataBase type文字列結合パラメータプレフィックス
SQL Server+@
SQLite||@
PostgreSQL||@
Oracle||:
MySQL+@
DB2||@

これを毎回指定するのは面倒なんで、コネクション(SqlConnectionとか)のタイプを渡すと最適なオプションを選択するようにしています。

//コネクションタイプを渡すと、最適なオプションを選択します。
var info = query.ToSqlInfo(connection.GetType());

同一の記法でSQLテキストをカスタマイズする方法も提供しています。

長くなるので端折りますが、ToSqlInfoの第三引数にテキスト出力をカスタマイズするインターフェイスを渡せるようにしています。どうしてもやりたい人はこちらでできるように。また句や関数を自分で増やせる手段も提供しています。(拡張オレオレ句も作れる)

//第三引数でカスタマイズインターフェイスを渡せる仕様にしてます。publicstatic SqlInfo ToSqlInfo(this ISqlExpression exp, SqlConvertOption option, ISqlSyntaxCustomizer customizer)

SQLWorld★大阪#38 に参加してきました。

$
0
0

SQL力を高めるべく、SqlWorld :: SQLWorld★大阪#38に参加してきました!
簡単なものから高度すぎるものまで、レンジの広い問題が出題されました。
で、その中でこんな問題がありました。
友達の人数を抽出するというものです。
f:id:ishikawa-tatsuya:20160825000048p:plain
で、私はこんなSQLを書きました。

SELECT人.名前,
       isnull(友達人数.人数, 0) AS人数
FROM人
       LEFT OUTER JOIN
       (SELECT友達一次元.ID AS ID,
                 sum(人数) AS人数
        FROM     (SELECT人ID1 AS ID,
                           count(*) AS人数
                  FROM友達
                  GROUPBY人ID1
                  UNIONSELECT人ID2 AS ID,
                           count(*) AS人数
                  FROM友達
                  GROUPBY人ID2) AS友達一次元
        GROUPBY友達一次元.ID) AS友達人数
       ON人.ID = 友達人数.ID;

LambdicSqlで書いてみます

まあ、サブクエリの入れ子とか複雑ですよね。これをLambdicSqlで書いてみようと思います。(実はもっと良い書き方を主催のおださんから教えていただいたのですが、LambdicSqlの題材としてはこっちの方がよかったので)
LambdicSqlは順に組み立てられるのでこんなとき便利です。
最後のToSqlInfoでがSQL文字列が作成されます。

//テーブルの定義class人
{
    publicint ID { get; set; }
    publicstring名前 { get; set; }
}
class友達
{
    publicint人ID1 { get; set; }
    publicint人ID2 { get; set; }
}
class DB
{
    public人 人 { get; set; }
    public友達 友達 { get; set; }
}

[TestMethod]
publicvoid TestSqlWorld()
{
    //①友達テーブルの人ID1の列で友達の数を数えるクエリ
    var 人数1 = Sql<DB>.Create(db =>
        Select(new
        {
            ID = db.友達.人ID1,
            人数 = Count<int>(new Asterisk())
        }).
        From(db.友達).
        GroupBy(db.友達.人ID1));

    //②友達テーブルの人ID2の列で友達の数を数えるクエリ
    var 人数2 = Sql<DB>.Create(db =>
        Select(new
        {
            ID = db.友達.人ID2,
            人数 = Count<int>(new Asterisk())
        }).
        From(db.友達).
        GroupBy(db.友達.人ID2));

    //③合算して一列のテーブルにするクエリ
    var 友達一次元 = 人数1.Union(人数2);

    //④IDでグルーピングして人数を数える
    var 友達人数 = Sql<DB>.Create(db =>
    Select(new
    {
        ID = 友達一次元.Body.ID,
        人数 = Sum(友達一次元.Body.人数)
    }).
    From(友達一次元).
    GroupBy(友達一次元.Body.ID));

    //⑤クエリ完成//名前を付けるクエリ
    var 友達人数_名前付き = Sql<DB>.Create(db =>
        Select(new
        {
            名前 = db.人.名前,
            人数 = IsNull(友達人数.Body.人数, 0)
        }).
        From(db.人).
        LeftJoin(友達人数, db.人.ID == 友達人数.Body.ID)
    );

    //文字列とパラメータの取得//後はDapperへ
    var info = 友達人数_名前付き.ToSqlInfo(typeof(SqlConnection));
    Debug.Print(info.SqlText);
}

こんなSQLになります。

SELECT人.名前 AS名前,
	ISNULL(友達人数.人数, @p_1) AS人数
FROM人
	LEFT JOIN 
	(SELECT友達一次元.ID AS ID,
		SUM(友達一次元.人数) AS人数
	FROM 
		(SELECT友達.人ID1 AS ID,
			COUNT(*) AS人数
		FROM友達
		GROUPBY友達.人ID1
		UNIONSELECT友達.人ID2 AS ID,
			COUNT(*) AS人数
		FROM友達
		GROUPBY友達.人ID2) 友達一次元
	GROUPBY友達一次元.ID) 友達人数 ON (人.ID) = (友達人数.ID)

履歴

2016/09/02 β版対応

Viewing all 104 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>