次の方法で共有


クエリにおけるnullの意味論

はじめに

SQL データベースは、C# のブールロジックではなく、比較を実行するときに 3 値ロジック (truefalsenull) で動作します。 LINQ クエリを SQL に変換する場合、EF Core はクエリの一部の要素に対して追加の null チェックを導入することで、その違いを補正しようとします。 これを説明するために、次のエンティティを定義しましょう。

public class NullSemanticsEntity
{
    public int Id { get; set; }
    public int Int { get; set; }
    public int? NullableInt { get; set; }
    public string String1 { get; set; }
    public string String2 { get; set; }
}

いくつかのクエリを発行します。

var query1 = context.Entities.Where(e => e.Id == e.Int);
var query2 = context.Entities.Where(e => e.Id == e.NullableInt);
var query3 = context.Entities.Where(e => e.Id != e.NullableInt);
var query4 = context.Entities.Where(e => e.String1 == e.String2);
var query5 = context.Entities.Where(e => e.String1 != e.String2);

最初の 2 つのクエリでは、単純な比較が生成されます。 最初のクエリでは、両方の列が null 非許容であるため、null チェックは必要ありません。 2 番目のクエリでは、 NullableIntnullが含まれる可能性がありますが、 Id は null 非許容です。結果として、 null と null 以外の結果を比較すると null が生成され、 WHERE 操作によって除外されます。 そのため、追加の用語も必要ありません。

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[Int]

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[Id] = [e].[NullableInt]

3 番目のクエリでは、null チェックが導入されています。 NullableIntnullのとき、比較Id <> NullableIntによりnullが得られます。これがWHERE操作で除去されます。 ただし、ブールロジックの観点からは、このケースは結果の一部として返される必要があります。 そのため、EF Core では、これを確認するために必要なチェックが追加されます。

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[Id] <> [e].[NullableInt]) OR [e].[NullableInt] IS NULL

クエリ 4 と 5 は、両方の列が null 許容の場合のパターンを示します。 <>操作では、==操作よりも複雑な (および低速になる可能性がある) クエリが生成される点に注目してください。

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] = [e].[String2]) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL)

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE (([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)

関数での null 許容値の処理

SQL の多くの関数は、引数の一部がnullされている場合にのみnull結果を返すことができます。 EF Core はこれを利用して、より効率的なクエリを生成します。 次のクエリは、最適化を示しています。

var query = context.Entities.Where(e => e.String1.Substring(0, e.String2.Length) == null);

生成される SQL は次のとおりです ( SUBSTRING 関数を評価する必要はありません。これは、いずれかの引数が null の場合にのみ null になるためです)。

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE [e].[String1] IS NULL OR [e].[String2] IS NULL

最適化は、ユーザー定義関数にも使用できます。 詳細については、 ユーザー定義関数のマッピング に関するページを参照してください。

パフォーマンスの高いクエリの記述

  • NULL 非許容列の比較は、NULL 許容列を比較するよりも簡単で高速です。 可能な限り、列を null 非許容としてマークすることを検討してください。

  • 等値 (==) のチェックは、クエリで!=nullの結果を区別する必要がないため、非等値 (false) をチェックするよりも簡単で高速です。 可能な限り等値比較を使用してください。 ただし、 == 比較を否定するだけでは、実質的に !=と同じであるため、パフォーマンスが向上することはありません。

  • 場合によっては、列から null 値を明示的に除外することで、複雑な比較を簡略化できます。たとえば、 null 値が存在しない場合や、これらの値が結果に関連しない場合などです。 次の例を確認してください。

var query1 = context.Entities.Where(e => e.String1 != e.String2 || e.String1.Length == e.String2.Length);
var query2 = context.Entities.Where(
    e => e.String1 != null && e.String2 != null && (e.String1 != e.String2 || e.String1.Length == e.String2.Length));

これらのクエリにより、次の SQL が生成されます。

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ((([e].[String1] <> [e].[String2]) OR ([e].[String1] IS NULL OR [e].[String2] IS NULL)) AND ([e].[String1] IS NOT NULL OR [e].[String2] IS NOT NULL)) OR ((CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)) OR ([e].[String1] IS NULL AND [e].[String2] IS NULL))

SELECT [e].[Id], [e].[Int], [e].[NullableInt], [e].[String1], [e].[String2]
FROM [Entities] AS [e]
WHERE ([e].[String1] IS NOT NULL AND [e].[String2] IS NOT NULL) AND (([e].[String1] <> [e].[String2]) OR (CAST(LEN([e].[String1]) AS int) = CAST(LEN([e].[String2]) AS int)))

2 番目のクエリでは、 null 結果は明示的 String1 列から除外されます。 EF Core では、比較中に String1 列を null 非許容として安全に処理できるため、クエリが簡単になります。

リレーショナル ヌル セマンティクスの使用

null 比較補正を無効にし、リレーショナル null セマンティクスを直接使用することができます。 これを行うには、 UseRelationalNulls(true) メソッド内のオプション ビルダーで OnConfiguring メソッドを呼び出します。

new SqlServerDbContextOptionsBuilder(optionsBuilder).UseRelationalNulls();

Warnung

リレーショナル null セマンティクスを使用する場合、LINQ クエリは C# と同じ意味を持たなくなり、予想とは異なる結果が得られる可能性があります。 このモードを使用する場合は注意が必要です。