Schema binding seems to not be enforced for view doing temporal query

%3CLINGO-SUB%20id%3D%22lingo-sub-2669046%22%20slang%3D%22en-US%22%3ESchema%20binding%20seems%20to%20not%20be%20enforced%20for%20view%20doing%20temporal%20query%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-2669046%22%20slang%3D%22en-US%22%3E%3CP%3EOriginally%20posted%20on%20stackexchange%20(%3CA%20href%3D%22https%3A%2F%2Fdba.stackexchange.com%2Fquestions%2F287616%2Fschema-binding-seems-to-not-be-enforced-for-view-doing-temporal-query%22%20target%3D%22_blank%22%20rel%3D%22nofollow%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Fdba.stackexchange.com%2Fquestions%2F287616%2Fschema-binding-seems-to-not-be-enforced-for-view-doing-temporal-query%3C%2FA%3E)%2C%20languished%20in%20user%20voice%20for%20a%20while%20before%20that%20was%20shut%20down%20(%3CA%20href%3D%22https%3A%2F%2Ffeedback.azure.com%2Fforums%2F908035-sql-server%2Fsuggestions%2F43011810-schema-binding-seems-to-not-be-enforced-for-view-d%22%20target%3D%22_blank%22%20rel%3D%22nofollow%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Ffeedback.azure.com%2Fforums%2F908035-sql-server%2Fsuggestions%2F43011810-schema-binding-seems-to-not-be-enforced-for-view-d%3C%2FA%3E)%2C%20now%20manually%20migrated%20to%20this%20site.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EI%20am%20using%20Microsoft%20SQL%20Server%202019.%20There%20are%20two%20strange%20behaviors%20that%20I%20don't%20see%20documented%20(if%20it%20is%2C%20please%20point%20me%20to%20the%20Microsoft%20docs%20link)%2C%20I%20suspect%20they're%20related.%3C%2FP%3E%3CP%3E1.%20The%20INFORMATION_SCHEMA.VIEW_COLUMN_USAGE%20system%20view%20does%20not%20show%20information%20about%20view%20queries%20that%20do%20temporal%20queries.%3C%2FP%3E%3CP%3E2.%20If%20I%20create%20a%20view%20which%20does%20a%20temporal%20query%2C%20schema%20binding%20is%20not%20enforced.%20I%20can%20alter%20the%20columns%20on%20the%20underlying%20table%20that%20the%20view's%20query%20uses.%20Normally%2C%20creating%20the%20view%20with%20the%20%22WITH%20SCHEMABINDING%22%20option%20would%20cause%20such%20attempts%20to%20alter%20the%20table%20to%20fail.%3C%2FP%3E%3CP%3ETry%20the%20sample%20code%20below%20to%20see%20that%20behavior.%20I%20added%20%22!!%22%20in%20the%20comments%20at%20the%20spots%20where%20things%20did%20not%20work%20out%20as%20I%20expected%20them%20to.%3C%2FP%3E%3CP%3EMy%20main%20question%3A%20is%20this%20a%20bug%20or%20is%20this%20expected%20behavior%3F%3C%2FP%3E%3CP%3E-----------------------------------------------------------------%3CBR%20%2F%3E%3CBR%20%2F%3E--cleanup%20from%20past%20run%3CBR%20%2F%3EDROP%20VIEW%20IF%20EXISTS%20%5BvwTemporalTest_doingTemporalQuery%5D%3CBR%20%2F%3EDROP%20VIEW%20IF%20EXISTS%20%5BvwTemporalTest_noTemporalQuery%5D%3B%3CBR%20%2F%3EIF%20EXISTS%20(SELECT%20*%20FROM%20INFORMATION_SCHEMA.TABLES%20WHERE%20TABLE_NAME%20%3D%20'TemporalTest')%3CBR%20%2F%3EBEGIN%3CBR%20%2F%3EALTER%20TABLE%20%5BTemporalTest%5D%20SET%20(SYSTEM_VERSIONING%20%3D%20OFF)%3B%3CBR%20%2F%3EEND%3CBR%20%2F%3EDROP%20TABLE%20IF%20EXISTS%20%5BTemporalTest%5D%3B%3CBR%20%2F%3EDROP%20TABLE%20IF%20EXISTS%20%5BTemporalTestHistory%5D%3B%3CBR%20%2F%3EGO%3CBR%20%2F%3E%3CBR%20%2F%3E--create%20the%20temporal%20table%3CBR%20%2F%3ECREATE%20TABLE%20%5BTemporalTest%5D%3CBR%20%2F%3E(%3CBR%20%2F%3E%5BId%5D%20%5Bint%5D%20NOT%20NULL%2C%3CBR%20%2F%3E%5BName%5D%20nvarchar(500)%20NOT%20NULL%2C%3CBR%20%2F%3E%5BDescription%5D%20nvarchar(100)%20NULL%2C%3CBR%20%2F%3E%5Bperiod_start%5D%20%5Bdatetime2%5D(7)%20GENERATED%20ALWAYS%20AS%20ROW%20START%20NOT%20NULL%2C%3CBR%20%2F%3E%5Bperiod_end%5D%20%5Bdatetime2%5D(7)%20GENERATED%20ALWAYS%20AS%20ROW%20END%20NOT%20NULL%2C%3CBR%20%2F%3EPERIOD%20FOR%20SYSTEM_TIME%20(%5Bperiod_start%5D%2C%20%5Bperiod_end%5D)%2C%3CBR%20%2F%3EPRIMARY%20KEY%20CLUSTERED%20(%5BId%5D)%3CBR%20%2F%3E)%20ON%20%5BPRIMARY%5D%3CBR%20%2F%3EWITH(SYSTEM_VERSIONING%20%3D%20ON%20(HISTORY_TABLE%20%3D%20%5Bdbo%5D.%5BTemporalTestHistory%5D%20%2C%20DATA_CONSISTENCY_CHECK%20%3D%20ON%20))%3CBR%20%2F%3EGO%3CBR%20%2F%3E%3CBR%20%2F%3E--insert%20some%20data%3CBR%20%2F%3EINSERT%20INTO%20%5BTemporalTest%5D%20(%5BId%5D%2C%5BName%5D)%20VALUES%3CBR%20%2F%3E(1%2C%20'Alice')%2C%3CBR%20%2F%3E(2%2C%20'Bob')%3B%3CBR%20%2F%3E--just%20do%20an%20update%20so%20some%20history%20row%20gets%20populated%3CBR%20%2F%3EUPDATE%20%5BTemporalTest%5D%20SET%20%5BName%5D%20%3D%20'Bobby'%2C%20%5BDescription%5D%20%3D%20'blah'%20WHERE%20%5BId%5D%20%3D%202%3B%3CBR%20%2F%3EGO%3CBR%20%2F%3E%3CBR%20%2F%3E--create%20the%20views%20(first%20one%20won't%20do%20a%20temporal%20query%2C%20second%20one%20will%20do%20a%20temporal%20query)%3CBR%20%2F%3E--%20and%20they'll%20reference%20different%20columns%20to%20showcase%20the%20issue%20(first%20one%20uses%20Name%2C%20second%20uses%20Description)%3CBR%20%2F%3ECREATE%20VIEW%20%5BvwTemporalTest_noTemporalQuery%5D%3CBR%20%2F%3EWITH%20SCHEMABINDING%3CBR%20%2F%3EAS%20SELECT%20%5BId%5D%2C%20%5BName%5D%20FROM%20%5Bdbo%5D.%5BTemporalTest%5D%3B%3CBR%20%2F%3EGO%3CBR%20%2F%3ECREATE%20VIEW%20%5BvwTemporalTest_doingTemporalQuery%5D%3CBR%20%2F%3EWITH%20SCHEMABINDING%3CBR%20%2F%3EAS%20SELECT%20%5BId%5D%2C%20%5BDescription%5D%2C%20%5Bperiod_start%5D%2C%20%5Bperiod_end%5D%20FROM%20%5Bdbo%5D.%5BTemporalTest%5D%20FOR%20SYSTEM_TIME%20ALL%3B%3CBR%20%2F%3EGO%3CBR%20%2F%3E%3CBR%20%2F%3E--test%20out%20the%20views%3CBR%20%2F%3ESELECT%20*%20FROM%20%5BvwTemporalTest_noTemporalQuery%5D%3B%20--returns%202%20current%20rows%3CBR%20%2F%3ESELECT%20*%20FROM%20%5BvwTemporalTest_doingTemporalQuery%5D%3B%20--returns%203%20rows%20(including%201%20historical%20row)%3CBR%20%2F%3E%3CBR%20%2F%3E--look%20at%20information%20about%20the%20views%3CBR%20%2F%3Eselect%20*%20from%20INFORMATION_SCHEMA.VIEW_COLUMN_USAGE%20where%20View_Name%20%3D%20'vwTemporalTest_noTemporalQuery'%20--returns%202%20rows%20(one%20for%20each%20column%20used)%3CBR%20%2F%3Eselect%20*%20from%20INFORMATION_SCHEMA.VIEW_COLUMN_USAGE%20where%20View_Name%20%3D%20'vwTemporalTest_doingTemporalQuery'%20--!!returns%20no%20rows%20(I%20expected%204%20rows%20at%20least%20since%20the%20view%20uses%204%20columns)%3CBR%20%2F%3E%3CBR%20%2F%3E%2F*%3CBR%20%2F%3E--now%20verify%20schema%20binding%20prevents%20the%20underlying%20table%20from%20being%20altered%3CBR%20%2F%3EALTER%20TABLE%20%5BTemporalTest%5D%20ALTER%20COLUMN%20%5BName%5D%20nvarchar(600)%20NOT%20NULL%3B%20--fails%20as%20expected%20-%20The%20object%20'vwTemporalTest_noTemporalQuery'%20is%20dependent%20on%20column%20'Name'.%3CBR%20%2F%3EALTER%20TABLE%20%5BTemporalTest%5D%20ALTER%20COLUMN%20%5BDescription%5D%20nvarchar(255)%20NULL%3B%20--!!unexpectedly%20succeeds.%20I%20expected%20it%20to%20fail%20because%20vwTemporalTest_doingTemporalQuery%20uses%20the%20Description%20column.%3CBR%20%2F%3E*%2F%3C%2FP%3E%3CP%3E-----------------------------------------------------------------%3C%2FP%3E%3C%2FLINGO-BODY%3E
New Contributor

