Read uncommited data in smple connect - sql

Is it possible (in SQL SERVER 2012) to read data before update in one transaction.
Example:
I have one table, name: tab with two columns col1 and col2. I have one record: col1 = 1 and col2 = 'a'
begin transaction
update tab set col2 = 'A' where col1 = 1
-- here i want to read data before update (in this example 'a')
-- here i want to read data after update (in this example 'A')
Committ transaction
Before committ transaction when using select always i get data after update (in this example 'A'). I try to do
select * from tab with(nolock)
but it doesn't work.
Question: In section: after update and before committ - can i read data which was before update ?
Thanks.

Locking hints determine how nicely you read data that is being updated by another transaction. But code that runs in a transaction always sees changes made earlier in the same transaction.
If you need the old state, why not store the old version in a variable? Like:
begin transaction
declare #old_col2 int
select #old_col2 = col2 from tab where id = 1
update tab set col2 = 'A' where id = 1
... now you can access both the old and the new data ...

You can achieve the same using below sample :
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table(
EmpID int NOT NULL,
OldVacationHours int,
NewVacationHours int,
ModifiedDate datetime);
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25,
ModifiedDate = GETDATE()
OUTPUT inserted.BusinessEntityID,
deleted.VacationHours,
inserted.VacationHours,
inserted.ModifiedDate
INTO #MyTableVar;
--Display the result set of the table variable.
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate
FROM #MyTableVar;
GO
--Display the result set of the table.
SELECT TOP (10) BusinessEntityID, VacationHours, ModifiedDate
FROM HumanResources.Employee;
GO
This is elegant solution instead of writing seperate SELECTs in the code.
Reference http://msdn.microsoft.com/en-IN/library/ms177564.aspx

Related

Enumerate the multiple rows in a multi-update Trigger

I have something like the table below:
CREATE TABLE updates (
id INT PRIMARY KEY IDENTITY (1, 1),
name VARCHAR (50) NOT NULL,
updated DATETIME
);
And I'm updating it like so:
INSERT INTO updates (name, updated)
VALUES
('fred', '2020-11-11),
('fred', '2020-11-11'),
...
('bert', '2020-11-11');
I need to write an after update Trigger and enumerate all the name(s) that were added and add each one to another table but can't work out how enumerate each one.
EDIT: - thanks to those who pointed me in the right direction, I know very little SQL.
What I need to do is something like this
foreach name in inserted
look it up in another table and
retrieve a count of the updates a 'name' has done
add 1 to the count
and update it back into the other table
I can't get to my laptop at the moment, but presumably I can do something like:
BEGIN
SET #count = (SELECT UCount from OTHERTAB WHERE name = ins.name)
SET #count = #count + 1
UPDATE OTHERTAB SET UCount = #count WHERE name = ins.name
SELECT ins.name
FROM inserted ins;
END
and that would work for each name in the update?
Obviously I'll have to read up on set based SQL processing.
Thanks all for the help and pointers.
Based on your edits you would do something like the following... set based is a mindset, so you don't need to compute the count in advance (in fact you can't). It's not clear whether you are counting in the same table or another table - but I'm sure you can work it out.
Points:
Use the Inserted table to determine what rows to update
Use a sub-query to calculate the new value if its a second table, taking into account the possibility of null
If you are really using the same table, then this should work
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE(UCount,0) + 1
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
If however you are using a second table then this should work:
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE((SELECT UCount+1 from OTHERTAB T2 WHERE T2.[name] = OTHERTAB.[name]),0)
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
Using inserted and set-based approach(no need for loop):
CREATE TRIGGER trg
ON updates
AFTER INSERT
AS
BEGIN
INSERT INTO tab2(name)
SELECT name
FROM inserted;
END

In SQL Server 2008 R2, is there a way to create a custom auto increment identity field without using IDENTITY(1,1)?

