How do I create system versioned tables using SSDT in Visual Studio 2019? - sql-server

I'm trying to create Table with system versioning using Database Project.
Following schema gives error:
SQL70633: System-versioned temporal table must have history table name explicitly provided.
CREATE TABLE [dbo].[Products]
(
[Id] INT NOT NULL PRIMARY KEY,
[Name] NVARCHAR(255) NOT NULL,
[ModifiedBy] NVARCHAR(127) NULL
)
WITH (SYSTEM_VERSIONING = ON)
GO
With explicit name:
SQL71501: Table: [dbo].[Products] has an unresolved reference to Table [history].[ProductsHistory].
SQL46010: Incorrect syntax near ].
CREATE TABLE [dbo].[Products]
(
[Id] INT NOT NULL PRIMARY KEY,
[Name] NVARCHAR(255) NOT NULL,
[ModifiedBy] NVARCHAR(127) NULL
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [history].ProductsHistory))
GO
I've tried both, latest version of Visual Studio 2019 (16.7.5) and latest preview (16.8.0 Preview 3.2).

The syntax in both cases is invalid. Executing the first query in SSMS returns:
Cannot set SYSTEM_VERSIONING to ON when SYSTEM_TIME period is not defined.
The command needs a PERIOD FOR SYSTEM_TIME clause specifying the columns used to specify the validity period of a record.
The documentation examples show how to create a temporal table with a default, automatically named history table :
CREATE TABLE [dbo].[Products]
(
[Id] INT NOT NULL PRIMARY KEY,
[Name] NVARCHAR(255) NOT NULL,
[ModifiedBy] NVARCHAR(127) NULL,
SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON)
In this case, the SysStartTime and SysEndTime are used to specify the validity period of a record.
Similar syntax is needed to create a temporal table with a user-specified table name
create TABLE [dbo].[Products]
(
[Id] INT NOT NULL PRIMARY KEY,
[Name] NVARCHAR(255) NOT NULL,
[ModifiedBy] NVARCHAR(127) NULL,
SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ProductHistory))
It's possible to create the history table on a different schema, eg history, as long as that schema exists, BUT it's probably not a good idea unless this solves some specific problem. The current and history table represent the same entity, depend on each other and have specific security restrictions so storing them in separate schemas can make life harder.
To create the table in a separate schema, first create the schema :
CREATE SCHEMA history
Then use the schema in the table definition:
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = history.ProductHistory))

Related

How to solve IntelliSense error in auto generated temporal table

I'm trying to use temporal tables in my SQL server db project in visual studio.
A sample table
CREATE TABLE [dbo].[Sale Types]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (MAX) NOT NULL,
SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime),
CONSTRAINT [PK_Sale Types] PRIMARY KEY CLUSTERED ([Id] ASC)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.[Sale Types History]));
My problem comes from errors being generated in these auto generated tables (dbo.[Sale Types History])
Error SQL46010: Incorrect syntax near Types

Is there any way to track changes from views in MS Sql Server?

I'm looking for how to track changes from a view in MS Sql-Server 2012. And, the role of the log-in user is Public. So, it's hard to do it.
For example, Assuming that there is the schema.
CREATE TABLE [dbo].[USER_CREDENTIAL](
[USERID] [nvarchar](48) NOT NULL,
[VALID_FROM] DATETIME NULL,
[EXPIRED_AT] DATETIME NULL,
[CREDENTIAL_ID] int NOT NULL,
CONSTRAINT [UNIQUE_USERID] PRIMARY KEY CLUSTERED( [USERID] ASC)
) ;
CREATE VIEW [VIEW_OF_USER_CREDENTIAL] as
SELECT * FROM dbo.[USER_CREDENTIAL];
It can be only permitted to access the view. The view will be changed when some data is inserted/updated/deleted from the USER_CREDENTIAL table. I will do query to the view.
I saw the document. I tried that, but the target to track should be the data table and the login user is lack of the role. I got the error message.
Object 'foo' is of a data type that is not supported by the CHANGETABLE function. The object must be a user-defined table.
I tried the following. I added the temporary table and the trigger which make changed-data be inserted to the temporary table when the view is changed. But, it was also failed because it was permission denied.
CREATE TABLE dbo.[CHANGES_FROM_A_VIEW] (
[VERSION] [int] IDENTITY(1,1) NOT NULL,
[USERID] [nvarchar](48) NOT NULL,
CONSTRAINT [UNIQUE_VERSION] PRIMARY KEY CLUSTERED ( [VERSION] ASC)
)
CREATE TRIGGER [SOMETHING_CHANGED] ON dbo.[VIEW_OF_USER_CREDENTIAL] ...
ALTER DATABASE database_name
SET CHANGE_TRACKING = ON (CHANGE_RETENTION = 2 DAYS,AUTO_CLEANUP = ON)
ALTER TABLE [CHANGES_FROM_A_VIEW]
ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = ON)
SELECT * FROM CHANGETABLE(CHANGES dbo.CHANGES_FROM_A_VIEW, 0) AS C
Anyone knows any way to solve this?