Originally posted on stackexchange (https://dba.stackexchange.com/questions/287616/schema-binding-seems-to-not-be-enforced-for-view-doin...), languished in user voice for a while before that was shut down (https://feedback.azure.com/forums/908035-sql-server/suggestions/43011810-schema-binding-seems-to-not...), now manually migrated to this site.

 

I am using Microsoft SQL Server 2019. There are two strange behaviors that I don't see documented (if it is, please point me to the Microsoft docs link), I suspect they're related.

1. The INFORMATION_SCHEMA.VIEW_COLUMN_USAGE system view does not show information about view queries that do temporal queries.

2. If I create a view which does a temporal query, schema binding is not enforced. I can alter the columns on the underlying table that the view's query uses. Normally, creating the view with the "WITH SCHEMABINDING" option would cause such attempts to alter the table to fail.

Try the sample code below to see that behavior. I added "!!" in the comments at the spots where things did not work out as I expected them to.

My main question: is this a bug or is this expected behavior?

-----------------------------------------------------------------

--cleanup from past run
DROP VIEW IF EXISTS [vwTemporalTest_doingTemporalQuery]
DROP VIEW IF EXISTS [vwTemporalTest_noTemporalQuery];
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'TemporalTest')
BEGIN
ALTER TABLE [TemporalTest] SET (SYSTEM_VERSIONING = OFF);
END
DROP TABLE IF EXISTS [TemporalTest];
DROP TABLE IF EXISTS [TemporalTestHistory];
GO

