Need to have a Trigger on Table with Encrypted Column in SQL Server that inserts a new record for each update into Archive Table - sql

I have a table similar to following schema in SQL Server 2017:
Table Sample in the main database where TaxID column is encrypted using SQL Server "Always Encrypted" feature:
CREATE TABLE [dbo].[Sample]
(
[CreatedDt] [smalldatetime] NOT NULL,
[LastModDt] [smalldatetime] NOT NULL,
[CompanyID] [int] IDENTITY(1,1) NOT NULL,
[CompanyName] [varchar](250) NOT NULL,
[CompanyTaxName] [varchar](250) NULL,
[TaxID] [varchar](15) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY =
[CEK_Auto1], ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
[Active] [bit] NOT NULL
)
Then we have another table with same schema in archive database for history purposes with TaxID encrypted.
This is the table Sample in the Main_Archive database:
CREATE TABLE [dbo].[Sample]
(
[CreatedDt] [smalldatetime] NOT NULL,
[LastModDt] [smalldatetime] NOT NULL,
[CompanyArchiveID] [int] IDENTITY(1,1) NOT NULL,
[CompanyID] [int] IDENTITY(1,1) NOT NULL,
[CompanyName] [varchar](250) NOT NULL,
[CompanyTaxName] [varchar](250) NULL,
[TaxID] [varchar](15) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY =
[CEK_Auto1], ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
[Active] [bit] NOT NULL
)
Now, we want to have a trigger on the main Sample table that inserts a new record into the archive Sample table for every update.
The trigger for the Sample table in the main database is as follows:
CREATE TRIGGER [dbo].[tr_iud_Sample]
ON [dbo].[Sample]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE #CurrDt AS SMALLDATETIME
SELECT #CurrDt = GETDATE()
DECLARE #CurrYear AS INT
SELECT #CurrYear = YEAR(#CurrDt)
UPDATE Sample
SET LastModDt = #CurrDt,
CreatedDt = CASE WHEN d.CompanyID IS NULL THEN #CurrDt ELSE Sample.CreatedDt END
FROM inserted i WITH (NOLOCK)
LEFT JOIN deleted d WITH (NOLOCK) ON d.CompanyID= i.CompanyID
WHERE Sample.CompanyID = i.CompanyID
INSERT INTO [Main_Archive].[dbo].Sample
SELECT CreatedDt, LastModDt, CompanyID, CompanyName, CompanyTaxName, TaxID, Active
FROM deleted
END
ALTER TABLE [dbo].[Sample] ENABLE TRIGGER [tr_iud_Sample]
GO
ALTER TABLE [dbo].[Vendor] DISABLE TRIGGER [tr_iud_Sample]
GO
But this fails and I get this error:
Msg 4920, Level 16, State 0, Line 50
Operand type clash: varchar(15) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'NCI_COMMON') collation_name = 'Latin1_General_BIN2' is incompatible with varchar
Is there a way to have a trigger on encrypted table and if so, how to achieve the
desired functionality?
Also, if SQL Server currently does not support that, is there any work around to achieve that?
Thank you in advance

As you are using Always Encrypted your SQL Server version System-Versioned Temporal Tables.
You can make your table system-versioned and leave the work of maintaining the history to the SQL Server Engine (also, when you are changing your table design, the engine will mitigate the changes to the history table).
Temporal tables can be queried using special clauses and bring to you new ways for analyzing historical data.
One disadvantage I have faced is that the history table columns must match the target table ones - so, if you need to have a ModifiedBy column in the history, you must change your application to populate such value in the original table.

Related

SQL Server Prevent Update on Column (datetime2 column value set by database on insert)

I have a table Values with 3 columns:
CREATE TABLE [dbo].[Values]
(
[Id] [uniqueidentifier] NOT NULL,
[Value] [nvarchar](150) NOT NULL,
[CreatedOnUtc] [datatime2](7) NOT NULL
)
I want SQL Server to set the value of CreatedOnUtc to UTC-Now whenever a new entry is created, and not allow an external command to set this value.
Is this possible?
This is sort of two questions. For the first:
CREATE TABLE [dbo].[Values] (
[Id] [uniqueidentifier] NOT NULL,
[Value] [nvarchar](150) NOT NULL,
[CreatedOnUtc] [datetime2](7) NOT NULL DEFAULT SYSUTCDATETIME()
);
The canonical way to prevent changes to the column is to use a trigger that prevents the value from being updated or inserted.
Note that Values is a really bad name for a table because it is a SQL keyword and SQL Server reserved word. Choose identifiers that do not need to be escaped.
There are other ways. For instance, you could turn off DML access to the table. Then create a view without CreatedOnUtc and only allow inserts and updates through the view.

IF-THEN-ELSE: Create / Truncate Table - SQL Server

