MERGE syntax SQL Server 2012 error - sql

Have a question about the MERGE syntax for which I cannot find the answer.
I have the following case:
Step1:
create temp table #TempTbl
Step2: MERGE:
MERGE INTO T1 target
USING T2 AS source ON (bunch of columns)
WHEN MATCHED
UPDATE
SET some columns from target equal some columns from source
WHEN NOT MATCHED BY TARGET
THEN INSERT (bunch of columns)
VALUES (bunch of columns from SOURCE)
OUTPUT $action, deleted.* into #TempTbl
What I need to know is for my above steps wouldn't I find only empty data in my temporary table #TempTbl, as I only stated WHEN NOT MATCHED ... THEN INSERT, not DELETE?
Second question, what type of column should $action be, as I'm having the error message:
Column name or supplied values do not match table definition
Although I've tried to define the first column from my table both varchar(100), nvarchar(100), but with no luck. But, If I omit the $action field, then my statement works.

So, the column that will hold the $action should be nvarchar(10).
The following statement would add rows to the temp table for both insert and update (as the update is really a delete followed by an insert) but with different actions:
-- sample test data
create table t1 (col1 int, col2 int)
create table t2 (col1 int, col2 int)
insert t1 values (1,1),(2,1)
insert t2 values (2,2),(3,3)
create table #temptbl (dml_action nvarchar(10), col1 int, col2 int)
-- merge statement
merge into t1 target
using t2 as source
on target.col1 = source.col1
when matched
then update set target.col2 = source.col2
when not matched by target
then insert (col1, col2) values (source.col2, source.col2)
output $action, inserted.col1, inserted.col2 into #temptbl ;
-- sample result
select * from #temptbl
dml_action col1 col2
---------- ----------- -----------
INSERT 3 3
UPDATE 2 2
If you don't want the update rows you could wrap the entire batch into another statement like so:
insert #temptbl (dml_action, col1, col2)
select dml_action, col1, col2
from
(
merge into t1 target
using t2 as source
on target.col1 = source.col1
when matched
then update set target.col2 = source.col2
when not matched by target
then insert (col1, col2) values (source.col2, source.col2)
output $action as dml_action, inserted.col1, inserted.col2
) a
where a.dml_action = 'INSERT'

Related

How can i define a CHECK CONSTRAINT to enforce the rule of minimum 3 instances of a value?