I would like to be able to pull the custom key value from a table, but would also like it to perform like SQL Server's IDENTITY(1,1) column on inserts.
The custom key is for another application and will need to be used by different functions so the value will need to be pulled from a table and available for other areas.
Here are some if my attempts:
Tried a trigger on the table works well on single inserts, failed on using SQL insert (forgetting the fact that a triggers are not per row but by batch)
ALTER TRIGGER [sales].[trg_NextInvoiceDocNo]
ON [sales].[Invoice]
AFTER INSERT
AS
BEGIN
DECLARE #ResultVar VARCHAR(25)
DECLARE #Key VARCHAR(25)
EXEC [dbo].[usp_GetNextKeyCounterChar]
#tcForTbl = 'docNbr', #tcForGrp = 'docNbr', #NewKey = #ResultVar OUTPUT
UPDATE sales.InvoiceRET
SET DocNbr = #ResultVar
FROM sales.InvoiceRET
JOIN inserted ON inserted.id = sales.InvoiceRET.id;
END;
Thought about a scalar function, but functions cannot exec stored procedures or update statements in order to set the new key value in the lookup table.
Thanks
You can use ROW_NUMBER() depending on the type of concurrency you are dealing with. Here is some sample data and a demo you can run locally.
-- Sample table
USE tempdb
GO
IF OBJECT_ID('dbo.sometable','U') IS NOT NULL DROP TABLE dbo.sometable;
GO
CREATE TABLE dbo.sometable
(
SomeId INT NULL,
Col1 INT NOT NULL
);
GO
-- Stored Proc to insert data
CREATE PROC dbo.InsertProc #output BIT AS
BEGIN -- Your proc starts here
INSERT dbo.sometable(Col1)
SELECT datasource.[value]
FROM (VALUES(CHECKSUM(NEWID())%100)) AS datasource([value]) -- simulating data from somewhere
CROSS APPLY (VALUES(1),(1),(1)) AS x(x);
WITH
id(MaxId) AS (SELECT ISNULL(MAX(t.SomeId),0) FROM dbo.sometable AS t),
xx AS
(
SELECT s.SomeId, RN = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))+id.MaxId, s.Col1, id.MaxId
FROM id AS id
CROSS JOIN dbo.sometable AS s
WHERE s.SomeId IS NULL
)
UPDATE xx SET xx.SomeId = xx.RN;
IF #output = 1
SELECT t.* FROM dbo.sometable AS t;
END
GO
Each time I run: EXEC dbo.InsertProc 1; it returns 3 more rows with the correct ID col. Each time I execute it, it adds more rows and auto-increments as needed.
SomeId Col1
-------- ------
1 62
2 73
3 -17

Output Always 1