I am attempting to create a backup table without having to re-create it every single time. If the table already exists in the next run then it should simply truncate the table.
But it doesn't seem to be working. It says backup_reportsettings is already in the database. Can anyone assist me with this?
--Only re-create table if table does not exist otherwise truncate the existing table.
IF NOT EXISTS (SELECT * FROM [Misc].sys.tables where name= 'dbo.backup_reportsettings')
CREATE TABLE [MISC].dbo.backup_reportsettings
(
[datestamp] [datetime] NULL,
[reportsettingid] [char](8) NOT NULL,
[description] [char](30) NOT NULL,
[formname] [char](30) NOT NULL,
[usersid] [char](8) NOT NULL,
[settings] [text] NOT NULL,
[notes] [varchar](255) NOT NULL,
[userdefault] [char](1) NOT NULL
)
ELSE
TRUNCATE TABLE [Misc].dbo.backup_reportsettings;
What am I doing wrong? Note: this is done within a transaction.
Object names in sys.tables don't have the schema as part of the name. Remove the table schema when verifying whether the table exists:
IF NOT EXISTS (SELECT * FROM [Misc].sys.tables where name= 'backup_reportsettings')
Despite the use of IF, SQL Server needs to Parse/Compile all the statements in your script, so when it sees a CREATE TABLE statement it will give you a compilation error if the table already exists, even though the IF would prevent that code from being executed when that is the case.
The way to get around this is to put your CREATE TABLE statement in dynamic SQL, which will not be parsed/compiled before execution.

Insert only modified values and column names into a table