How to prevent SQL71609 with SSDT on a new temporal table (system-versioned)

I try to use SQL Server Temporal Tables within Visual Studio 2017 and SQL Server Data Tools (SSDT).
But I get immediately following error:
SQL71609: System-versioned current and history tables do not have
matching schemas. Mismatched column: '[dbo].[MyTable].[ValidFrom]'
I don't see any mistake. Do I miss something?
I created a small repository on GIT HUB for reproduction
The current table is defined as:
CREATE TABLE [dbo].[MyTable]
(
[TenantId] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_MyTable_TenantId] DEFAULT
CAST(SESSION_CONTEXT(N'TenantId') AS UNIQUEIDENTIFIER),
[Rn] BIGINT IDENTITY(1,1) NOT NULL,
[Id] UNIQUEIDENTIFIER NOT NULL,
[PropA] INT NOT NULL,
[PropB] NVARCHAR(100) NOT NULL,
[ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL CONSTRAINT [DF_ValidFrom] DEFAULT CONVERT(DATETIME2, '0001-01-01'),
[ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL CONSTRAINT [DF_ValidTo] DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]),
CONSTRAINT [PK_MyTable] PRIMARY KEY NONCLUSTERED ([Id]),
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[MyTableHistory]))
GO
CREATE UNIQUE CLUSTERED INDEX [CIX_MyTable] ON [dbo].[MyTable]([Rn])
GO
And the history table :
CREATE TABLE [dbo].[MyTableHistory]
(
[TenantId] UNIQUEIDENTIFIER NOT NULL,
[Rn] BIGINT IDENTITY(1,1) NOT NULL,
[Id] UNIQUEIDENTIFIER NOT NULL,
[PropA] INT NOT NULL,
[PropB] NVARCHAR(100) NOT NULL,
[ValidFrom] DATETIME2,
[ValidTo] DATETIME2,
);
GO
CREATE CLUSTERED COLUMNSTORE INDEX [COLIX_MyTableHistory]
ON [dbo].[MyTableHistory];
GO
CREATE NONCLUSTERED INDEX [IX_ImpactHistory_ValidFrom_ValidTo_Id]
ON [dbo].[MyTableHistory] ([ValidFrom], [ValidTo], [Id]);
GO
Not really sure why you are getting this particular error message.
I've tested your code on db fiddle and got different errors.
BTW, please note that you don't have to write the history table yourself - if you only set it's name using the SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[MyTableHistory]) and not create it, SQL Server will generate it automatically for you - as can be seen in this fiddle.
For the first attempt I've got this error:
Msg 13518 Level 16 State 1 Line 20
Setting SYSTEM_VERSIONING to ON failed because history table 'fiddle_e3d361da65804a39b041c8149132b443.dbo.MyTableHistory' has IDENTITY column specification. Consider dropping all IDENTITY column specifications and trying again.
So I've removed the identity from [Rn] column in the history table and tried again.
Then I've got this error:
Msg 13530 Level 16 State 1 Line 20
Setting SYSTEM_VERSIONING to ON failed because system column 'ValidFrom' in history table 'fiddle_d6660ab11cdc448dba35790867169a14.dbo.MyTableHistory' corresponds to a period column in table 'fiddle_d6660ab11cdc448dba35790867169a14.dbo.MyTable' and cannot be nullable.
So I've changed both the ValidFrom and ValidTo columns to NOT NULL and finally got it working.
The working version is copied to here:
CREATE TABLE [dbo].[MyTableHistory]
(
[TenantId] UNIQUEIDENTIFIER NOT NULL,
[Rn] BIGINT NOT NULL,
[Id] UNIQUEIDENTIFIER NOT NULL,
[PropA] INT NOT NULL,
[PropB] NVARCHAR(100) NOT NULL,
[ValidFrom] DATETIME2 NOT NULL,
[ValidTo] DATETIME2 NOT NULL,
);
CREATE CLUSTERED COLUMNSTORE INDEX [COLIX_MyTableHistory]
ON [dbo].[MyTableHistory];
CREATE NONCLUSTERED INDEX [IX_ImpactHistory_ValidFrom_ValidTo_Id]
ON [dbo].[MyTableHistory] ([ValidFrom], [ValidTo], [Id]);
CREATE TABLE [dbo].[MyTable]
(
[TenantId] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_MyTable_TenantId] DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS UNIQUEIDENTIFIER),
[Rn] BIGINT IDENTITY(1,1) NOT NULL,
[Id] UNIQUEIDENTIFIER NOT NULL,
[PropA] INT NOT NULL,
[PropB] NVARCHAR(100) NOT NULL,
[ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL CONSTRAINT [DF_ValidFrom] DEFAULT CONVERT(DATETIME2, '0001-01-01'),
[ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL CONSTRAINT [DF_ValidTo] DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]),
CONSTRAINT [PK_MyTable] PRIMARY KEY NONCLUSTERED ([Id]),
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[MyTableHistory]))
CREATE UNIQUE CLUSTERED INDEX [CIX_MyTable] ON [dbo].[MyTable]([Rn])

