I made a historical table along with a trigger function built in the reference table. Is based on possible name changes for an user and as well recording down the date.
My trigger built:
Target table:
The trigger function pulls the names off the table.
But I'm getting error converting data type. All my data that I'm updating are of VARCHAR type. Where am I missing?
Change the INSERT..VALUES statement to INSERT..SELECT and set aliases for all the columns. Make sure the aliases match the every column in the target table (defaulting all NOT NULL columns), and they are in the same order as they are declared.
Note that the SELECT statement uses the patientLastNameChange table, because the MAX() without a GROUP BY ensures only one row is returned.
I recommend to use COALESCE to set the MAX() result to 0 if it returns NULL. Then simply add 1 to increment the lastNameChangeID. I think this is more readable.
CREATE TRIGGER patientLastNameChangeTrigger
ON patient
AFTER UPDATE
AS
BEGIN
DECLARE #oldLastName VARCHAR(255) = (SELECT pt_lastName FROM DELETED);
DECLARE #newLastName VARCHAR(255) = (SELECT pt_lastName FROM INSERTED);
IF #oldLastName <> #newLastName
INSERT INTO dbo.patientLastNameChange (lastnameChangeID, patientID, oldLastName, newLastName, datechanged)
SELECT
COALESCE(MAX(plnc.lastnameChangeID),0)+1 AS lastnameChangeID,
(SELECT patientID FROM INSERTED) AS patientID,
#oldLastName AS oldLastName,
#newLastName AS newLastName,
GETDATE() AS datechanged
FROM patientLastNameChange plnc;
END;
Related
Given: Given a Microsoft SQL (2016 and above) database table Log with multiple columns including these important ones: id (primary key), code (an integer that can take multiple values representing status changes), lastupdated (a datetime field)...
What I need:
I need to add a computed column ActiveDate which stores the exact first time when the code changed to 10 (i.e. an active status). As the status keep[s changing in future, this column must maintain the same value as the exact time it went active (thus keeping the active datetime record persistently). This timestamp value should initially begin with a NULL.
My approach
I want the activedate field to automatically store the datetime at which the status code becomes 10, but when the status changes again, I want it to remain the same. Since I can't reference a calculated column from a calculated column, I created a user defined function to fetch the current value of activedate and use that whenever the status code is not 10.
Limitations:
I can't make modifications to the Db or to columns (other than the new columns I can add).
This T-SQL script must be idempotent such that it can be run multiple times at anytime in the production pipeline without losing or damaging data.
Here is what I tried.
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE Name=N'ActiveDate' AND OBJECT_ID = OBJECT_ID(N'[dbo].[Log]'))
/* First, create a dummy ActiveDate column since the user-defined function below needs it */
ALTER TABLE [dbo].[Log] ADD ActiveDate DATETIME NULL
IF OBJECT_ID('UDF_GetActiveDate', 'FN') IS NOT NULL
DROP FUNCTION UDF_GetActiveDate
GO
/* Function to grab the datetime when status goes active, otherwise leave it unchanged */
CREATE FUNCTION UDF_GetActiveDate(#ID INT, #code INT) RETURNS DATETIME WITH SCHEMABINDING AS
BEGIN
DECLARE #statusDate DATETIME
SELECT #statusDate = CASE
WHEN (#code = 10) THEN [lastupdated]
ELSE (SELECT [ActiveDate] from [dbo].[Log] WHERE id=#ID)
END
FROM [dbo].[Log] WHERE id=#ID
RETURN #statusDate
END
GO
/* Rename the dummy ActiveDate column so that we can be allowed to create the computed one */
EXEC sp_rename '[dbo].[Log].ActiveDate', 'ActiveDateTemp', 'COLUMN';
/* Computed column for ActiveDate */
ALTER TABLE [dbo].[Log] ADD ActiveDate AS (
[dbo].UDF_GetActiveDate([id],[code])
) PERSISTED NOT NULL
/* Delete the dummy ActiveDate column */
ALTER TABLE [dbo].[Log] DROP COLUMN ActiveDateTemp;
print ('Successfully added ActiveDate column to Log table')
GO
What I get: The following errors
[dbo].[Log].ActiveDate cannot be renamed because the object
participates in enforced dependencies.
Column names in each table
must be unique. Column name 'ActiveDate' in table 'dbo.Log' is
specified more than once.
Is my approach wrong? Or is there a better way to achieve the same result? Please help.
You shouldn't try to compute a column from itself.
Instead, I'd use a trigger...
CREATE TRIGGER dbo.log__set_active_date
ON dbo.log
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE
log
SET
active_date = INSERTED.last_updated
FROM
dbo.log
INNER JOIN
INSERTED
ON log.id = INSERTED.id
WHERE
INSERTED.code = 10
AND log.active_date IS NULL -- Added to ensure the value is only ever copied ONCE
END
db<>fiddle demo
I would advise you not to use a computed column or functions for this.
Just create a query that uses window functions:
SELECT
id,
code,
lastupdateddate,
ActiveDate = MIN(CASE WHEN l.code = 10 THEN l.lastupdateddate END)
OVER (PARTITION BY l.id)
FROM dbo.Log;
An example to the problem:
There are 3 columns present in my SQL database.
+-------------+------------------+-------------------+
| id(integer) | age(varchar(20)) | name(varchar(20)) |
+-------------+------------------+-------------------+
There are a 100 rows of different ids, ages and names. However, since many people update the database, age and name constantly change.
However, there are some boundaries to age and name:
Age has to be an integer and has to be greater than 0.
Name has to be alphabets and not numbers.
The problem is a script to check if the change of values is within the boundaries. For example, if age = -1 or Name = 1 , these values are out of the boundaries.
Right now, there is a script that does insert * into newtable where age < 0 and isnumeric(age) = 0 or isnumeric(name) = 0;
The compiled new table has rows of data that have values that are out of the boundary.
I was wondering if there is a more efficient method to do such checking in SQL. Also, i'm using microsoft sql server, so i was wondering if it is more efficient to use other languages such as C# or python to solve this issue.
You can apply check constraint. Replace 'myTable' with your table name. 'AgeCheck' and 'NameCheck' are names of the constraints. And AGE is the name of your AGE column.
ALTER TABLE myTable
ADD CONSTRAINT AgeCheck CHECK(AGE > 0 )
ALTER TABLE myTable
ADD CONSTRAINT NameCheck CHECK ([Name] NOT LIKE '%[^A-Z]%')
See more on Create Check Constraints
If you want to automatically insert the invalid data into a new table, you can create AFTER INSERT Trigger. I have given snippet for your reference. You can expand the same with additional logic for name check.
Generally, triggers are discouraged, as they make the transaction lengthier. If you want to avoid the trigger, you can have a sql agent job to do auditing on regular basis.
CREATE TRIGGER AfterINSERTTrigger on [Employee]
FOR INSERT
AS
BEGIN
DECLARE #Age TINYINT, #Id INT, Name VARCHAR(20);
SELECT #Id = ins.Id FROM INSERTED ins;
SELECT #Age = ins.Age FROM INSERTED ins;
SELECT #Name = ins.Name FROM INSERTED ins;
IF (#Age = 0)
BEGIN
INSERT INTO [EmployeeAudit](
[ID]
,[Name]
,[Age])
VALUES (#ID,
#Name,
#Age);
END
END
GO
I have a main table . I will get some real time records added to that table .I want to fetch all records which has been added ,altered or changed in previous existing records.
How can i Achieve this ?
You can use 2 commonly used approaches:
Track changes with another table through a trigger.
Should be something similar to this:
CREATE TABLE Tracking (
ID INT,
-- Your original table columns
TrackDate DATETIME DEFAULT GETDATE(),
TrackOperation VARCHAR(100))
GO
CREATE TRIGGER TrackingTrigger ON OriginalTable AFTER UPDATE, INSERT, DELETE
AS
BEGIN
INSERT INTO Tracking(
ID,
TrackOperation
-- Other columns
)
SELECT
ID = ISNULL(I.ID, D.ID),
TrackOperation = CASE
WHEN I.ID IS NOT NULL AND D.ID IS NOT NULL THEN 'Update'
WHEN I.ID IS NOT NULL THEN 'Insert'
ELSE 'Delete' END
-- Other columns
FROM
inserted AS I
FULL JOIN deleted AS D ON I.ID = D.ID -- ID is primary key
END
GO
Include CreatedDate, ModifiedDate and IsDeleted columns on your table. CreatedDate should have a default with current date, ModifiedDate should be updated each time your data is updated and IsDeleted should be flagged when you are deleting (and not actually being deleted). This option requires a lot more handling that the previous one, and you won't be able to track consecutive updates.
You have to search your table first from the sys.objects and grab that object id before using the usage_stats table.
declare #objectid int
select #objectid = object_id from sys.objects where name = 'YOURTABLENAME'
select top 1 * from sys.dm_db_index_usage_stats where object_id = #objectid
and last_user_update is not null
order by last_user_update
If you have Identity column in your table you may find last inserted row information through SQL query. And for that, we have multiple options like:
##IDENTITY
SCOPE_IDENTITY
IDENT_CURRENT
All three functions return last-generated identity values. However, the scope and session on which last is defined in each of these functions differ.
I have the following requirement:
I have a table with 1 unique auto-incremental Int ID and data columns.
I need the following:
Every time a row is inserted into that table, a column at the right end of the table must hold the full datetime of that insert.
Also, if a row is updated I need that column that holds the full datetime of the insert of that row into the table, to be updated to hold the update time for that row.
Now the obvious and very straightforward way to do this is:
you create your table:
create table My_Test_Table
(my_id int identity not null,
my_data nvarchar(max));
you alter your table adding the datetime column and a Default constraint on it:
ALTER TABLE My_Test_Table
ADD [timestamp] datetime;
ALTER TABLE My_Test_Table
ADD CONSTRAINT DF_My_Test_Table_timestamp DEFAULT GETDATE() FOR [timestamp];
then you make a nice trigger for update, like so:
CREATE TRIGGER DML_Trigger_update_My_Test_Table
ON My_Test_Table
FOR UPDATE
AS
IF ##ROWCOUNT <> 0
BEGIN
IF EXISTS (SELECT * FROM INSERTED) AND EXISTS (SELECT * FROM DELETED)
BEGIN
UPDATE My_Test_Table
SET [timestamp] = GETDATE()
FROM INSERTED
WHERE My_Test_Table.my_id = INSERTED.my_id;
END
END
Now the tricky part is:
I want, for reasons that are beyond scope here, to implement this exact thing as above but without a Trigger!
Is it possible?
I do not want to use the SQL type timestamp or rowversion, this won't work for me, I need the date, time down to the milliseconds to be clearly stored in that column.
Any ideas would be much appreciated.
You don't need a trigger
You can use the DEFAULT keyword as the source value in the UPDATE statement to use, well, the DEFAULT constraint defined on that column
UPDATE
MyTable
SET
foo = ...,
bar = ...,
ChangedDateTime = DEFAULT
WHERE
...;
I wouldn't use a column called timestamp because this has meaning on SQL Server, as a synonym for rowversion. For SQL Server 2008+ use datetime2(3) to accurately record milliseconds. The "old" datetime is accurate to a rounded 3.33 milliseconds only
Is there a special way to declare a DateCreated column in a MS Sql Server table so that it will automatically fill it with the appropriate time-stamp when created?
Or.. do I have to provide the datetime to it when I do the query, manually?
Default values suffer from two major drawbacks.
if the insert statement specifies a value for the column, the default isn't used.
the column can be updated any time.
These mean that you can't be certain that the values haven't been modified outside of your control.
If you want true data integrity (so that you're sure the date in the row is the creation date), you need to use triggers.
An insert trigger to set the column to the current date and an update trigger to prevent changes to that column (or, more precisely, set it to its current value) are the way to implement a DateCreated column.
An insert and update trigger to set the column to the current date is the way to implement a DateModified column.
(edit from user Gabriel - here's my attempt to implement this as described - i'm not 100% sure it's correct but I'm hoping the OP reviews it...):
CREATE TRIGGER [dbo].[tr_Affiliate_IU]
ON [dbo].[Affiliate]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Get the current date.
DECLARE #getDate DATETIME = GETDATE()
-- Set the initial values of date_created and date_modified.
UPDATE
dbo.Affiliate
SET
date_created = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
LEFT OUTER JOIN DELETED D ON I.id = D.id
WHERE
D.id IS NULL
-- Ensure the value of date_created does never changes.
-- Update the value of date_modified to the current date.
UPDATE
dbo.Affiliate
SET
date_created = D.date_created
,date_modified = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
INNER JOIN DELETED D ON I.id = D.id
END
You can set the default value of the column to "getdate()"
We have DEFAULT on CreatedDate and don't enforce with Triggers
There are times when we want to set the date explicitly - e.g. if we import data from some other source.
There is a risk that Application Bug could mess with the CreateDate, or a disgruntled DBA for that matter (we don't have non-DBAs connecting direct to our DBs)
I suppose you might set Column-level permissions on CreateDate.
A half-way-house might be to have an INSERT TRIGGER create a row in a 1:1 table, so that column was outside the main table. The second table could have SELECT permissions, where the main table has UPDATE permissions, and thus not need an UPDATE trigger to prevent changes to CreateDate - which would remove some "weight" when updating rows normally.
I suppose you coul have an UPDATE/DELETE trigger on the second table to prevent change (which would never be executed in normal circumstances, so "lightweight")
Bit of a pain to have the extra table though ... could have one table for all CreateDates - TableName, PK, CreateDate. Most database architects will hate that though ...
Certainly is.
Here is an example in action for you.
Create table #TableName
(
ID INT IDENTITY(1,1) PRIMARY KEY,
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
SomeDate VARCHAR(100)
)
INSERT INTO #TableName (SomeDate)
SELECT 'Some data one' UNION ALL SELECT 'some data two'
SELECT * FROM #TableName
DROP TABLE #TableName
Setting the default value isn't enough, you should add a trigger to prevent updating:
CREATE TRIGGER UpdateRecord ON my_table
AFTER UPDATE AS UPDATE my_table
SET [CreatedDate] = ((SELECT TOP 1 [CreatedDate] FROM Deleted d where d.[id]=[id]))