I have a sql server 2012 database. In which i have a changeLog table that contains
TableName, ColumnName, FromValue and ToValue columns. Which will be used to keep track of modified columns and data.
So if any update occur through application then only modified columns should insert into this table with its new and old value.
Can anyone help me in this.
For Example:
If the procedure updates all columns of property table (propertyName, address)
then if user update propertyName (but update also contains address column but with no data change) then only propertyName and its data will be inserted into ChangeLog table not address column and its data because address data does not contains any data change.
IF there is no other auditing requirement at all - you would not be thinking about Auditing in any way without this - then OK, go for it. However this is a very limited use of Auditing: User X changed this field at time Y. Generally this is interesting as part of a wider question: what did user X do? What happened to that customer data in the database to end up the way it is now?
Questions like that are harder to answer if you have the data structure you propose and would be quite onerous to reconstruct. My usual approach would be as follows. Starting from a base table like so (this from one of my current projects):
CREATE TABLE [de].[Generation](
[Id] [int] IDENTITY(1,1) NOT NULL,
[LocalTime] [datetime] NOT NULL,
[EntityId] [int] NOT NULL,
[Generation] [decimal](18, 4) NOT NULL,
[UpdatedAt] [datetime] NOT NULL CONSTRAINT [DF_Generation_UpdatedAt] DEFAULT (getdate()),
CONSTRAINT [PK_Generation] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
(I've excluded FK definitions as they aren't relevant here.)
First create an Audit table for this table:
CREATE TABLE [de].[GenerationAudit](
[AuditId] int identity(1, 1) not null,
[Id] [int] NOT NULL,
[LocalTimeOld] [datetime] NULL,
[EntityIdOld] [int] NULL,
[GenerationOld] [decimal](18, 4) null,
[UpdatedAtOld] [datetime] null,
[LocalTimeNew] [datetime] null,
[EntityIdNew] [int] null,
[GenerationNew] [decimal](18, 4) null,
[UpdatedAtNew] [datetime] NOT NULL CONSTRAINT [DF_GenerationAudit_UpdatedAt] DEFAULT (getdate()),
[UpdatedBy] varchar(60) not null
CONSTRAINT [PK_GenerationAudit] PRIMARY KEY CLUSTERED
(
[AuditId] ASC
)
This table has an *Old and a *New version of each column that can't change. The Id, being an IDENTITY PK, can't change so no need for an old/new. I've also added an UpdatedBy column. It also has a new AuditId IDENTITY PK.
Next create three triggers on the base table: one for INSERT, one for UPDATE and one for DELETE. In the Insert trigger, insert a row into the Audit table with the New columns selected from the inserted table and the Old values as null. In the UPDATE one, the Oldvalues come from the deleted and the new from the inserted. In the DELETE trigger, old from from deleted and the new are all null.
The UPDATE trigger would look like this:
CREATE TRIGGER GenerationAuditUpdate
ON de.Generation
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
insert into de.GenerationAudit (Id, LocalTimeOld, EntityIdOld, GenerationOld, UpdatedAtOld,
LocalTimeNew, EntityIdNew, GenerationNew, UpdatedAtNew,
UpdatedBy)
select isnull(i.Id, d.Id), d.LocalTime, d.EntityId, d.Generation, d.UpdatedAt,
i.LocalTime, i.EntityId, d.Generation, getdate(),
SYSTEM_USER)
from inserted i
full outer join deleted d on d.Id = i.Id;
END
GO
You then have a full before/after picture of each change (and it'll be faster than seperating out diffs column by column). You can create views over the Audit table to get entries where the Old value is different to the new, and include the base table Id (which you will also need in your structures!), the user who did it, and the time they did it (UpdatedAtNew).
That's my version of Auditing and it's mine!

Why database does not recognize the columns and table?

I have created a table in there is data in the table,
I can insert, update ect.
CREATE TABLE [dbo].[cse_reports_month](
[report_id] [int] IDENTITY(1,1) NOT NULL,
[starburst_dept_name] [varchar](50) NULL,
....more collumns
[shooting_total_stars] [float] NULL,
) ON [PRIMARY]
but for some reason when I hover over the columns name, for example
select the top 1000 rows :
SELECT TOP 1000 [report_id]
,[starburst_dept_name]
,[starburst_dept_average]
...... more columns
,[rising_star_dept_name]
FROM [Intranet].[dbo].[cse_reports_month]
It says "invalid column name 'starburst_dept_name'"
It like it does not requinize it, but all my other tables are
good.
Wondering why it does not recognize the columns?
I have
Microsoft SQL Server Management Studio 10.0.2531.0

Creating trigger in SQL Server 2005 (has to work in 2008 too) to prevent duplicates?

I have table that I insert data with following query (from c# code):
INSERT INTO [BazaZarzadzanie].[dbo].[Wycena]
([KlienciPortfeleKontaID]
,[WycenaData]
,[WycenaTyp]
,[WycenaWartosc]
,[WycenaWaluta]
,[WycenaUzytkownik]
,[WycenaUzytkownikData])
VALUES
(#varKlienciPortfeleKontaID
,#varWycenaData
,#varWycenaTyp
,#varWycenaWartosc
,#varWycenaWaluta
,#varWycenaUzytkownik
,#varWycenaUzytkownikData)
Table creation script looks like this:
CREATE TABLE [dbo].[Wycena](
[KlienciPortfeleKontaID] [int] NULL,
[WycenaData] [datetime] NULL,
[WycenaTyp] [int] NULL,
[InID] [int] NULL,
[WycenaIlosc] [decimal](18, 2) NULL,
[WycenaCena] [decimal](18, 2) NULL,
[WycenaWartosc] [decimal](18, 2) NULL,
[WycenaWaluta] [nvarchar](3) NULL,
[WycenaUzytkownik] [nvarchar](50) NULL,
[WycenaUzytkownikData] [datetime] NULL
) ON [PRIMARY]
It also has couple of foreign keys but nothing that i could make primary/unique key. So i thought to prevent duplicates i would go for a trigger since to know one row is duplicate i actually have to test every single value of that row (well maybe not 2 last columns) ? This table has around 2mln rows.
Is this good idea? Or is there a better way?
Below is trigger I've created (not tested if it works):
CREATE TRIGGER [dbo].[trg_WycenaDuplicateCheck]
ON [dbo].[Wycena] FOR INSERT
AS
IF EXISTS(SELECT INSERTED.[KlienciPortfeleKontaID]
,INSERTED.[WycenaData]
,INSERTED.[WycenaTyp]
,INSERTED.[InID]
,INSERTED.[WycenaIlosc]
,INSERTED.[WycenaCena]
,INSERTED.[WycenaWartosc]
,INSERTED.[WycenaWaluta]
FROM INSERTED, Wycena
WHERE INSERTED.[KlienciPortfeleKontaID] = Wycena.[KlienciPortfeleKontaID]
AND INSERTED.[WycenaData] = Wycena.[WycenaData]
AND INSERTED.[WycenaTyp] = Wycena.[WycenaTyp]
AND INSERTED.[InID] = Wycena.[InID]
AND INSERTED.[WycenaIlosc] = Wycena.[WycenaIlosc]
AND INSERTED.[WycenaCena] = Wycena.[WycenaCena]
AND INSERTED.[WycenaWartosc] = Wycena.[WycenaWartosc]
AND INSERTED.[WycenaWaluta] = Wycena.[WycenaWaluta]
Group By INSERTED.[KlienciPortfeleKontaID]
,INSERTED.[WycenaData]
,INSERTED.[WycenaTyp]
,INSERTED.[InID]
,INSERTED.[WycenaIlosc]
,INSERTED.[WycenaCena]
,INSERTED.[WycenaWartosc]
,INSERTED.[WycenaWaluta]
HAVING COUNT (*) > 1)
BEGIN
RAISERROR('>>>DUPLICATES PREVENTED<<< ',10,1)
ROLLBACK TRAN
END
Create a "unique" index on the fields you care about.
CREATE UNIQUE INDEX IX_YOUR_FAVORITE_NAME
ON [dbo].[Wycena](... list of columns goes here ...)
Seems like you need to look at UNIQUE Constraints