.NET Conf関西 2019 で .NET + SQL Server Tips というお題で話してきました

.NET Conf関西 2019 - connpass

資料はこちらですが、デモ中心なので資料だけ見てもアレかも。

主に System(Microsoft).Data.Sqlclient 名前空間のクラスを使った話しが主なので、興味ない方も居られたと思います。
が、半分くらいの方は紹介した中で知らなかった事があったようなので良かったかな。

.NET Core の SqlConnection の ConnectRetryCount ちゃんと動いてない?

検証コード
.NET Core SqlConnection ConnectRetryCount not work? · GitHub

.NET Framework 4.7.2 + System.Data.SqlClient 4.7.0 と
.NET Core 3.0 + System.Data.SqlClient 4.7.0 で
同じコード動かしたけど、例外が発生する時間が違う。。
なんやろ?

2019/11/16 追記
Fix connection resiliency check by David-Engel · Pull Request #310 · dotnet/SqlClient · GitHub で修正されました。

.NET Conf関西 2019 で話しします

来週 10/19(土) に、.NET Conf関西 2019 - connpass
.NET + SQL Server Tips というタイトルでお話しします。

Tips なので既に知ってるという方も居られるかもしれませんが、
何人の方にはへーっと思って頂ければと思っています。

満席間近みたいなので、参加しようと思っている方はお早めに~。

varchar 列に nvarchar 渡しても convert_implicit しなくなってた

varchar と nvarchar の列持つテーブルにそれぞれ index つけて適当にデータ突っ込んで試したら、動作変わってた。

create table [Hoge] (
  [var] varchar(20) not null,
  [nvar] nvarchar(20) not null
)
create index [Hoge_var] on [Hoge] ([var])
create index [Hoge_nvar] on [Hoge] ([nvar])

;
with cte as (select 1 as cnt union all select cnt + 1 from cte where cnt < 100000)
insert into [Hoge] ([var], [nvar])
select cnt, 100000-cnt from cte option (MAXRECURSION 0)

Azure SQL DB
(Microsoft SQL Azure (RTM) - 12.0.2000.8)

declare @nvar nvarchar(20) = N'111'
declare @var varchar(20) = '111'


select * from [Hoge] where [var] = @nvar
select * from [Hoge] where [nvar] = @nvar
select * from [Hoge] where [var] = @var
select * from [Hoge] where [nvar] = @var

f:id:odashinsuke:20191006113548j:plain

一番上のプランの動作変わってんなーってとこ。
一番上は、昔は [var] 列が convert_implicit で nvarchar になってテーブルスキャンになってたけど、実行プランの xml みたら、GetRangeThroughConvert 使って範囲検索になってた。

SQL Server 2012 の環境あったから、同じクエリ試したけどやっぱ変わってた。
(Microsoft SQL Server 2012 - 11.0.5569.0 (X64))
f:id:odashinsuke:20191006115026p:plain

どのバージョンから変わったのかまでは調べてないけど、たまには色々試してみないとダメやね~。

テーブルを条件にいれた Index Scan なプランを取得する

必要になったので書くつもりが、良いの見つけたのでちょっとだけ改変。
元ネタ:Clustered Index Scan のプランを抽出する at SE の雑記

WITH XMLNAMESPACES
('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
select
  * 
from (
SELECT
RO.C.value(N'@LogicalOp','varchar(20)') as LogicalOp
, text
, query_plan
, creation_time
--, RO.C.value(N'@EstimateRows','numeric(20,5)') as EstimateRows
, min_elapsed_time
, max_elapsed_time
, min_logical_reads
, max_logical_reads
, min_logical_writes
, max_logical_writes
, RO.C.value(N'(./sp:IndexScan/sp:Object/@Table)[1]', 'nvarchar(100)') as TableName
FROM
sys.dm_exec_query_stats
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
CROSS APPLY query_plan.nodes(N'//sp:RelOp') as RO(C)
WHERE
RO.C.value(N'@LogicalOp','varchar(20)') like '%Index Scan'
) data_
where TableName = N'[テーブル名]'

09/10(火) SQLWorld★大阪#51 開催します #sqlworld

SqlWorld :: SQLWorld★大阪#51 開催します!
32回目の平日夜開催で、前回同様 ハンズオン 形式で行う予定です。

【日時】
2019年09月10日(火曜日) 19:00~21:00
 

【イベント概要】
SQLWorld、今回で33回目の平日夜開催~。今回も、みんなで SQL を書いてみようというハンズオン企画です!ブラウザがあれば参加出来るようにしていますので、iPad 等のタブレットでも大丈夫です。
 

【会場】
フェンリル株式会社さま大阪本社 http://www.fenrir-inc.com/
〒530-0011 大阪府大阪市北区大深町 3番1号 グランフロント大阪タワーB(オフィス)
 

【参加費】
無料
 

【持ち物】
パソコン/タブレット (DB のインストールは不要です。)
 

【参加可能人数】
13 人
 

お題に沿って、SQL を書いてみようという勉強会です。是非ご参加を~。
開催回数は増えていっていますが、続き物というわけでは無いので初めて参加される方でもお気軽にどぞー。

LambdicSql で bit型(bool) をリテラルで指定する場合の対処法

LambdicSql でリテラルをパラメータ化したくない場合は DirectValue か N 使おう - お だ のスペース
で、DirectValue 使おうねーって書いたけど、bit型のカラム(.NET の型だと bool) は DirectValue をそのまま使うとエラーなクエリが吐かれます。

using LambdicSql;
using System;
using System.Data.SqlClient;
using static LambdicSql.SqlServer.Symbol;

public class table
{
  public int foo { get; set; }
  public int bar { get; set; }
  public bool IsValid { get; set; }
}
public class DB
{
  public table table { get; set; }
}

class Program
{

  static void Main(string[] args)
  {
    var query = Db<DB>.Sql(db => Select(new table
    {
      foo = db.table.foo,
      bar = db.table.bar
    }).
    From(db.table).
    Where(db.table.IsValid == true.DirectValue()));

    var qs = query.Build(typeof(SqlConnection)).Text;

    Console.WriteLine(qs);
/*
SELECT
   table.foo AS foo,
   table.bar AS bar
FROM table
WHERE table.IsValid = True
*/
  }
}

なクエリが吐かれてこのまま実行すると、

列名 'True' が無効です。

となってしまいます。
bit (Transact-SQL) - SQL Server | Microsoft Docs
bit 型の場合、0 (false) か 0以外 (true) という扱いなので、取り合えず 0 か 1 を渡したら良さげです。
※文字列 'TRUE'、'FALSE' でもクエリエラーにはなりませんが、前回も書いた暗黙の型変換(convert_implicit)が発生します。
では、table.IsValid == 1.DirectValue() と書きたいけどこうすると bool型 == int型 になってコンパイルエラーとなります。
なので、1.DirectValue() の結果を bool にキャストします。これも int型 を bool型にそのままキャスト出来ないのでobject型を経由します。

class Program
{

  static void Main(string[] args)
  {
    var query = Db<DB>.Sql(db => Select(new table
    {
      foo = db.table.foo,
      bar = db.table.bar
    }).
    From(db.table).
    Where(db.table.IsValid == (bool)(object)1.DirectValue()));

    var qs = query.Build(typeof(SqlConnection)).Text;

    Console.WriteLine(qs);
/*
SELECT
   table.foo AS foo,
   table.bar AS bar
FROM table
WHERE table.IsValid = 1
*/
  }
}

めんどくさー。