I have a table that looks like this:
Table1(col1,col2,col3)
I want to define a CHECK CONSTRAINT that enforce a rule that there must be a minimum of 3 instances of a value in a column.
Something like this:
ALTER TABLE Athletes_Participations
ADD CONSTRAINT Check_participations CHECK (count(AID) >= 3)
The error message that get is:
An aggregate may not appear in a computed column expression or check constraint.
this is what i've tried:
CREATE FUNCTION CHECKINSTANCES()
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = COUNT(AID) FROM tbl1
RETURN #retval
END;
GO
ALTER TABLE tbl1
ADD CONSTRAINT CHK_INS CHECK (dbo.CHECKINSTANCES() >= 3 );
GO
As has been mentioned in the comments, this is messy. You can't do this with a CONSTRAINT alone, which means you either need to use a TRIGGER or a scalar function. I dislike using scalar functions in CONSTRAINTs, they have been known to tank performance, so I'm using a TRIGGER here.
Note that either implementation comes with caveats. Firstly, if you are inserting new rows you cannot insert them one by one. This will fail (as the first row will fail the pseudo CONSTRAINT). You won't be able to do task like UPDATE 1 row to have the value of new value, and INSERT 2 new rows either (with the same new value) as when the first statement completes the error will be thrown (due to there only being 1 or 2 rows). You'd have to use "proxy rows" to get around that. I demonstrate this below.
All in all, I suggest a design rethink.
CREATE TABLE dbo.YourTable (col1 int NOT NULL,
col2 varchar(10) NOT NULL,
col3 date NOT NULL);
GO
CREATE TRIGGER dbo.YourTable_Min3Col1 ON dbo.YourTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1
FROM dbo.YourTable YT
WHERE YT.Col1 IN (SELECT i.col1
FROM inserted i
UNION ALL
SELECT d.col1
FROM deleted d)
GROUP BY Col1
HAVING COUNT(Col1) < 3)
--Use an error code appropriate for you
THROW 79845, N'A check rule in the trigger ''YourTable_Min3Col1'' has failed. Less than 3 instances of a value of in the column ''Col1'' exist. The statement has been aborted.', 16;
END;
GO
--Succeeds
INSERT INTO dbo.YourTable (col1,
col2,
col3)
VALUES(1,'abc',GETDATE()),
(1,'def',GETDATE()),
(1,'xyz',GETDATE());
GO
--Fails
INSERT INTO dbo.YourTable (col1,
col2,
col3)
VALUES(2,'abc',GETDATE()),
(2,'def',GETDATE());
GO
--Succeeds
INSERT INTO dbo.YourTable (col1,
col2,
col3)
VALUES(3,'abc',GETDATE()),
(3,'def',GETDATE()),
(3,'xyz',GETDATE()),
(3,'opq',GETDATE());
GO
--Fails, despite that at the end of the batch there would be 3 rows with a value of 4
INSERT INTO dbo.YourTable (col1,
col2,
col3)
VALUES(4,'abc',GETDATE()),
(4,'def',GETDATE());
UPDATE dbo.YourTable
SET col1 = 4
WHERE col1 = 3
AND col2 = 'opq';
GO
--Use a "proxy" row to enforce it works
INSERT INTO dbo.YourTable (col1,
col2,
col3)
VALUES(4,'abc',GETDATE()),
(4,'def',GETDATE()),
(4,'def','19000101');
UPDATE dbo.YourTable
SET col1 = 4
WHERE col1 = 3
AND col2 = 'opq';
DELETE
FROM dbo.YourTable
WHERE Col1 = 4
AND Col3 = '19000101';
GO
--Fails, would reduce below 3
DELETE
FROM dbo.YourTable
WHERE col1 = 1
AND col2 = 'xyz';
GO
SELECT *
FROM dbo.YourTable;
GO
DROP TABLE dbo.YourTable;
db<>fiddle

Create automatically generated timestamp column in table?

