SQL Server 2008 Audit trigger based on sub string - sql

I would like to create a trigger based on a column but only for those records that end in _ess. How can I set up an audit trigger to do this?
Here is the current trigger but it just checks for all changes to username, whereas I just want it to check when username is updated to or from a username ending in _ess.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[AUDIT_UPD_HRPERSONS_USERNAME] ON [dbo].[HRPersons] FOR UPDATE NOT FOR REPLICATION As
BEGIN
DECLARE
#OperationNum int,
#DBMSTransaction VARCHAR(255),
#OSUSER VARCHAR(50),
#DBMSUSER VARCHAR(50),
#HostPhysicalAddress VARCHAR(17),
#contexto varchar(128),
#ApplicationModifierUser varchar(50),
#SessionInfo_OSUser varchar(50),
#HostLogicalAddress varchar(30)
Set NOCOUNT On
IF ##trancount>0
BEGIN
EXECUTE sp_getbindtoken #DBMSTransaction OUTPUT
END
ELSE BEGIN
SET #DBMSTransaction = NULL
END
IF PatIndex( '%\%',SUSER_SNAME()) > 0
BEGIN
set #OSUSER = SUSER_SNAME()
set #DBMSUSER = NULL
END
ELSE BEGIN
SET #OSUSER = NULL
SET #DBMSUSER = SUSER_SNAME()
END
set #HostPhysicalAddress = (SELECT net_address FROM master..sysprocesses where spid=##spid )
set #HostPhysicalAddress = substring (#HostPhysicalAddress,1,2) + '-' + substring (#HostPhysicalAddress,3,2) + '-' + substring (#HostPhysicalAddress,5,2) + '-' + substring (#HostPhysicalAddress,7,2) + '-' + substring (#HostPhysicalAddress,9,2) + '-' + substring (#HostPhysicalAddress,11,2)
SELECT #contexto=CAST(context_info AS varchar(128)) FROM master..sysprocesses WHERE spid=##SPID
IF (PatIndex( '%APPLICATION_USER=%',#contexto) is not null) and (PatIndex( '%APPLICATION_USER=%',#contexto) > 0)
set #ApplicationModifierUser=substring(ltrim(substring(#contexto,PatIndex( '%APPLICATION_USER=%',#contexto)+17,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%APPLICATION_USER=%',#contexto)+17,128) ) ) - 1 )
ELSE
set #ApplicationModifierUser=NULL
IF (PatIndex( '%OS_USER=%',#contexto) is not null) and ( PatIndex( '%OS_USER=%',#contexto)>0 )
set #SessionInfo_OSUser=substring(ltrim(substring(#contexto,PatIndex( '%OS_USER=%',#contexto)+8,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%OS_USER=%',#contexto)+8,128) ) ) - 1 )
ELSE
set #SessionInfo_OSUser=NULL
IF (PatIndex( '%LOGICAL_ADDRESS=%',#contexto) is not null) and (PatIndex( '%LOGICAL_ADDRESS=%',#contexto)>0)
set #HostLogicalAddress=substring(ltrim(substring(#contexto,PatIndex( '%LOGICAL_ADDRESS=%',#contexto)+16,128)),1, charIndex( '///',ltrim(substring(#contexto,PatIndex( '%LOGICAL_ADDRESS=%',#contexto)+16,128) ) ) - 1 )
ELSE
set #HostLogicalAddress=NULL
INSERT INTO AuditedOperations ( Application, Object, OperationType, ModifiedDate, ApplicationModifierUser, OSModifierUser, DBMSModifierUser, Host, HostLogicalAddress, HostPhysicalAddress, DBMSTransaction)
VALUES (APP_NAME(), 'HRPERSONS', 'U', GETDATE(), #ApplicationModifierUser, #OSUSER, #DBMSUSER, HOST_NAME(), #HostLogicalAddress, #HostPhysicalAddress, #DBMSTransaction)
Set #OperationNum = ##IDENTITY
INSERT INTO AuditedRows (OperationNum, RowPK)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar))
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
INSERT INTO AuditedRowsColumns (OperationNum, RowPK, ColumnName, ColumnAudReg, OldValue, NewValue)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar)), 'USERNAME','A', CONVERT( VARCHAR(3500),DELETED.USERNAME), CONVERT( VARCHAR(3500),INSERTED.USERNAME)
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
END
GO

Just add this:
INSERT INTO AuditedRows (OperationNum, RowPK)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar))
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
-- Restrict it to only those where the username is changing from or to %_ess
WHERE (deleted.username like '%_ess' or inserted.username like '%_ess')
INSERT INTO AuditedRowsColumns (OperationNum, RowPK, ColumnName, ColumnAudReg, OldValue, NewValue)
SELECT #OperationNum, ISNULL(CAST(INSERTED.ID as nvarchar),CAST(DELETED.ID as nvarchar)), 'USERNAME','A', CONVERT( VARCHAR(3500),DELETED.USERNAME), CONVERT( VARCHAR(3500),INSERTED.USERNAME)
FROM INSERTED FULL OUTER JOIN DELETED ON INSERTED.ID=DELETED.ID
-- Restrict it to only those where the username is changing from or to %_ess
WHERE (deleted.username like '%_ess' or inserted.username like '%_ess')

Related

Select results from A table & insert into another table - Stored Procedure

In my stored procedure, i have a select statement which will return more than one row of results. And i would want to insert those results into another table.
When i run the stored procedure, there are two rows of result returned from select statement, but it only insert one row of result instead of two into sendemail table.
Can anyone help on this? thanks
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#newid int,
#Name nvarchar (50),
#Email nvarchar(50),
#ExpiryDate varchar(50)
SET #newid = 0
--Get results
SELECT #newid = b.[Id]
, #Name = c.[Name]
, #Email = b.[Email]
, #ExpiryDate = a.AccountExpiryDate
FROM Account a
LEFT JOIN Customer b ON c.[Id] = a.[CustomerId]
LEFT JOIN VIP c on c.[Id] = b.[VIPId]
WHERE a.[AccountExpiryDate] Between Cast(GETDATE() AS DATE) AND CAST(DATEADD(d, 30, GETDATE()) AS DATE)
--Insert into send email table if return results
IF(#newid > 0)
BEGIN
INSERT INTO [dbo].[SendEmail]
VALUES (
5
,'test#hotmail.com'
,'Test'
,#Email
,#Name
,NULL
,NULL
,''
,NULL
,'Test Email'
,'<p>
Hi ' + #Name + ',<br /><br />
Your account will be expired on ' + #ExpiryDate + '.
</p>'
,NULL
,NULL
,0
,GETUTCDATE()
,NULL
,1
,NULL
,1)
END
END
You should use insert . . . select:
INSERT INTO [dbo].[SendEmail] ( column list goes here )
SELECT 5, 'test#hotmail.com', 'Test', c.[Email], v.[Name], '',
'Test Email',
'<p>
Hi ' + v.[Name] + ',<br /><br />
Your account will be expired on ' + cast(a.AccountExpiryDate as varchar(255)) + '.
</p>',
0, GETUTCDATE(), 1, 1)
FROM Account a LEFT JOIN
Customer c
ON c.[Id] = a.[CustomerId] LEFT JOIN
VIP v
ON v.[Id] = c.[VIPId]
WHERE a.AccountExpiryDate Between Cast(GETDATE() AS DATE) AND CAST(DATEADD(day, 30, GETDATE()) AS DATE);
Notes:
Always explicitly list the columns you are inserting. It is very easy to make a mistake if you do not do so.
Only include the columns that actually have values; the rest will be defaulted to NULL (presumably).
Give your tables meaningful table aliases such as c for Customers, not VIP.
When using DATEADD(), explicitly write out the date part (day rather than d).
Variables are not needed for this query.

MSSQL After Insert Trigger not executing after database restore

I restored a SQL 2k5 Database to a SQL 2014 server (Note this is a SQL Web Edition instance) and it does not seem to execute at all.
CREATE TRIGGER [dbo].[tInsertTransactionsSnapshot]
ON [GDL_TNA].[dbo].[TRANSACTIONS]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #FirstRecord int
DECLARE #LastRecord int
DECLARE #MSTSQ int
DECLARE #EventDate varchar(50)
SET #FirstRecord = (SELECT TOP 1 T_ID FROM INSERTED ORDER BY T_ID ASC)
SET #LastRecord = (SELECT TOP 1 T_ID FROM INSERTED ORDER BY T_ID DESC)
insert into GDL_TNA.dbo.dbg(Dbgfirst,dbgLast) VALUES (#FirstRecord,#LastRecord)
WHILE #FirstRecord <= #LastRecord
BEGIN
SET #MSTSQ = (SELECT MST_SQ FROM INSERTED WHERE T_ID = #FirstRecord)
SET #EventDate = (SELECT T_Date FROM INSERTED WHERE T_ID = #FirstRecord)
IF EXISTS(SELECT NULL FROM GDL_TNA.dbo.SNAPSHOTs WHERE MST_SQ = #MSTSQ AND SS_EVENT_DATE = #EventDate AND SS_ACTIVE = 1 AND SS_TYPE = 'TRANSACTION')
BEGIN
UPDATE GDL_TNA.dbo.SNAPSHOTs SET SS_ACTIVE = 0 WHERE MST_SQ = #MSTSQ AND SS_EVENT_DATE = #EventDate AND SS_ACTIVE = 1 AND SS_TYPE = 'TRANSACTION'
END
INSERT INTO GDL_TNA.dbo.SNAPSHOTs
(
[SS_TYPE]
,[SS_EVENT]
,[MST_SQ]
,[MD_ACTIVE]
,[MST_FIRST_NAME]
,[MST_LAST_NAME]
,[MD_SHIFTSTART]
,[MD_SHIFTEND]
,[COMP_ID]
,[DEPT_ID]
,[FUNC_ID]
,[MST_PREVIOUS_FIRST_NAME]
,[MST_PREVIOUS_LAST_NAME]
,[MST_RATE_NORMAL]
,[MST_RATE_TIMEANDHALF]
,[MST_RATE_DOUBLE]
,[MST_RATE_SPECIAL]
,[SS_EVENT_ID]
,[SS_EVENT_DATE]
,[SS_EVENT_START]
,[SS_EVENT_END]
,[SS_EVENT_APPROVED]
,[SS_EVENT_RATETYPE]
)
SELECT
'TRANSACTION'
,'INSERT'
,MST_SQ
,MD_ACTIVE
,MST_FIRST_NAME
,MST_LAST_NAME
,MD_SHIFTSTART
,MD_SHIFTEND
,COMP_ID
,DEPT_ID
,FUNC_ID
,MST_PREVIOUS_FIRST_NAME
,MST_PREVIOUS_LAST_NAME
,MST_RATE_NORMAL
,MST_RATE_TIMEANDHALF
,MST_RATE_DOUBLE
,MST_RATE_SPECIAL
,(
SELECT T_ID
FROM INSERTED
WHERE T_ID = #FirstRecord
)
,(
SELECT T_Date
FROM INSERTED
WHERE T_ID = #FirstRecord
)
,ISNULL((
SELECT
CASE
WHEN len(T_TIMEIN) = 6 THEN left(T_TIMEIN,2) + ':' + right(left(T_TIMEIN,4),2) + ':' + right(T_TIMEIN,2) + '.000'
WHEN len(T_TIMEIN) < 6 THEN '0'+left(T_TIMEIN,1) + ':' + right(left(T_TIMEIN,3),2) + ':' + right(T_TIMEIN,2) + '.000'
END
FROM INSERTED
WHERE T_ID = #FirstRecord
),'00:00:00.000')
,ISNULL((
SELECT
CASE
WHEN len(T_TIMEOUT) = 6 THEN left(T_TIMEOUT,2) + ':' + right(left(T_TIMEOUT,4),2) + ':' + right(T_TIMEOUT,2) + '.000'
WHEN len(T_TIMEOUT) < 6 THEN '0'+left(T_TIMEOUT,1) + ':' + right(left(T_TIMEOUT,3),2) + ':' + right(T_TIMEOUT,2) + '.000'
END
FROM INSERTED
WHERE T_ID = #FirstRecord
),'00:00:00.000')
,1
,1
FROM GDL_TNA.dbo.MASTER_DETAILS
WHERE MST_SQ = #MSTSQ
SET #FirstRecord = #FirstRecord + 1
END
END
Steps I have Taken
Check Trigger Status: SELECT name, is_disabled FROM sys.triggers (All Enabled)
Drop & Recreate Triggers on 2014 instance
Ran a Recompile of table with sp_recompile
Any advice would be greatly appreciated.

Convert unknown number of comma separated varchars within 1 column into multiple columns

Let me say upfront that I'm a brand-spanking-new SQL Developer. I've researched this and haven't been able to find the answer.
I'm working in SSMS 2012 and I have a one-column table (axis1) with values like this:
axis1
296.90, 309.4
296.32, 309.81
296.90
300.11, 309.81, 311, 313.89, 314.00, 314.01, V61.8, V62.3
I need to convert this column into multiple columns like so:
axis1 axis2 axis3 axis4
296.90 309.4 null null
296.32 309.81 null null
296.90 null null null
300.11 309.81 311 313.89...
So far I've tried/considered:
select case when charindex(',',Axis1,1)>0
then substring(Axis1,1,CHARINDEX(',',Axis1,1)-1)
else Axis1
end as Axis1
from tablex
That works fine for a known number of column values, but there could be 0, 1, or 20+ values in this column.
Is there any way to split an unknown quantity of comma-separated values that are in one column into multiple single-value columns?
Thanks in advance for any help everyone!
I made one assumption while creating this answer, which is that you need this as a separate stored proc.
Step 1
Create a data type to enable the use of passing a table-valued parameter (TVP) into a stored proc.
use db_name
GO
create type axisTable as table
(
axis1 varchar(max)
)
GO
Step 2
Create the procedure to parse out the values.
USE [db_name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_util_parse_out_axis]
(
#axis_tbl_prelim axisTable readonly
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
declare #axis_tbl axisTable
--since TVP's are readonly, moving the data in the TVP to a local variable
--so that the update statement later on will work as expected
insert into #axis_tbl
select *
from #axis_tbl_prelim
declare #comma_cnt int
, #i int
, #sql_dyn nvarchar(max)
, #col_list nvarchar(max)
--dropping the global temp table if it already exists
if object_id('tempdb..##axis_unpvt') is not null
drop table ##axis_unpvt
create table ##axis_unpvt
(
axis_nbr varchar(25)
, row_num int
, axis_val varchar(max)
)
--getting the most commas
set #comma_cnt = (select max(len(a.axis1) - len(replace(a.axis1, ',', '')))
from #axis_tbl as a)
set #i = 1
while #i <= #comma_cnt + 1
begin --while loop
--insert the data into the "unpivot" table one parsed value at a time (all rows)
insert into ##axis_unpvt
select 'axis' + cast(#i as varchar(3))
, row_number() over (order by (select 100)) as row_num --making sure the data stays in the right row
, case when charindex(',', a.axis1, 0) = 0 and len(a.axis1) = 0 then NULL
when charindex(',', a.axis1, 0) = 0 and len(a.axis1) > 0 then a.axis1
when charindex(',', a.axis1, 0) > 0 then replace(left(a.axis1, charindex(',', a.axis1, 0)), ',', '')
else NULL
end as axis1
from #axis_tbl as a
--getting rid of the value that was just inserted from the source table
update a
set a.axis1 = case when charindex(',', a.axis1, 0) = 0 and len(a.axis1) > 0 then NULL
when charindex(',', a.axis1, 0) > 0 then rtrim(ltrim(right(a.axis1, (len(a.axis1) - charindex(',', a.axis1, 0)))))
else NULL
end
from #axis_tbl as a
where 1=1
and (charindex(',', a.axis1, 0) = 0 and len(a.axis1) > 0
or charindex(',', a.axis1, 0) > 0)
--incrementing toward terminating condition
set #i += 1
end --while loop
--getting list of what the columns will be after pivoting
set #col_list = (select stuff((select distinct ', ' + axis_nbr
from ##axis_unpvt as a
for xml path ('')),1,1,''))
--building the pivot statement
set #sql_dyn = '
select '
+ #col_list +
'
from ##axis_unpvt as a
pivot (max(a.axis_val)
for a.axis_nbr in ('
+ #col_list +
')) as p'
--executing the pivot statement
exec(#sql_dyn);
END
Step 3
Make a procedure call using the data type created in Step 1 as the parameter.
use db_name
go
declare #tvp as axisTable
insert into #tvp values ('296.90, 309.4')
insert into #tvp values ('296.32, 309.81')
insert into #tvp values ('296.90')
insert into #tvp values ('300.11, 309.81, 311, 313.89, 314.00, 314.01, V61.8, V62.3')
exec db_name.dbo.usp_util_parse_out_axis #tvp
Results from your example are as follows:

Incrementing Character value in T-sql

I have 2 set of values in a column i.e first 4 character are characters and next 4 character are numeric.
Ex:AAAA1234
Now I have to increment the value from right end i.e when numeric value reached 9999 then I have to increment character by 1 character.
Sample :
Consider the last value stored in a column is AAAA9999 then next incremented values should be in a sequence AAAB9999,....... AABZ9999,..... BZZZ9999..... ZZZZ9999(last value). And when it reaches ZZZZ9999 then I have to reset the value to AAAA0001.
How can do it in T-SQL ???
Here is a conceptual script, which does what you want. You will need to tweak it to suit your requirements
DECLARE #test table(TestValue char(8))
DECLARE #CharPart char(4),#NumPart int
SET #CharPart = 'AAAA'
SET #NumPart = 1
WHILE #NumPart <=9999
BEGIN
INSERT INTO #test
SELECT #CharPart+RIGHT(('0000'+CAST(#NumPart AS varchar(4))),4)
IF #NumPart = 9999
BEGIN
IF SUBSTRING(#CharPart,4,1)<>'Z'
BEGIN
SET #CharPart = LEFT(#CharPart,3)+CHAR(ASCII(SUBSTRING(#CharPart,4,1))+1)
SET #NumPart = 1
END
ELSE IF SUBSTRING(#CharPart,4,1)='Z' AND SUBSTRING(#CharPart,3,1) <>'Z'
BEGIN
SET #CharPart = LEFT(#CharPart,2)+CHAR(ASCII(SUBSTRING(#CharPart,3,1))+1)+RIGHT(#CharPart,1)
SET #NumPart = 1
END
ELSE IF SUBSTRING(#CharPart,3,1)='Z' AND SUBSTRING(#CharPart,2,1) <>'Z'
BEGIN
SET #CharPart = LEFT(#CharPart,1)+CHAR(ASCII(SUBSTRING(#CharPart,2,1))+1)+RIGHT(#CharPart,2)
SET #NumPart = 1
END
ELSE IF SUBSTRING(#CharPart,1,1)<>'Z'
BEGIN
SET #CharPart = CHAR(ASCII(SUBSTRING(#CharPart,1,1))+1)+RIGHT(#CharPart,3)
SET #NumPart = 1
END
ELSE IF SUBSTRING(#CharPart,1,1)='Z'
BEGIN
SET #CharPart = 'AAAA'
SET #NumPart = 1
INSERT INTO #test
SELECT #CharPart+RIGHT(('0000'+CAST(#NumPart AS varchar(4))),4)
BREAK
END
END
ELSE
BEGIN
SET #NumPart=#NumPart+1
END
END
SELECT * FROM #test
With the help of PATINDEX,SUBSTRING,ASCII functions you can achieve your special cases.
(I have found the solution for your special cases). Likewise you can add your own addition feature.
create table #temp(col1 varchar(20))
insert into #temp values('AAAA9999')
insert into #temp values('AAAZ9999')
insert into #temp values('AAZZ9999')
insert into #temp values('AZZZ9999')
insert into #temp values('ZZZZ9999')
select * from #temp
select col1,
case when cast(substring(col1,patindex('%[0-9]%',col1),len(col1)) as int) = 9999 and left(col1,4) <> 'ZZZZ'
then
case
when substring(col1,(patindex('%[0-9]%',col1)-1),1) <> 'Z' then left(col1,3)+char(ASCII(substring(col1,(patindex('%[0-9]%',col1)-1),1)) + 1)+right(col1,4)
when substring(col1,(patindex('%[0-9]%',col1)-2),1) <> 'Z' then left(col1,2)+char(ASCII(substring(col1,(patindex('%[0-9]%',col1)-2),1)) + 1)+right(col1,5)
when substring(col1,(patindex('%[0-9]%',col1)-3),1) <> 'Z' then left(col1,1)+char(ASCII(substring(col1,(patindex('%[0-9]%',col1)-3),1)) + 1)+right(col1,6)
when substring(col1,(patindex('%[0-9]%',col1)-4),1) <> 'Z' then char(ASCII(substring(col1,(patindex('%[0-9]%',col1)-4),1)) + 1)+right(col1,7)
end
else 'AAAA0001'
end as outputofcol1
--patindex('%[0-9]%',col1)-1 as charpos,
--substring(col1,(patindex('%[0-9]%',col1)-1),1) as substr4,
--substring(col1,(patindex('%[0-9]%',col1)-2),1) as substr3,
--substring(col1,(patindex('%[0-9]%',col1)-3),1) as substr2,
--substring(col1,(patindex('%[0-9]%',col1)-4),1) as substr1
--ASCII(substring(col1,(patindex('%[0-9]%',col1)-1),1)) as ASC_value
from #temp
The following function should return the desired value:
IF OBJECT_ID (N'dbo.ufnGetIndexValue') IS NOT NULL
DROP FUNCTION dbo.ufnGetIndexValue;
GO
CREATE FUNCTION dbo.ufnGetIndexValue(#MainString CHAR(8))
RETURNS CHAR(8)
AS
BEGIN
DECLARE #NumberPart INT
DECLARE #StringPart CHAR(4)
DECLARE #Position TINYINT
DECLARE #char CHAR
SET #NumberPart=CONVERT(INT,SUBSTRING(#MainString,5,8))
SET #StringPart=SUBSTRING(#MainString,1,4)
IF #NumberPart=9999
BEGIN
SET #NumberPart=1111;
SET #Position=4
WHILE #Position >= 1
BEGIN
SET #char=SUBSTRING(#StringPart,#Position,1)
IF(#char!='Z')
BEGIN
SET #char=CHAR(ASCII(#char)+1);
SET #StringPart = STUFF(#StringPart,#Position,1,#char);
BREAK;
END
SET #StringPart = STUFF(#StringPart,#Position,1,'A');
SET #Position-=1;
END
END
ELSE
BEGIN
SET #NumberPart+=1;
END
SET #MainString=#StringPart+CAST(#NumberPart AS CHAR(4));
RETURN #MainString
END
GO
Here is a scalar select function that do the increment.
CREATE FUNCTION dbo.inc_serial( #id char(8) )
RETURNS char(8) BEGIN
select #id = case when SUBSTRING(id,2,1) <> '[' then id else STUFF( id, 1, 2, char(((ascii(id)+1-65)%26)+65) + 'A' ) end from (
select case when SUBSTRING(id,3,1) <> '[' then id else STUFF( id, 2, 2, char(ascii(right(id,7))+1) + 'A' ) end as id from (
select case when SUBSTRING(id,4,1) <> '[' then id else STUFF( id, 3, 2, char(ascii(right(id,6))+1) + 'A' ) end as id from (
select
case when right(#id,4) < '9999'
then concat( left(#id,4), right(concat( '000', (cast(right(#id,4) as smallint)+1) ), 4 ) )
else concat( left(#id,3), char(ascii(right(#id,5))+1), '0001' ) end as id
) t1 ) t2 ) t3
RETURN #id
END
Basically, the code just add one to the number, and repeatingly carring overflow up to the left.
If your table always has one and only one row to be updated (e.g. an option/flag table):
UPDATE [table] SET [serial] = dbo.inc_serial( [serial] );
If your table has multiple rows, you will need an identity or high precision creation time column, so that we know where to continue from after reset.
INSERT INTO [table] (serial) VALUES ( dbo.inc_serial((
select top 1 case when count(*) > 0 then max([serial]) else 'AAAA0000' end AS id
from [table] where [id] = ( select max([id]) from [table] )
)));
For concurrency safety, use XLOCK,ROWLOCK,HOLDLOCK to lock the table.
They are obmitted from the examples for simplicity.
If you do not like udf, you can embedded the query inline.
An inline example for first case:
UPDATE [table] SET [serial] = ((
select case when SUBSTRING(id,2,1) <> '[' then id else STUFF( id, 1, 2, char(((ascii(id)+1-65)%26)+65) + 'A' ) end as id from (
select case when SUBSTRING(id,3,1) <> '[' then id else STUFF( id, 2, 2, char(ascii(right(id,7))+1) + 'A' ) end as id from (
select case when SUBSTRING(id,4,1) <> '[' then id else STUFF( id, 3, 2, char(ascii(right(id,6))+1) + 'A' ) end as id from (
select
case when right(id,4) < '9999'
then concat( left(id,4), right(concat( '000', (cast(right(id,4) as smallint)+1) ), 4 ) )
else concat( left(id,3), char(ascii(right(id,5))+1), '0001' ) end as id
from (
select top 1 [serial] as id from [table] with (XLOCK,ROWLOCK,HOLDLOCK)
) t0
) t1 ) t2 ) t3
))
The function can also be written as an inline table value function for better performance, at cost of more complex usage, but I would not border unless it frequently runs on multiple rows.

Delete Duplicate Records with Same Values

I have a TSQL statement that is taking several hours to run. I'm sure I need to look into the import process to avoid duplicates being inserted but for the time being I'd just like to remove all records except one with duplicate values. ParameterValueId is the primary key on the table but I have many duplicate entries that need to be deleted. I only need one record for each ParameterId, SiteId, MeasurementDateTime, and ParameterValue. Below is my current method for deleting duplicate records. It finds all values that have a count > 1. It then finds the first Id with those values and deletes all of the records with those values that don't match the first ID found by those values. Besides the print statements is there a more efficient way of doing this. Can I do a way with the cursor at all to improve performance?
BEGIN TRANSACTION
SET NOCOUNT ON
DECLARE #BeginningRecordCount INT
SET #BeginningRecordCount =
(
SELECT COUNT(*)
FROM ParameterValues
)
DECLARE #ParameterId UNIQUEIDENTIFIER
DECLARE #SiteId UNIQUEIDENTIFIER
DECLARE #MeasurementDateTime DATETIME
DECLARE #ParameterValue FLOAT
DECLARE CDuplicateValues CURSOR FOR
SELECT
[ParameterId]
,[SiteId]
,[MeasurementDateTime]
,[ParameterValue]
FROM [ParameterValues]
GROUP BY
[ParameterId]
,[SiteId]
,[MeasurementDateTime]
,[ParameterValue]
HAVING COUNT(*) > 1
OPEN CDuplicateValues
FETCH NEXT FROM CDuplicateValues INTO
#ParameterId
,#SiteId
,#MeasurementDateTime
,#ParameterValue
DECLARE #FirstParameterValueId UNIQUEIDENTIFIER
DECLARE #DuplicateRecordsDeleting INT
WHILE ##FETCH_STATUS <> -1
BEGIN
SET #FirstParameterValueId =
(
SELECT TOP 1 ParameterValueId
FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
)
SET #DuplicateRecordsDeleting =
(
SELECT COUNT(*)
FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
AND ParameterValueId <> #FirstParameterValueId
)
PRINT 'DELETING ' + CAST(#DuplicateRecordsDeleting AS NVARCHAR(50))
+ ' records with values ParameterId : ' + CAST(#ParameterId AS NVARCHAR(50))
+ ', SiteId : ' + CAST (#SiteId AS NVARCHAR(50))
+ ', MeasurementDateTime : ' + CAST(#MeasurementDateTime AS NVARCHAR(50))
+ ', ParameterValue : ' + CAST(#ParameterValue AS NVARCHAR(50))
DELETE FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
AND ParameterValueId <> #FirstParameterValueId
FETCH NEXT FROM CDuplicateValues INTO
#ParameterId
,#SiteId
,#MeasurementDateTime
,#ParameterValue
END
CLOSE CDuplicateValues
DEALLOCATE CDuplicateValues
DECLARE #EndingRecordCount INT
SET #EndingRecordCount =
(
SELECT COUNT(*)
FROM ParameterValues
)
PRINT 'Beginning Record Count : ' + CAST(#BeginningRecordCount AS NVARCHAR(50))
PRINT 'Ending Record Count : ' + CAST(#EndingRecordCount AS NVARCHAR(50))
PRINT 'Total Records Deleted : ' + CAST((#BeginningRecordCount - #EndingRecordCount) AS NVARCHAR(50))
SET NOCOUNT OFF
PRINT 'RUN THE COMMIT OR ROLLBACK STATEMENT AFTER VERIFYING DATA.'
--COMMIT
--ROLLBACK
Use option with CTE and OVER clause. OUTPUT.. INTO clause saves the information from rows affected by an DELETE statement into #delParameterValues table. Further, in the body of procedure, you can use this table to print the affected rows.
DECLARE #delParameterValues TABLE
(
ParameterId UNIQUEIDENTIFIER,
SiteId UNIQUEIDENTIFIER,
MeasurementDateTime DATETIME,
ParameterValue FLOAT,
DeletedRecordCount int
)
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY [ParameterId],[SiteId],[MeasurementDateTime],[ParameterValue] ORDER BY 1/0) AS rn,
COUNT(*) OVER (PARTITION BY [ParameterId],[SiteId],[MeasurementDateTime],[ParameterValue]) AS cnt
FROM [ParameterValues]
)
DELETE cte
OUTPUT DELETED.[ParameterId],
DELETED.[SiteId],
DELETED.[MeasurementDateTime],
DELETED.[ParameterValue],
DELETED.cnt INTO #delParameterValues
WHERE rn != 1
SELECT DISTINCT *
FROM #delParameterValues
Demo on SQLFiddle
you can do it in a single sql:
DELETE p FROM ParameterValues p
LEFT JOIN
(SELECT ParameterId, SiteId, MeasurementDateTime, ParameterValue, MAX(ParameterValueId) AS MAX_PARAM
FROM ParameterValues
GROUP BY ParameterId, SiteId, MeasurementDateTime, ParameterValue
) m
ON m.ParameterId = p.ParameterId
AND m.SiteId = p.SiteId
AND m.MeasurementDateTime = p.MeasurementDateTime
AND m.ParameterValue = p.ParameterValue
AND m.MAX_PARAM = p.ParameterValueId
WHERE m.ParameterId IS NULL
Of course it will not print the output, but you can still print the rows before and after