I have a stored procedure and am using a Merge Statement to Insert and Update. This aspect is working as I require.
However, the output when inserting the record is always 1 and I cannot see why? I would be grateful if someone could review this procedure and let me know what I could be doing wrong,.
CREATE PROCEDURE [dbo].[FileAdd]
#FileId int,
#FileData varbinary(max),
#ContentType Varchar(100),
#OperatorId int
AS
BEGIN
--In Memory Table to
DECLARE #MergeOutput TABLE
(
Id INT
);
--Merge needs a table to Merge with so using a CTE
WITH CTE AS (
SELECT #FileId as FileId)
--Merge
MERGE INTO [dbo].[Files] as T
USING CTE AS S
ON T.FileId = S.FileId
WHEN NOT MATCHED THEN
INSERT (
FileData,
ContentType,
OperatorIdCreated,
OperatorIdUpdated
)
VALUES(
#FileData,
#ContentType,
#OperatorId,
#OperatorId
)
WHEN MATCHED THEN
UPDATE SET
FileData = #FileData,
ContentType= #ContentType,
OperatorIdUpdated = #OperatorId,
Updated = GetDate()
OUTPUT
INSERTED.FileId
INTO #MergeOutput;
SELECT * FROM #MergeOutput;
END
GO
The reason you are getting 1 is because that is what is being UPDATED or INSERTED. When it's the UPDATED value, then it is the value are passing into #FileID.
With the OUTPUT clause:
INSERTED Is a column prefix that specifies the value added by the
insert or update operation.
Thus, what ever value is UPDATED (which is #FileID) or INSERTED (which will be whatever your FileID table logic is) this will be returned in your code. If you are always getting 1, then you must me always updating the column for FileID = 1.
Changing your bottom to inserted.* would show you this, as it would OUTPUT the updated row.
Check the demo here.

SQL Server UPDATE Column if Same CODE and DeptID Don't Exist

When I execute a Procedure I would like to UPDATE table1 and SET the new values for the CODE, NUMBER, and ADDRESS columns only if the same CODE and DeptID do not exist. If I change the CODE to an existing name in table1 it is OK as long as the DeptID is different.
Example:
Say I want to change Beta to Delta. That is fine because they have different DeptIDs. So I want to UPDATE everything, aka the third row in my example would now have the values (1, Delta, 'whateverNUMBER', 'whateverADDRESS')
But if I wanted to take that same third row Beta and change the CODE to Alpha I don't want to allow that and I don't want to SET the NUMBER or ADDRESS either because there is already a row with CODE Alpha and DeptID 1.
How would I be able to accomplish this?
Here is one of my attempts which does not work:
UPDATE dbo.table1
SET
CODE = #CODE
,NUMBER = #NUMBER
,ADDRESS = #ADDRESS
WHERE ID = #ID
AND NOT EXISTS
(
SELECT NAME FROM dbo.table1
WHERE NAME = #NAME
AND ID = #ID
)
This should work if you have the commas in the right place for syntax and specify the old code:
UPDATE dbo.table1
SET CODE = #CODE,
NUMBER = #NUMBER,
ADDRESS = #ADDRESS
WHERE ID = #ID AND
CODE = #OLDCODE AND
NOT EXISTS (SELECT 1
FROM dbo.table1
WHERE NAME = #NAME AND ID = #ID
);
The easiest would be to add a unique constraint for columns DeptId and Code.
This will prevent any duplicate insert as well as an update to already existing values.
The constraint will even prevent any changes performed directly against the table and not just within your update statement.

IF UPDATE() in SQL server trigger

If there's:
IF UPDATE (col1)
...in the SQL server trigger on a table, does it return true only if col1 has been changed or been updated?
I have a regular update query like
UPDATE table-name
SET col1 = 'x',
col2 = 'y'
WHERE id = 999
Now what my concern is if the "col1" was 'x' previously then again we updated it to 'x'
would IF UPDATE ("col1") trigger return True or not?
I am facing this problem as my save query is generic for all columns, but when I add this condition it returns True even if it's not changed...So I am concerned what to do in this case if I want to add condition like that?
It returns true if a column was updated. An update means that the query has SET the value of the column. Whether the previous value was the same as the new value is largely irelevant.
UPDATE table SET col = col
it's an update.
UPDATE table SET col = 99
when the col already had value 99 also it's an update.
Within the trigger, you have access to two internal tables that may help. The 'inserted' table includes the new version of each affected row, The 'deleted' table includes the original version of each row. You can compare the values in these tables to see if your field value was actually changed.
Here's a quick way to scan the rows to see if ANY column changed before deciding to run the contents of a trigger. This can be useful for example when you want to write a history record, but you don't want to do it if nothing really changed.
We use this all the time in ETL importing processes where we may re-import data but if nothing really changed in the source file we don't want to create a new history record.
CREATE TRIGGER [dbo].[TR_my_table_create_history]
ON [dbo].[my_table] FOR UPDATE AS
BEGIN
--
-- Insert the old data row if any column data changed
--
INSERT INTO [my_table_history]
SELECT d.*
FROM deleted d
INNER JOIN inserted i ON i.[id] = d.[id]
--
-- Use INTERSECT to see if anything REALLY changed
--
WHERE NOT EXISTS( SELECT i.* INTERSECT SELECT d.* )
END
Note that this particular trigger assumes that your source table (the one triggering the trigger) and the history table have identical column layouts.
What you do is check for different values in the inserted and deleted tables rather than use updated() (Don't forget to account for nulls). Or you could stop doing unneeded updates.
Trigger:
CREATE TRIGGER boo ON status2 FOR UPDATE AS
IF UPDATE (id)
BEGIN
SELECT 'DETECT';
END;
Usage:
UPDATE status2 SET name = 'K' WHERE name= 'T' --no action
UPDATE status2 SET name = 'T' ,id= 8 WHERE name= 'K' --detect
To shortcut the "No actual update" case, you need also check at the beginning whether your query affected any rows at all:
set nocount on; -- this must be the first statement!
if not exists (select 1 from inserted) and not exists (select 1 from deleted)
return;
SET NOCOUNT ON;
declare #countTemp int
select #countTemp = Count (*) from (
select City,PostCode,Street,CountryId,Address1 from Deleted
union
select City,PostCode,Street,CountryId,Address1 from Inserted
) tempTable
IF ( #countTemp > 1 )
Begin
-- Your Code goes Here
End
-- if any of these "City,PostCode,Street,CountryId,Address1" got updated then trigger
-- will work in " IF ( #countTemp > 1 ) " Code)
This worked for me
DECLARE #LongDescDirty bit = 0
Declare #old varchar(4000) = (SELECT LongDescription from deleted)
Declare #new varchar(4000) = (SELECT LongDescription from inserted)
if (#old <> #new)
BEGIN
SET #LongDescDirty = 1
END
Update table
Set LongDescUpdated = #LongDescUpdated
.....