I would like to print the names of the 'only updated columns' from inside my trigger.
For instance, my table has three columns - ColA, ColB and ColC.
If I update only ColB, my trigger should print only ColB.
If I update only ColA & ColC, my trigger should print only ColA & ColC.
May I know how to achieve this in a shorter and cleaner way please?
This site helped me - https://ask.sqlservercentral.com/questions/44368/columns-updated-not-returning-the-column-name.html
create table Sample1
(
a varchar(10),
b varchar(10),
c varchar(10)
);
alter trigger TR_Sample1_Update ON Sample1 for update as
DECLARE #modifiedColumns nvarchar(max)
SET #modifiedColumns = STUFF((SELECT ',' + name FROM sys.columns WHERE object_id = OBJECT_ID('Sample1') AND COLUMNS_UPDATED() & (POWER(2, column_id - 1)) <> 0 FOR XML PATH('')), 1, 1, '')
PRINT #modifiedColumns
go
update Sample1 set a = 1 where a = 1
update Sample1 set b = 4 where a = 1
update Sample1 set c = 5 where a = 1
update Sample1 set a = 1, c = 5 where a = 1
update Sample1 set a = 1, b = 4, c = 5 where a = 1
It worked. Please try it in LinqPad.
Personally I hate triggers and try to avoid them most of the time ;-)
However, the only way I know of is with the function COLUMNS_UPDATED() inside your trigger code. See also examples like: https://sites.google.com/site/bugsarewelcome/home/columns_updated
Related
I have a stored procedure in a program that is not performing well. Its truncated version follows. The MyQuotes table has an IDENTITY column called QuoteId.
CREATE PROCEDURE InsertQuote
(#BinderNumber VARCHAR(50) = NULL,
#OtherValue VARCHAR(50))
AS
INSERT INTO MyQuotes (BinderNumber, OtherValue)
VALUES (#BinderNumber, #OtherValue);
DECLARE #QuoteId INT
SELECT #QuoteId = CONVERT(INT, SCOPE_IDENTITY());
IF #BinderNumber IS NULL
UPDATE MyQuotes
SET BinderNumber = 'ABC' + CONVERT(VARCHAR(10),#QuoteId)
WHERE QuoteId = #QuoteId;
SELECT #QuoteId AS QuoteId;
I feel like the section where we derive the binder number from the scope_identity() can be done much, much, cleaner. And I kind of think we should have been doing this in the C# code rather than the SQL, but since that die is cast, I wanted to fish for more learned opinions than my own on how you would change this query to populate that value.
The following update avoids needing the id:
UPDATE MyQuotes SET
BinderNumber = 'ABC' + CONVERT(VARCHAR(10), QuoteId)
WHERE BinderNumber is null;
If selecting QuoteId as a return query is required then using scope_identity() is as good a way as any.
Dale's answer is better, however this can be useful way too:
DECLARE #Output TABLE (ID INT);
INSERT INTO MyQuotes (BinderNumber, OtherValue) VALUES (#BinderNumber, #OtherValue) OUTPUT inserted.ID INTO #Output (ID);
UPDATE q SET q.BinderNumber = 'ABC' + CONVERT(VARCHAR(10),o.ID)
FROM MyQuotes q
INNER JOIN #Output o ON o.ID = q.ID
;
Also, if BinderNumber is always linked to ID, it would be better to just create computed column
AS 'ABC' + CONVERT(VARCHAR(10),ID)
I have a table with a column (Integer). In a MS Sql Server 2005 stored procedure I want to take the value in that column, increment it by 1 and update the column to be the incremented value. This column is a counter so each row in the table will have a different counter, IE: I can't just put a trigger on the column to auto increment it or something like that. I have some code as listed below, but all it does is update the column to be 1.
SELECT ControlNo = #c
FROM MedicaidInterchange
WHERE 1=1
AND Interkey = #InterKey;
SET #c = #c +1;
UPDATE MedicaidInterchange
SET InterDate = #d
, InterTime = #t
, ControlNo = #c
WHERE 1=1
AND Interkey = #InterKey;
Will someone please help me figure out where my code is failing? Thanks in advance for any and all help.
The reason your code doesn't work is this line:
SELECT ControlNo = #c
This is the same as:
SELECT #c as ControlNo
Per the documentation:
| column_alias = expression
You probably meant to write:
SELECT #c = ControlNo
But not sure if you need a variable here. You can refer to old values on the right hand side of SET:
UPDATE MedicaidInterchange
SET ControlNo = ControlNo + 1
WHERE Interkey = #InterKey
I searched the web but cannot find a solution for my problem (but perhaps I am using the wrong keywords ;) ).
I've got a Stored Procedure which does some automatic validation (every night) for a bunch of records. However, sometimes a user wants to do the same validation for a single record manually. I thought about calling the Stored Procedure with a parameter, when set the original SELECT statement (which loops through all the records) should get an AND operator with the specified record ID. I want to do it this way so that I don't have to copy the entire select statement and modify it just for the manual part.
The original statement is as follows:
DECLARE GenerateFacturen CURSOR LOCAL FOR
SELECT TOP 100 PERCENT becode, dtreknr, franchisebecode, franchisenemer, fakgroep, vonummer, vovolgnr, count(*) as nrVerOrd,
FaktuurEindeMaand, FaktuurEindeWeek
FROM (
SELECT becode, vonummer, vovolgnr, FaktuurEindeMaand, FaktuurEindeWeek, uitgestfaktuurdat, levdat, voomschrijving, vonetto,
faktureerperorder, dtreknr, franchisebecode, franchisenemer, fakgroep, levscandat
FROM vwOpenVerOrd WHERE becode=#BecondeIN AND levdat IS NOT NULL AND fakstatus = 0
AND isAllFaktuurStukPrijsChecked = 1 AND IsAllFaktuurVrChecked = 1
AND (uitgestfaktuurdat IS NULL OR uitgestfaktuurdat<=#FactuurDate)
) sub
WHERE faktureerperorder = 1
GROUP BY becode, dtreknr, franchisebecode, franchisenemer, fakgroep, vonummer, vovolgnr,
FaktuurEindeMaand, FaktuurEindeWeek
ORDER BY MIN(levscandat)
At the WHERE faktureerperorder = 1 I came up with something like this:
WHERE faktureerperorder = 1 AND CASE WHEN #myParameterManual = 1 THEN vonummer=#vonummer ELSE 1=1 END
But this doesn't work. The #myParameterManual indicates whether or not it should select only a specific record. The vonummer=#vonummer is the record's ID. I thought by setting 1=1 I would get all the records.
Any ideas how to achieve my goal (perhaps more efficient ideas or better ideas)?
I'm finding it difficult to read your query, but this is hopefully a simple example of what you're trying to achieve.
I've used a WHERE clause with an OR operator to give you 2 options on the filter. Using the same query you will get different outputs depending on the filter value:
CREATE TABLE #test ( id INT, val INT );
INSERT INTO #test
( id, val )
VALUES ( 1, 10 ),
( 2, 20 ),
( 3, 30 );
DECLARE #filter INT;
-- null filter returns all rows
SET #filter = NULL;
SELECT *
FROM #test
WHERE ( #filter IS NULL
AND id < 5
)
OR ( #filter IS NOT NULL
AND id = #filter
);
-- filter a specific record
SET #filter = 2;
SELECT *
FROM #test
WHERE ( #filter IS NULL
AND id < 5
)
OR ( #filter IS NOT NULL
AND id = #filter
);
DROP TABLE #test;
First query returns all:
id val
1 10
2 20
3 30
Second query returns a single row:
id val
2 20
I've got a table variable (#t_var) like this:
[RSIN] [Grp]
S-000001 1
S-000002 2
S-000003 1
C-000002 null
C-000003 null
I need to set [Grp] for "C"-types based on [Grp] for "S"-types with respective right parts. In the end I should get like this:
[RSIN] [Grp]
S-000001 1
S-000002 2
S-000003 1
C-000002 2
C-000003 1
The most obvious way I was trying to do:
UPDATE #t_var
SET [Grp] = B.[Grp]
FROM #t_var A
LEFT JOIN #t_var B
ON 'C'+RIGHT(A.[RSIN], 7) = B.[RSIN]
WHERE LEFT(A.[RSIN],1) = 'S'
But Management Studio tell me something about it can't distinct which #t_var to use. This construct works fine when we talk about physical tables, but refuses to work when it comes to table variables.
Is there any elegant workaround but to create duplicate table variable like #t_var2 and using it in join?
declare #t_var table (RSIN char(8), Grp int);
insert #t_var
values
('S-000001', 1),
('S-000002', 2),
('S-000003', 1),
('C-000002', null),
('C-000003', null)
;with x as (
select b.rsin, a.grp, b.grp as prev
from #t_var a
inner join #t_var b on 'c'+right(a.[rsin], 7) = b.[rsin]
where left(a.[rsin],1) = 's'
)
update x
set prev = grp
select * from #t_var
Table names and column names can't be dynamic. They need to stay static
You need to use Dynamic SQL here
Read more about
Building Dynamic SQL In a Stored Procedure
The Curse and Blessings of Dynamic SQL
sp_executesql
Hoe this helps
UPDATE #t_var
SET [Grp] = CASE WHEN LEFT([RSIN], 1) = 'C'
AND RIGHT([RSIN], 1) = '2'
THEN 2
WHEN LEFT([RSIN], 1) = 'C'
AND ( RIGHT([RSIN], 1) = '1'
OR
RIGHT([RSIN], 1) = '3' )
THEN 1
END
I have a table showing locations with a BIT column for each tool in use at each location:
CREATE TABLE dbo.[ToolsSelected] (
[LocationID] NVARCHAR(40) NOT NULL,
[Tool1] INTEGER DEFAULT 0 NOT NULL,
[Tool2] INTEGER DEFAULT 0 NOT NULL,
[Tool3] INTEGER DEFAULT 0 NOT NULL,
[Tool4] INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY ([LocationID])
);
LocID Tool1 Tool2 Tool3 Tool4
----- ----- ----- ----- -----
AZ 0 1 1 0
NY 1 0 1 1
I need to convert this to a table by LocationID indicating which tools at which locations:
CREATE TABLE dbo.[ByLocation] (
[LocationID] NVARCHAR(40) NOT NULL,
[Tool] NVARCHAR(40) NOT NULL, -- Column title of ToolsSelected table
PRIMARY KEY ([LocationID], [Tool])
);
LocID Tool
----- -----
AZ Tool2
AZ Tool3
NY Tool1
NY Tool3
NY Tool4
The idea is that each location can select the tools they need, I then need to query the tools table to get details (versions, etc) for each tool selected. Each location is unique; each tool is unique. Is there a way to do this or a much better implementation?
Here is the answer to the immediate question, given only 4 tools columns:
SELECT LocID = LocationID, Tool
FROM
(
SELECT LocationID, Tool = 'Tool1' FROM dbo.ToolsSelected WHERE Tool1 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool2' FROM dbo.ToolsSelected WHERE Tool2 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool3' FROM dbo.ToolsSelected WHERE Tool3 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool4' FROM dbo.ToolsSelected WHERE Tool4 = 1
) AS x
ORDER BY LocID, Tool;
With 40 columns, you could do the same thing, but along with the desire to generate this dynamically:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql += '
UNION ALL
SELECT LocationID, Tool = ''' + name + '''
FROM dbo.ToolsSelected WHERE ' + name + ' = 1'
FROM sys.columns WHERE [object_id] = OBJECT_ID('dbo.ToolsSelected')
AND name LIKE 'Tool[0-9]%';
SELECT #sql = N'SELECT LocID = LocationID, Tool
FROM
(' + STUFF(#sql, 1, 17, '') + '
) AS x ORDER BY LocID, Tool;';
PRINT #sql;
-- EXEC sp_executesql #sql;
*BUT*
Storing these as separate columns is a recipe for disaster. So when you add Tool41, Tool42 etc. you have to change the schema then change all your code that passes the column names and 1/0 via parameters etc. Why not represent these as simple numbers, e.g.
CREATE TABLE dbo.LocationTools
(
LocID NVARCHAR(40),
ToolID INT
);
So in the above case you would store:
LocID Tool
----- ----
AZ 2
AZ 3
NY 1
NY 3
NY 4
Now when you pass in the checkboxes they've selected, presumably from the front end you are receiving two values, such as:
LocID: "NY"
Tools: "Tool1, Tool5, Tool26"
If that's about right, then you can populate the table when a user creates or changes their choice, first using a split function to break up the comma-separated list dictated by the checkboxes:
CREATE FUNCTION dbo.SplitTools
(
#ToolList NVARCHAR(MAX)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT ToolID = y.i.value('(./text())[1]', 'int')
FROM
(
SELECT x = CONVERT(XML,
'<i>' + REPLACE(REPLACE(#List, ',', '</i><i>'), 'Tool', '')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
(You forgot to tell us which version of SQL Server you are using - if 2008 or above you could use a table-valued parameter as an alternative to a split function.)
Then a procedure to handle it:
CREATE PROCEDURE dbo.UpdateLocationTools
#LocID NVARCHAR(40),
#Tools NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
-- in case they had previously selected tools
-- that are no longer selected, clear first:
DELETE dbo.LocationTools WHERE LocID = #LocID;
INSERT dbo.LocationTools(LocID, ToolID)
SELECT #LocID, ToolID
FROM dbo.SplitTools(#Tools);
END
GO
Now you can add new tool #s without changing schema or code, since your list of checkboxes could also be generated from your data - assuming you have a dbo.Tools table or want to add one. This table could also be used for data integrity purposes (you could put a foreign key on dbo.LocationTools.ToolID).
And you can generate your desired query very simply:
SELECT LocID, Tool = 'Tool' + CONVERT(VARCHAR(12), ToolID)
FROM dbo.LocationTools
ORDER BY LocID, ToolID;
No redundant data, no wide tables with unmanageable columns, and a proper index can even help you search for, say, all locations using Tool3 efficiently...