transact sql temporal table and user-defined data type

Sorry for my english.
I have the following definitions in a visual studio 2017 dacpac project (target platform = SQL SERVER 2016).
CREATE SCHEMA [Demo]
CREATE TYPE Demo.Code FROM NVARCHAR(16) NOT NULL
CREATE TABLE [Demo].[Gear] (
[Id] int identity(1,1) primary key,
[Name] nvarchar(25) not null,
[Code] [Demo].[Code],
[rowguid] uniqueidentifier constraint [DF_Gear_rowguid] default (newid()) rowguidcol not null,
[ValidStart] DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
[ValidEnd] DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME([ValidStart], [ValidEnd])
)
WITH ( SYSTEM_VERSIONING = ON(HISTORY_TABLE = [Histo].[Gear]) )
GO
After build, I have this error :
SQL71609: System-versioned current and history tables do not have
matching schemas. Mismatched column: '[Demo].[Gear].[Code]'.
Without user defined data type, it's okay. Is someone knowing that's the problem ?

How to automatically create rows and pass values to other tables

There are three tables in database:
"BusinessEntity " which has the identity column "BusinessEntityID" as Primary Key (as well as rowguid and ModifiedDate columns).
"Firm" which has similarly the identity column "BusinessEntityID" as Primary Key, which is also a Foreign Key to BusinessEntity.BusinessEntityID (it has a 1-to-1 relationship with "BusinessEntity" table, FirmName, rowguid and ModifiedDate columns ).
"Customer" which has the identity column "CustomerID" as Primary Key and column "FirmID" as Foreign Key to Firm .BusinessEntityID (plus CustomerName, rowguid and ModifiedDate columns).
i.e. (also see image)
tables: BusinessEntity Firm Customer
columns: CustomerID (PK)
BusinessEntityID(PK) --> BusinessEntityID (PK/FK) --> FirmID (FK)
What I'm trying to do is whenever a new Customer row is to be created:
A new BusinessEntity row to be created automatically and then pass its BusinessEntityID value to an (automatically) newly created Firm row which it turn would pass its own BusinessEntityID to Customer table as FirmID column.
As you can see a BusinessEntity row was no meaning unless it corresponds to a Firm (or other entities) and a Customer must include a Firm.
I created a view containing all three tables along with a trigger to do the job without success. Any suggestions?
The tables:
BusinessEntity
CREATE TABLE [dbo ].[BusinessEntity](
[BusinessEntityID] [int] IDENTITY(1,1) NOT NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_BusinessEntity_BusinessEntityID] PRIMARY KEY CLUSTERED
(
[BusinessEntityID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[BusinessEntity] ADD CONSTRAINT [DF_BusinessEntity_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo ].[BusinessEntity] ADD CONSTRAINT [DF_BusinessEntity_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
Firm
CREATE TABLE [dbo].[Firm](
[BusinessEntityID] [int] IDENTITY(1,1) NOT NULL,
[FirmName] [nvarchar](30) NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Firm_BusinessEntityID] PRIMARY KEY CLUSTERED
(
[BusinessEntityID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Firm] ADD CONSTRAINT [DF_Firm_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo].[Firm] ADD CONSTRAINT [DF_Firm_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
ALTER TABLE [dbo].[Firm] WITH CHECK ADD CONSTRAINT [FK_Firm_BusinessEntity_BusinessEntityID] FOREIGN KEY([BusinessEntityID])
REFERENCES [dbo].[BusinessEntity] ([BusinessEntityID])
GO
ALTER TABLE [dbo].[Firm] CHECK CONSTRAINT [FK_Firm_BusinessEntity_BusinessEntityID]
GO
Customer
CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[FirmID] [int] NULL,
[CustomerName] [nvarchar](28) NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Customer_CustomerID] PRIMARY KEY CLUSTERED
(
[CustomerID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
ALTER TABLE [dbo].[Customer] WITH CHECK ADD CONSTRAINT [FK_Customer_Firm_FirmID] FOREIGN KEY([FirmID])
REFERENCES [dbo].[Firm] ([BusinessEntityID])
GO
ALTER TABLE [dbo].[Customer] CHECK CONSTRAINT [FK_Customer_Firm_FirmID]
GO
Something weird happens here. I created this stored procedure:
CREATE PROCEDURE [dbo].[CreateFirmCustomer](#FirmName NVARCHAR(30), #CustomerName NVARCHAR(28)) AS
BEGIN;
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT BusinessEntity DEFAULT VALUES;
DECLARE #BusinessEntityID INT = SCOPE_IDENTITY();
SET IDENTITY_INSERT [dbo].[Firm] ON
INSERT Firm(BusinessEntityID, FirmName)
VALUES (#BusinessEntityID, #FirmName);
SET IDENTITY_INSERT [dbo].[Firm] OFF
INSERT Customer(FirmID, CustomerName)
VALUES (#BusinessEntityID, #CustomerName);
DECLARE #CustomerID INT = SCOPE_IDENTITY();
SELECT #BusinessEntityID AS FirmID, #CustomerID AS CustomerID;
COMMIT;
END;
GO
When I run it sometimes the CustomerID column gets the value of BusinessEntityID column when it should really be independently auto-generated. Also the BusinessEntityID column auto-generates weird values e.g. jumped from value 7 to value 1002. (BusinessEntityID is BusinessEntity.BusinessEntityID ) Any clues? (see picture)
Now I created this view to insert Customers as Firms:
CREATE VIEW [dbo].[vBusEntityFirmCustomer]
AS
SELECT dbo.Firm.FirmName, dbo.Customer.CustomerName
FROM dbo.BusinessEntity INNER JOIN
dbo.Firm ON dbo.BusinessEntity.BusinessEntityID = dbo.Firm.BusinessEntityID INNER JOIN
dbo.Customer ON dbo.Firm.BusinessEntityID = dbo.Customer.FirmID
GO
And this trigger on the view:
CREATE TRIGGER [dbo].[trg_FirmCustomer]
ON [dbo].[vBusEntityFirmCustomer]
INSTEAD OF INSERT
AS
exec [dbo].[CreateFirmCustomer]
GO
But every time I enter a new FirmName CustomerName to insert a new row I get this message (see image):
Procedure or function 'CreateFirmCustomer' expects parameter '#FirmName', which was not supplied.
The fact is that I do supply FirmName.
Logically, as designed, you have to create a BusinessEntity first, then a Firm, then a Customer. Across all these tables, the only real information you're storing is the firm name and the customer name -- all the rest is derived and autogenerated by the database. We can encapsulate the operation CreateCustomer in a stored procedure:
CREATE PROCEDURE CreateCustomer(#FirmName NVARCHAR(30), #CustomerName NVARCHAR(28)) AS
BEGIN;
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT BusinessEntity DEFAULT VALUES;
DECLARE #BusinessEntityID INT = SCOPE_IDENTITY();
INSERT Firm(BusinessEntityID, FirmName)
VALUES (#BusinessEntityID, #FirmName);
INSERT Customer(FirmID, CustomerName)
VALUES (#BusinessEntityID, #CustomerName);
DECLARE #CustomerID INT = SCOPE_IDENTITY();
-- Return IDs of the newly created rows as the result set
SELECT #BusinessEntityID AS FirmID, #CustomerID AS CustomerID;
COMMIT;
END;
Invoke as (for example) EXEC CreateCustomer 'Firm', 'Customer'. With the table definitions as given, this will fail because Firm.BusinessEntityID is an IDENTITY -- if it is to take its value from BusinessEntity, it shouldn't be. (You can work around this with IDENTITY_INSERT, but in a properly designed database this shouldn't be necessary.)
Another thing that's obviously weird is that we insert no business data at all in BusinessEntity (which is why we need the DEFAULT VALUES syntax) -- it's nothing but a super-general container of IDs, so it's of dubious value. Nevertheless, this demonstrates the general technique of inserting rows in multiple tables that have dependencies.
As written, this stored procedure always creates a new Firm and BusinessEntity to go along with the Customer. Logically, a Firm can have more than one Customer, so you probably want another stored procedure to create a Customer for an existing Firm. This is simpler, as it's just an INSERT in Customer with the appropriate FirmID. You may wish to have a separate CreateFirm stored procedure that you call first, followed by a CreateCustomer to add a customer for that firm.
According to me,
it all depend how and when those 3 tables are populated.
Suppose those three table are populated using single UI, then
I will write them in single proc within one transaction.
Suppose those 3 table will be will populated at diff stage i.e diff UI then i write them in diff proc as you have already define constraint.
BTW what is the purpose of rowguid in all 3 tables.

Resources