How to create a automatically generated timestamp column in table in Microsoft SQL Server 2019? Timestamp column should be automatically generated when I insert or update table.
In IBM Db2 database the syntax is the following:
create table myschema.mytable (col1 int, col2 timestamp not null generated always for each row on update as row change timestamp
insert into myschema.mytable (col1) values (1)
update myschema.mytable set col1 = 2
After insert/update of column col1, column col2 is automatically generated as current timestamp.
In Microsoft SQL Server you can try this code:
CREATE TABLE myschema.mytable
(
col1 int,
col2 datetime not null default(current_timestamp)
)
INSERT INTO myschema.mytable(col1) VALUES (1)
UPDATE myschema.mytable SET col1 = 2
SELECT * FROM myschema.mytable
Update:
Let's create temporary table for test
DECLARE #mytable TABLE
(
col1 int,
col2 datetime not null default(current_timestamp)
)
INSERT INTO #mytable(col1) VALUES (1)
SELECT * FROM #mytable
UPDATE #mytable SET col1 = 2
SELECT * FROM #mytable

How to update a column with values depending upon conditions - Oracle

My requirement is to update a column with 'Y' if a set of conditions are met or with 'N'. I want to do it in the same query.
For example :
CREATE TABLE TAB1 (COL1 INT, COL2 INT);
CREATE TABLE TAB2 (COL1 INT);
INSERT INTO TAB1 VALUES(1, 3);
INSERT INTO TAB1 VALUES(2, 3);
INSERT INTO TAB2 VALUES(1);
INSERT INTO TAB2 VALUES(1);
I want to update the COL2 of TAB1 with 'Y' if TAB1.COL1 matches with the TAB2.COL1 or COL2 should be updated with 'N'. This of course a simple example and the actual requirement far more complex.
Edit: Corrected my answer to update 0 as well.
Assuming Y as 1 and N as 0 (since you said COL2 is INT)
UPDATE TAB1 T1
SET T1.COL2 =
CASE WHEN EXISTS(SELECT 1 FROM TAB2 T2 WHERE T2.COL1 = T1.COL1) THEN 1
ELSE 0
END;
Here is the SQLFiddle.

How do I update a table based off the generated index key of an insert?

I'm created a temp table with most of the values I need to insert into a set of tables. From this temp table I have all the values I need for the insert to the first table, but the insert to the next table depends on the identity key generated by the insert to the first table.
I could very well just update my temp table after the first insert, but I'd like to try using the output clause.
I want something like this:
INSERT INTO Table1
<values from temp table>
OUTPUT <update my temp table with generated identity keys>
INSERT INTO Table2
<values from temp table including the output updated id column>
I think you better create another temp table (OR) table type variable and go from there as shown below. Cause I don't think you can update the same temp table from where you are inserting using output clause.
CREATE TABLE TestTable (ID INT not null identity primary key,
TEXTVal VARCHAR(100))
create TABLE #tmp(ID INT, TEXTVal VARCHAR(100))
create TABLE #tmp1(ID INT, TEXTVal VARCHAR(100))
CREATE TABLE TestTable1 (ID INT not null, TEXTVal VARCHAR(100))
INSERT #tmp (ID, TEXTVal)
VALUES (1,'FirstVal')
INSERT #tmp (ID, TEXTVal)
VALUES (2,'SecondVal')
INSERT INTO TestTable (TEXTVal)
OUTPUT Inserted.ID, Inserted.TEXTVal INTO #tmp1
select TEXTVal from #tmp
INSERT INTO TestTable1 (ID, TEXTVal)
select ID, TEXTVal from #tmp1
You could merge your temptable into Table1, and output the results to a variable table, then insert the original data joined to the variable table into Table2.
Example:
DECLARE #MyIDs TABLE (TempTableID int NOT NULL, Table1ID int NOT NULL)
MERGE INTO Table1
USING TempTable AS Tmp
ON Table1.SomeValue = Tmp.SomeValue
WHEN NOT MATCHED THEN
INSERT (col1, col2, col3, col4, col5)
VALUES (tmp.col1, tmp.col2, tmp.col3, tmp.col4, tmp.col5)
OUTPUT Tmp.ID
,Table1.ID
INTO #MyIDs;
INSERT INTO Table2 (col1, col2, col3, col4, col5, Table1ID)
SELECT tmp.col1, tmp.col2, tmp.col3, tmp.col4, tmp.col5, new.Table1ID
FROM TempTable tmp
JOIN #MyIDs new ON tmp.ID = new.TempTableID

How to know that MERGE operation was INSERT or UPDATE?

Let say I have,
MERGE INTO SHARE_AD_GROUP A
USING (
SELECT SHARE_AD_GROUP_ID,
SHARE_ID,
AD_GROUP,
SHARE_PERMISSIONS
FROM SHARE_AD_GROUP
WHERE SHARE_ID = #shareID AND AD_GROUP = #ownerId
) B ON (A.SHARE_AD_GROUP_ID = B.SHARE_AD_GROUP_ID)
WHEN MATCHED THEN
UPDATE SET A.SHARE_PERMISSIONS = B.SHARE_PERMISSIONS
WHEN NOT MATCHED THEN
INSERT (SHARE_PERMISSIONS) VALUES(#sharePermissions);
-- In Here how do I know that it is insert or update
How to know that MERGE operation was INSERT or UPDATE after INSERT OR UPDATE?
Please refer here
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(20));
MERGE tblTarget AS Target
USING (SELECT Col1,Col2 FROM tblSource)
AS Source
ON (Target.Col1 = Source.Col1)
WHEN MATCHED THEN
UPDATE SET target.Col2 = source.Col2 -- Need to get affected rows here
WHEN NOT MATCHED BY TARGET THEN
INSERT (Col1,Col2) VALUES (Col1,Col2); -- Need to get affected rows here
OUTPUT $action INTO #SummaryOfChanges;
SELECT Change, COUNT(*) AS CountPerChange
FROM #SummaryOfChanges
GROUP BY Change;