dotnet / dotnet/efcore

Consider removing the CAST to BIT for boolean literal values

Open
#27,150 14 comments 13 reactions 0 assignees View on GitHub
area-perf area-query area-sqlserver customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

Our SQL representation of boolean literal values is `CAST(1 AS BIT)`. However, in at least some cases, the CAST produces an inferior query plan. The following example shows this when calling hierarchyid's IsDescendantOf:

### Hierarchyid plan without CAST

```sql
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = 1;
```

Plan:

```
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = 1,1,1,0,,,1,,1,,,,4.456616,,,SELECT,false,
" |--Compute Scalar(DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1003],0)))",1,2,1,Compute Scalar,Compute Scalar,"DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1003],0))","[Expr1002]=CONVERT_IMPLICIT(int,[Expr1003],0)",1,0,0,11,4.456616,[Expr1002],,PLAN_ROW,false,1
|--Stream Aggregate(DEFINE:([Expr1003]=Count(*))),1,3,2,Stream Aggregate,Aggregate,,[Expr1003]=Count(*),1,0,0.6000005,11,4.456616,[Expr1003],,PLAN_ROW,false,1
" |--Index Seek(OBJECT:([master].[dbo].[Data].[IX_name]), SEEK:([master].[dbo].[Data].[Hid] >= / AND [master].[dbo].[Data].[Hid] <= Showplan: failed to convert to string from hierarchyid) ORDERED FORWARD)",1,4,3,Index Seek,Index Seek,"OBJECT:([master].[dbo].[Data].[IX_name]), SEEK:([master].[dbo].[Data].[Hid] >= / AND [master].[dbo].[Data].[Hid] <= Showplan: failed to convert to string from hierarchyid) ORDERED FORWARD",,1000000,2.7564583,1.100157,9,3.8566153,,,PLAN_ROW,false,1
```

### Hierarchyid plan with CAST

```sql
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = CAST(1 AS bit);
```

Plan:

```
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = CAST(1 AS bit),1,1,0,,,1,,1,,,,4.6366158,,,SELECT,false,
" |--Compute Scalar(DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1004],0)))",1,2,1,Compute Scalar,Compute Scalar,"DEFINE:([Expr1002]=CONVERT_IMPLICIT(int,[Expr1004],0))","[Expr1002]=CONVERT_IMPLICIT(int,[Expr1004],0)",1,0,0,11,4.6366158,[Expr1002],,PLAN_ROW,false,1
|--Stream Aggregate(DEFINE:([Expr1004]=Count(*))),1,3,2,Stream Aggregate,Aggregate,,[Expr1004]=Count(*),1,0,0.6000005,11,4.6366158,[Expr1004],,PLAN_ROW,false,1
|--Filter(WHERE:([master].[dbo].[Data].[Hid].IsDescendantOf(/)=(1))),1,4,3,Filter,Filter,WHERE:([master].[dbo].[Data].[Hid].IsDescendantOf(/)=(1)),,1000000,0,0.18,9,4.0366154,,,PLAN_ROW,false,1
|--Index Scan(OBJECT:([master].[dbo].[Data].[IX_name])),1,5,4,Index Scan,Index Scan,OBJECT:([master].[dbo].[Data].[IX_name]),[master].[dbo].[Data].[Hid],1000000,2.7564583,1.100157,13,3.8566153,[master].[dbo].[Data].[Hid],,PLAN_ROW,false,1
```

### Test SQL

hierarchyid test

```sql
CREATE TABLE Data (Id INT IDENTITY(1,1) PRIMARY KEY, Hid HIERARCHYID);
CREATE INDEX IX_name ON Data(Hid);

BEGIN TRANSACTION;

DECLARE @i INT = 0;
WHILE @i < 50000
BEGIN
INSERT INTO Data (Hid) VALUES ('/1/3/');
SET @i = @i + 1;
END;
COMMIT;

SET SHOWPLAN_ALL ON;
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = 1;
SELECT COUNT(*) FROM Data WHERE Hid.IsDescendantOf('/') = CAST(1 AS bit);
SET SHOWPLAN_ALL OFF;
```

Note that in other scenarios, e.g. simply comparing a regular BIT column to literal true/false, the degradation does not occur (this could be specific to hierarchyid, though who knows):

Regular column test

```sql
CREATE TABLE Data (Id INT IDENTITY(1,1) PRIMARY KEY, IsSomething BIT);
CREATE INDEX IX_name ON Data(IsSomething);

BEGIN TRANSACTION;

DECLARE @i INT = 0;
WHILE @i < 500000
BEGIN
INSERT INTO Data (IsSomething) VALUES (1);
SET @i = @i + 1;
END;

WHILE @i < 1000000
BEGIN
INSERT INTO Data (IsSomething) VALUES (0);
SET @i = @i + 1;
END;

COMMIT;

SET SHOWPLAN_ALL ON;
SELECT COUNT(*) FROM Data WHERE IsSomething = 1;
SELECT COUNT(*) FROM Data WHERE IsSomething = CAST(1 AS bit);
SET SHOWPLAN_ALL OFF;
```

Flagged by @diogonborges in https://github.com/dotnet/efcore/issues/23472#issuecomment-1007618566

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.