SQL Update a column in a table using Trigger to a Function - sql

I am trying to auto generate a document reference number when a new document is added to an sql table - the reference number is a concatonation of some of the other fields in that table.
Looking online i can see one method is to use the dbid function to generate a uid and then create a function to concatonate and then a trigger on the table on insert to populate the column, but ive spent numerous hours and i cant get it to work.
The table has the following columns:-
Table:- dbo.codeallocations21322
Columns :-
Dbid
Projectcode
Type
Discipline]
Hdlreference
So the hdlreference column would be populated with :-
[projectcode]-[type]-[discipline]-[bdid]
With the [bdid] set to 6 characters'.
Eg 21322-rfq-mech-000001
Any help would be much appreciated / advise a better way ?
Many thanks in advance.

You can use a computed column
alter table codeallocations21322
add hdlreference
as ( projectcode + '-' + type + '-' + discipline + '-' + dbid);

Many thanks John - altered slightly to convert the Int dbID to a varchar and works great :-
alter table CodeAllocations21322
add hdlreference2
as (ProjectCode + '-' + Type + '-' + Discipline + '-' + right('00000' + Cast (dbID AS varchar(5)), 5));
Thanks again, look forward to talking to you in the future.

Related

SQL Server Dynamic SQL Query

I have a text file with about 400,000 records which have to be read, processed and inserted into a table. I'm using a stored procedure to do the same. The records are pipe separated as shown below
a | b | c .... 180 columns (1st record)
d | e | f .... 180 columns (2nd record)
.
.
.
x | y | z .....180 columns (4,00,00th record)
In the stored procedure, one insert statement was being fired for each record. I created a dynamic SQL query that would club 1,000 records in one insert but noticed that the execution time did not decrease. In fact, the SQL dynamic query created for a single record (includes isnull and cast functions for each column) takes more time than than the time taken to insert a single record into the table.
Is there a way I reduce the time taken to perform the task at hand?
EDIT
The dynamic SQL query looks something like this (just a tiny snapshot)
CAST(GETDATE() AS VARCHAR(20)) + ''',' +
CAST(#N_ER AS VARCHAR(20)) + ',' +
CAST(#N_INSDE AS VARCHAR(20)) + ',' +
CAST(#N_CODE AS VARCHAR(20)) + ',' +
CAST(#NJOB_NUMBER AS VARCHAR(30)) + ',' +
CAST(#NNUMBER AS VARCHAR(30)) + ',''' +
ISNULL(DESTINATION,'') + ''',''' +
ISNULL(#VPE_ID,'') + ''',''' +
ISNULL(dbo.fn_NEW_CARD(#VAN),'') +
Or is there a way to improve the concatenation using some other set of functions maybe?
Instead of using EXEC to run your Dynamic SQL - have you tried ExecuteSQL (with parameters) The advantage is that SQL can cache the query plan - which is a fairly significant saving on 400K inserts.
To be honest - SSIS is by far the best way to do it - Right click on the DB, select Tasks and Import Data then follow the wizard - you can even save the created package for later use in a Job.

How to create custom, dynamic string sequence in SQL Server

Is there any way to dynamically build sequences containing dates/strings/numbers in SQL Server?
In my application, I want every order to have a unique identificator that is a sequence of: Type of order, Year, Month, Incrementing number
(ex: NO/2016/10/001, NO/2016/10/002)
where NO = "Normal order", 2016 is a year, 10 is a month and 001 is an incrementing number. The idea is that it is easier for employees to comunicate using these types of identificators (of course this sequence would not be primary key of database table)
I know that I could create a stored procedure that would take Order type as an argument and return the sequence, but I'm curious if there is any better way to do it.
Cheers!
An IDENTITY column might have gaps. Just imagine an insert which is rollbacked out of any reason...
You could use ROW_NUMBER() OVER(PARTITION BY CONVERT(VARCHAR(6),OrderDate,112) ORDER BY OrderDate) in order to start a sorted numbering starting with 1 for each month. What will be best is depending on the following question: Are there parallel insert operations?
As this order name should be unique, you might run into unique-key-violations where you'd need complex mechanisms to work around...
If it is possible for you to use the existing ID you might use a scalar function together with a computed column (might be declared persistant):
CREATE TABLE OrderType(ID INT,Name VARCHAR(100),Abbr VARCHAR(2));
INSERT INTO OrderType VALUES(1,'Normal Order','NO')
,(2,'Special Order','SO');
GO
CREATE FUNCTION dbo.OrderCaption(#OrderTypeID INT,#OrderDate DATETIME,#OrderID INT)
RETURNS VARCHAR(100)
AS
BEGIN
RETURN ISNULL((SELECT Abbr FROM OrderType WHERE ID=#OrderTypeID),'#NA')
+ '/' + CAST(YEAR(#OrderDate) AS VARCHAR(4))
+ '/' + REPLACE(STR(MONTH(#OrderDate),2),' ','0')
+ '/' + REPLACE(STR(#OrderID,5),' ','0')
END
GO
CREATE TABLE YourOrder
(
ID INT IDENTITY
,OrderDate DATETIME DEFAULT(GETDATE())
,OrderTypeID INT NOT NULL --foreign key...
,Caption AS dbo.OrderCaption(OrderTypeID,OrderDate,ID)
);
GO
INSERT INTO YourOrder(OrderDate,OrderTypeID)
VALUES({ts'2016-01-01 23:23:00'},1)
,({ts'2016-02-02 12:12:00'},2)
,(GETDATE(),1);
GO
SELECT * FROM YourOrder
The result
ID OrderDate OrderTypeID Caption
1 2016-01-01 23:23:00.000 1 NO/2016/01/00001
2 2016-02-02 12:12:00.000 2 SO/2016/02/00002
3 2016-10-23 23:16:23.990 1 NO/2016/10/00003
You could create a computed column in your table definition which concatenates other values in your database into the kind of Identifier you're looking for.
Try this for a simplified example:-
CREATE TABLE Things (
[Type of Order] varchar(10),
[Year] int,
[Month] int,
[Inc Number] int identity(1,1),
[Identifier] as [Type of Order] + '/' + cast([Year] as varchar) + '/' + cast([Month] as varchar) + '/' + cast([Inc Number] as varchar)
)
insert into Things
values
('NO',2016,10)
select * from Things
If you wanted to do something more complex you could always use a trigger to update the column post insert or update.

Concate Primary Keys in SQL

I want to concate Primary Keys of multiple tables in SQL directly. I used below query to concate three primary keys with a hyphen between them but the SQL skipped the hyphen and sum up the primary keys and result in a single value.
SELECT CID + '-' + RID + '-'+ CGID As [IdCombination] ...
where CID , RID and CGID are the Primary Keys of three SQL Tables.
How it skipped the string part in query ?
Any help would be highly appreciated.
Updated
For Example : The Values of CID , RID and CGID are 3 , 4, 3 respectively. It should be 3-4-3 but the result is 10.
What is happening? Remember that + means both addition and string concatenation. It so happens that - can be interpreted as a number (like -0), so SQL Server prefers to interpret the + as addition.
Normally, when you do this type of operation, the separation character cannot be interpreted as a number, and you just get an error. I am amused that you don't get an error in this case.
One method is to explicitly cast the values as strings:
SELECT CAST(CID as VARCHAR(255)) + '-' + CAST(RID + as VARCHAR(255)) '-'+ CAST(CGID as VARCHAR(255)) As [IdCombination]
In SQL Server 2012+, you can do this more simply using CONCAT():
SELECT CONCAT(CID, '-', RID, '-', 'CGID) As [IdCombination]
CONCAT() knows that everything should be a string.
Try this
SELECT 5 + '-' + 8
The output is 13. I must admit, that I did not expect this...
And now try this
SELECT CAST('-' AS INT)
The result is 0. As your select starts with an INT, SQL Server tries to do a summa of int values. As the single hyphen is casteable to int implicitly, this returns the summa of your values...
The solution, as pointed out by others is either a cast of your column values to a string type or the usage of CONCAT
I would need to see output, but I am assuming, some ID is stored as int and it is being counted, so you should use
SELECT Cast(CID as Varchar(50)) + '-' + Cast(RID as Varchar(50)) + '-'+ Cast(CGID as Varchar(50)) As [IdCombination]

I want to create a stored procedure using T-SQL that dynamically creates a table based on outside data that isn't vulnerable to SQL injection

I have a table which has a column that represents the name of a table we'd like to create. There's a foreign key relationship to another table which has a column representing the name of the columns for the desired table (all data types assumed to be nvarchar). I'm using a stored procedure to create this table. Essentially what I'm doing is getting all of the relevant data from my tables, then building a SQL string up to generate the table, and finally using EXEC sp_executesql #CreateTableSQL.
#CreateTableSQL is generated through string concatenation like this:
SET #CreateTableSQL = 'CREATE TABLE ' + #TableName + ' (' + #ColumnString + ')';
This leaves me vulnerable to SQL injection. If someone were to use a #TableName value of:
C (t int); DROP TABLE MyTable;--
then this would drop MyTable (undesirable).
Can someone help me build this SQL and leave it invulnerable to injection? Help is greatly appreciated. Thanks!
You can make use of QUOTENAME() function which will enforce square brackets [] around the variables(Table and column names) and any value passed to these variables will only be treated as an Object name.
Something like ......
SET #CreateTableSQL = 'CREATE TABLE ' + QUOTENAME(#TableName)
+ ' (' + QUOTENAME(#ColumnString) + ')';
Now even if someone passes a value of C (t int); DROP TABLE MyTable;-- to any of these variables, the whole value C (t int); DROP TABLE MyTable;-- will still be treated as an object name.

Comparing values between two rows of data and only showing the columns that are different

In a previous application version we were using a particular field for a primary key, but because the field may represent different identities across various systems we have made it a non significant field(ie not a primary key or part of a composite primary) however since we dont have another system yet users still use that field as a primary method of identification.
The problem is with auditing...previously I used a single table to do all audits for the database dumping the data with a newvalue oldvalue schema using the generic trigger that is floating around. This could still work fine except for one thing. I have moved contactinformation into a separate table that is tied to the new primary key of the original table. So when changes are made the unfamiliar and unused primary key shows in the auditlog instead of the now insignificant foreignSystemID...
I moved to doing a one to one copy method of auditing so that any changes to any table are now written to a mirror image in a different schema. The problem comes down to showing changes to the users. They are used to seeing a report that shows only the changed values for a particular doctor...
My question would be using sql queries and Crystal reports, how could I show only the changed column values between rows in my audit tables. I have looked at the pivot command, but I dont think thats really going to help me. I had also looked at the code within the script that compares the columns and determines if they are different and writes them to the table.
im really spinning in the sand here and this is a critical issue for me to solve. Thanks in advance for ANY help...
we are early enough into production that I could change my changetracking method if need be, but it needs to be soon. thanks
EDIT:
My boss and I have worked on this a bit and this is what we have started with...I would like to get further opinions and options...as well...thanks..
CREATE TABLE #TEMP (
DoctorsID bigint,
TableName varchar(50),
FieldName varchar(50),
CurrentFieldValue varchar(255),
PreviousFieldValue varchar(255),
PreviousValueDate datetime
)
DECLARE #sql varchar(MAX)
SELECT
#sql = COALESCE(#sql,'') +
CAST(
'INSERT INTO #TEMP ' +
'SELECT ' +
'o.DoctorsID, ' +
'''' + TABLE_NAME + ''' ,' +
'''' + COLUMN_NAME + ''',' +
'o.' + COLUMN_NAME + ',' +
'a.' + COLUMN_NAME + ',' +
'a.AuditDate' +
' FROM ' +
'dbo.DoctorLicenses o ' +
'INNER JOIN Audit.DoctorLicenses a ON ' +
'o.DoctorsID = a.DoctorsID ' +
'WHERE ' +
'AuditDate BETWEEN ''10/01/2010'' AND ''10/31/2010'' AND ' +
'o.' + COLUMN_NAME + ' <> a.' + COLUMN_NAME +
';'
AS varchar(MAX))
FROM
INFORMATION_SCHEMA.COLUMNS AS [Fields]
WHERE
TABLE_SCHEMA = 'dbo' AND
TABLE_NAME = 'DoctorLicenses'
PRINT #sql
EXEC(#sql)
SELECT * FROM #TEMP
DROP TABLE #TEMP
It sounds to me like there is a design issue, but I have a hard time envisioning what your design is at the moment. Can you be more specific on what your tables look like at the moment and what data you're trying to generate the report(s) on?
Also, when talking about auditing "the changed values", how do you keep track of what's been changed?