--create the temporal table
CREATE TABLE [TemporalTest]
(
[Id] [int] NOT NULL,
[Name] nvarchar(500) NOT NULL,
[Description] nvarchar(100) NULL,
[period_start] [datetime2](7) GENERATED ALWAYS AS ROW START NOT NULL,
[period_end] [datetime2](7) GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME ([period_start], [period_end]),
PRIMARY KEY CLUSTERED ([Id])
) ON [PRIMARY]
WITH(SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[TemporalTestHistory] , DATA_CONSISTENCY_CHECK = ON ))
GO

--insert some data
INSERT INTO [TemporalTest] ([Id],[Name]) VALUES
(1, 'Alice'),
(2, 'Bob');
--just do an update so some history row gets populated
UPDATE [TemporalTest] SET [Name] = 'Bobby', [Description] = 'blah' WHERE [Id] = 2;
GO

--create the views (first one won't do a temporal query, second one will do a temporal query)
-- and they'll reference different columns to showcase the issue (first one uses Name, second uses Description)
CREATE VIEW [vwTemporalTest_noTemporalQuery]
WITH SCHEMABINDING
AS SELECT [Id], [Name] FROM [dbo].[TemporalTest];
GO
CREATE VIEW [vwTemporalTest_doingTemporalQuery]
WITH SCHEMABINDING
AS SELECT [Id], [Description], [period_start], [period_end] FROM [dbo].[TemporalTest] FOR SYSTEM_TIME ALL;
GO

--test out the views
SELECT * FROM [vwTemporalTest_noTemporalQuery]; --returns 2 current rows
SELECT * FROM [vwTemporalTest_doingTemporalQuery]; --returns 3 rows (including 1 historical row)

--look at information about the views
select * from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where View_Name = 'vwTemporalTest_noTemporalQuery' --returns 2 rows (one for each column used)
select * from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where View_Name = 'vwTemporalTest_doingTemporalQuery' --!!returns no rows (I expected 4 rows at least since the view uses 4 columns)

/*
--now verify schema binding prevents the underlying table from being altered
ALTER TABLE [TemporalTest] ALTER COLUMN [Name] nvarchar(600) NOT NULL; --fails as expected - The object 'vwTemporalTest_noTemporalQuery' is dependent on column 'Name'.
ALTER TABLE [TemporalTest] ALTER COLUMN [Description] nvarchar(255) NULL; --!!unexpectedly succeeds. I expected it to fail because vwTemporalTest_doingTemporalQuery uses the Description column.
*/

-----------------------------------------------------------------

0 Replies