Доклад с таким длинным и непонятным названием я читал на SQL Server User Group 10 сентября в Москве. Ниже слайды запись доклада:
Простой тест:

Как видите план оказывается далеко не оптимальным в обоих случаях.
А с помощью Linq можно написать так:
Более того, такая оптимизация для null value встроена в провайдер Linq2DB. Там можно непосредственно nullable значения подставлять в linq.
Более того, можно использовать filtered index, когда больше 2% значений по индексируемому полю равны NULL.
К сожалению, как обычно я не показал все что хотел, часть материала не попала на видео запись. Но я восполню этот недостаток.
Как вы думаете, можно ли на Linq делать запросы, которые работают быстрее рукопашных? Оказывается да, и очень просто.
Например надо сделать функцию, которая отбирает заказы по дате отгрузки. Если параметр указал, то выбрать заказы за эту дату. А если не указана дата, то выбрать все заказы, у которых дата отгрузки пустая. Обычный разработчик напишет такую процедуру:
CREATE PROCEDURE [dbo].[GetTransactionsByShipDate] @shipDate datetime AS SELECT t.Id, t.ProductId, t.TransactionDate from Transactions t where (@shipDate is not null and t.ShippedDate = @shipDate) or (@shipDate is null and t.ShippedDate is null)Эта процедура подвержена parameter sniffing problem. Проблема заключается в том, что план процедуры генерируется один раз при первом вызове с учетом фактических параметров при вызове. Если при первом вызове ShipDate был NULL (низкая селективность), то сгенерируется план с Index Scan. Если же первый вызов был с конкретным значением даты, то получится Index Seek, который будет неэффективно работать для значений с низкой селективностью.
Простой тест:
DBCC FREEPROCCACHE GO EXEC [dbo].[GetTransactionsByShipDate] NULL GO declare @shipdate datetime = getdate() EXEC [dbo].[GetTransactionsByShipDate] @shipdate GO DBCC FREEPROCCACHE GO declare @shipdate datetime = getdate() EXEC [dbo].[GetTransactionsByShipDate] @shipdate GO EXEC [dbo].[GetTransactionsByShipDate] NULL GOРезультаты:

Как видите план оказывается далеко не оптимальным в обоих случаях.
А с помощью Linq можно написать так:
private static IQueryable<Transaction> GetValues( IQueryable<Transaction> query, DateTime? dateTime) { if (dateTime.HasValue) { return query.Where(t => t.ShippedDate == dateTime.Value); } else { return query.Where(t => t.ShippedDate == null); } }Тогда буду сгенерированы два разных запроса, каждый со своим, оптимальным для данного запроса, планом.
Более того, такая оптимизация для null value встроена в провайдер Linq2DB. Там можно непосредственно nullable значения подставлять в linq.
Более того, можно использовать filtered index, когда больше 2% значений по индексируемому полю равны NULL.