How can I do a merge of two tables in SQL Server? - sql-server

I have two tables with schemas like this:
CREATE TABLE [dbo].[WordsA] (
[WordId] INT IDENTITY (1, 1) NOT NULL,
[Word] NVARCHAR (MAX) NOT NULL
[FromWordA] BIT NULL,
[FromWordB] BIT NULL
);
CREATE TABLE [dbo].[WordsB] (
[WordId] INT IDENTITY (1, 1) NOT NULL,
[Word] NVARCHAR (MAX) NOT NULL
);
How can I take the contents of table WordsB and insert into WordsA row by row:
If Word does not exist in WordsA
Insert into WordsA and set FromWordB = 1
If Word exists in WordsA
Update WordsA setting FromWordB = 1

You need MERGE:
MERGE [dbo].[WordsA] as target
USING [dbo].[WordsB] as source
ON target.[Word] = source.[Word]
WHEN MATCHED THEN
UPDATE SET [FromWordB] = 1
WHEN NOT MATCHED THEN
INSERT ([Word],[FromWordA],[FromWordb])
VALUES (source.[Word],0,1);

Give this a try (not tested):
MERGE WordsA A
USING WordsB B
ON A.WordId = B.WordID
WHEN NOT MATCHED BY SOURCE THEN
INSERT (Word, FromWordB)
VALUES (B.Word, 1)
WHEN MATCHED THEN
UPDATE SET FromWordB = 1
;

Related

Temp tables, Column name or number of supplied values does not match table definition

Even though this tends to look as a duplicate, I had to post it as I can't seem to spot the error.
I don't know if I am mad or what but I can't seem to spot why there is a mismatch in the number of supplied values.
Here are they:
CREATE TABLE #TIPSTOPE_TS
(
TIP INT NULL,
SIFVAL VARCHAR(5),
GRUPA INT NULL,
DATUMOD VARCHAR(15),
PASIVNA DECIMAL(15,4) NULL DEFAULT(0),
REDOVNA DECIMAL(15,4) NULL DEFAULT(0),
ZATEZNA DECIMAL(15,4) NULL DEFAULT(0),
STOPA DECIMAL(15,4) NULL DEFAULT(0),
DATUMDO VARCHAR(15),
KONTO VARCHAR(15),
)
INSERT INTO #TIPSTOPE_TS
SELECT TS.TIP,
TS.SIFVAL,
TS.GRUPA,
CASE WHEN ISDATE(MAX(TS.DATUMOD)) = 0 THEN '2017.12.31' ELSE MAX(TS.DATUMOD) END AS DATUMOD,
CAST (2 AS DECIMAL(10,4)) AS PASIVNA,
CAST (1 AS DECIMAL(10,4)) AS REDOVNA,
CAST (3 AS DECIMAL(10,4)) AS ZATEZNA,
TS.REDOVNA,
TS.DATUMDO,
TP.M1 AS KONTO
FROM TIPSTOPE TS WITH(NOLOCK)
JOIN TIPPART TP WITH(NOLOCK) ON TP.TIP = TS.TIP
WHERE TS.DATUMOD <= '2017.12.31'
GROUP BY TS.TIP,TS.SIFVAL,TS.GRUPA,TP.M1,TS.DATUMDO,TS.REDOVNA
CREATE NONCLUSTERED INDEX IX_TIPSTOPE_TS ON #TIPSTOPE_TS (TIP, GRUPA, SIFVAL)
INCLUDE (DATUMOD)
And the second one...
CREATE TABLE #UNPVT_TIPSTOPE_TS
(
TIP INT NULL,
SIFVAL VARCHAR(5) NULL,
GRUPA INT NULL,
DATUMOD VARCHAR(10) NULL,
TIP_KS VARCHAR(15) NULL,
KAMATNA_STOPA DECIMAL(15,4) NULL DEFAULT(0),
DATUMDO VARCHAR(10) NULL,
)
INSERT INTO #UNPVT_TIPSOPE_TS
SELECT TIP, SIFVAL, GRUPA, DATUMOD, TIP_KS, KAMATNA_STOPA,DATUMDO
FROM
(
SELECT TIP, SIFVAL, GRUPA, DATUMOD, ISNULL(REDOVNA,0) AS REDOVNA, ISNULL(PASIVNA,0) AS PASIVNA, ISNULL(ZATEZNA,0) AS ZATEZNA,STOPA,DATUMDO
FROM #TIPSTOPE_TS
) P
UNPIVOT (KAMATNA_STOPA FOR TIP_KS IN (REDOVNA, PASIVNA, ZATEZNA)) AS UNPVT
The second temp tables is taking data from the first one.
When I try to create the second one error is thrown:
Insert error: Column name or number of supplied values does not match table definition
You are specifying the exact number of values that are needed. If you copy the whole code in new query window and execute it, it will work. Or in your current window drop the table table:
DROP TABLE #TIPSTOPE_TS;
DROP TABLE #UNPVT_TIPSTOPE_TS;
I mean execute only the above statements, and the execute the rest of the code. It should work again.
Sometime, when are debugging we forgot that the temporary table meta data is cached. For example, you can have the following code:
DROP TABLE IF EXISTS #TEST;
CREATE TABLE #TEST
(
[A] INT
);
INSERT INTO #TEST ([A])
SELECT 1;
And its valid. If we change it to this:
DROP TABLE IF EXISTS #TEST;
CREATE TABLE #TEST
(
[A] INT
,[B] INT
);
INSERT INTO #TEST ([A], [B])
SELECT 1, 2;
We will get:
Msg 207, Level 16, State 1, Line 9 Invalid column name 'B'.
Because, in the current session the #TEST table already exists and the engine is able to check that the B column does not exists. So, we need to drop the table manually, after the columns are changed, or we need to drop the tables at the end of our code statements.

Make a column receives 0 or 1 based on the value of another column

I want to know if it's possible to create a column in a table, that get's their value automatically based on the value of another column in the same table, example below to clarify:
CREATE TABLE dbo.example
(
m_id INT NOT NULL CONSTRAINT PK_mid PRIMARY KEY IDENTITY (1,1),
m_name NVARCHAR(30) NOT NULL,
m_startdate DATE NOT NULL CONSTRAINT CHK_startdate CHECK(m_startdate <= SYSDATETIME()),
m_enddate DATE CONSTRAINT CHK_enddate CHECK(m_enddate <= SYSDATETIME()),
m_status INT CONSTRAINT CHK_status CHECK(m_status = 0 or m_status = 1)
)
I want to make m_status receive 0 if m_enddate is null and 1 if it's not null. This of course would be upon an insert of a row.
You could use a calculated column as follows...
CREATE TABLE dbo.example
(
m_id INT NOT NULL CONSTRAINT PK_mid PRIMARY KEY IDENTITY (1,1),
m_name NVARCHAR(30) NOT NULL,
m_startdate DATE NOT NULL CONSTRAINT CHK_startdate CHECK(m_startdate <= SYSDATETIME()),
m_enddate DATE CONSTRAINT CHK_enddate CHECK(m_enddate <= SYSDATETIME()),
m_status AS CASE
WHEN m_enddate is null THEN 0 ELSE 1
END
)
A calculated column as Michael suggested is best, but in case that you cannot alter the table then you could make a view for this.
CREATE VIEW dbo.vwExample AS
SELECT m_id,
m_name,
m_startdate,
m_enddate,
CASE WHEN m_enddate is null THEN 0 ELSE 1 END as m_status
FROM dbo.example
Now you can do
select * from dbo.vwExample
and it will have the correct value for m_status without having to alter the table itself.
In T-SQL ,you could use Instead of insert/update triggers, which allow you to intercept the inserts and updates and inject logic.
CREATE TRIGGER dbo.exampleInsertTrigger
ON [dbo].[example]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO [dbo].[example]
([m_name]
,[m_startdate]
,[m_enddate]
,[m_status])
SELECT
[m_name]
,[m_startdate]
,[m_enddate]
,CASE
WHEN [m_enddate] is null THEN 0 ELSE 1
END
FROM inserted
END
CREATE TRIGGER dbo.exampleUpdateTrigger
ON dbo.example
INSTEAD OF UPDATE
AS
BEGIN
UPDATE [dbo].[example]
SET
[m_name] = inserted.[m_name]
,[m_startdate] = inserted.[m_startdate]
,[m_enddate] = inserted.[m_enddate]
,[m_status] = CASE
WHEN inserted.[m_enddate] is null THEN 0 ELSE 1
END
FROM inserted
WHERE inserted.[m_id] = [dbo].[example].[m_id]
END

TSQL merge fails for for identity column even if code is not run through

I've written an application which creates T-SQL code to merge data from one database to another. The structure in of both databases are equal.
My generated code fails if the table I want insert to has an autoincrement column.
I solved it temporarily like this: if the table contains no autoincrement column, I just merge it. Else I move the data from the target table, insert the new one and just recreate the missing from a buffer of the old table.
IF EXISTS (
SELECT *
FROM [TargetDB].[dbo].syscolumns
WHERE id = Object_ID('[TargetDB].[dbo].[TargetTable]')
AND colstat & 1 = 1
)
BEGIN
SET IDENTITY_INSERT [TargetDB].[dbo].[TargetTable] ON
END
IF EXISTS (
SELECT *
FROM [TargetDB].[dbo].syscolumns
WHERE id = Object_ID('[TargetDB].[dbo].[TargetTable]')
AND colstat & 1 = 1
)
BEGIN
print 'Path1'
SELECT *
INTO [TargetDB].[dbo].[SourceTable_Copy]
FROM [TargetDB].[dbo].[TargetTable]
TRUNCATE TABLE [TargetDB].[dbo].[TargetTable]
INSERT INTO [TargetDB].[dbo].[TargetTable] (
[ID]
,[RecordID]
,[Another Info]
)
SELECT [ID]
,[RecordID]
FROM [LinkedServer].[SourceDB].[dbo].[SourceTable]
MERGE [TargetDB].[dbo].[TargetTable] AS TR
USING [TargetDB].[dbo].[SourceTable_Copy] AS SR
ON ((TR.[ID] = SR.[ID]))
WHEN NOT MATCHED BY TARGET
THEN
INSERT (
[ID]
,[RecordID]
,[Another Info]
)
VALUES (
[ID]
,[RecordID]
,[Another Info]
);
DROP TABLE [TargetDB].[dbo].[SourceTable_Copy];
END
ELSE
BEGIN
print 'Path2'
MERGE [TargetDB].[dbo].[TargetTable] AS TR
USING [LinkedServer].[SourceDB].[dbo].[SourceTable] AS SR
ON ((TR.[ID] = SR.[ID]))
WHEN MATCHED
THEN
UPDATE
SET TR.[RecordID] = SR.[RecordID]
,TR.[Another Info] = SR.[Another Info]
WHEN NOT MATCHED BY TARGET
THEN
INSERT (
[ID]
,[RecordID]
,[Another Info]
)
VALUES (
SR.[ID]
,SR.[RecordID]
,SR.[Another Info]
);
END
IF EXISTS (
SELECT *
FROM [TargetDB].[dbo].syscolumns
WHERE id = Object_ID('[TargetDB].[dbo].[TargetTable]')
AND colstat & 1 = 1
)
BEGIN
SET IDENTITY_INSERT [TargetDB].[dbo].[TargetTable] OFF
END
GO
My problem is that the code fails with this error:
Msg 8102, Level 16, State 1, Line 64
Cannot update identity column 'Another Info'.
The error occurs even if the code is not run through. If I comment out the merge statement it returns "Path1".
Did anybody knows this problem and/or can provide a solution or some ideas?
UPDATE:
I edited the upper code and error message to indicate that the problem occurs on non primary key fields to.

MERGE and condition on the match

I have a table type that I am sending as a variable in my stored procedure.
CREATE TYPE OperationKeysTableType AS TABLE
(
[KeysId] [int] NOT NULL,
[OperationId] [int] NOT NULL,
[IsChecked] [bit] NOT NULL
)
And then I want to use this table to do a search, if they match or not, insert or delete but only if it is not checked like so:
MERGE INTO dbo.tblOperationKeys AS T
USING (SELECT VT.KeysId, VT.OperationId as OperationId FROM #ValuesTable AS VT) AS S
ON T.KeysId = S.KeysId AND T.OperationId = S.OperationId
WHEN MATCHED AND S.IsChecked = 0 THEN --DELETE
DELETE
WHEN NOT MATCHED THEN--INSERT
INSERT (KeysId, OperationId) VALUES (S.KeysId, S.OperationId)
OUTPUT $action AS ChangesMade, inserted.OperationKeysId AS new_OperationKeysId, deleted.OperationKeysId AS old_OperationKeysId;
My issue is that SQL tells me IsChecked is invalid. Any idea on what I'm doing wrong?
You aren't SELECTING IsChecked.
At the line:
USING (SELECT VT.KeysId, VT.OperationId as OperationId FROM #ValuesTable AS VT) AS S
You need:
USING (SELECT VT.KeysId, VT.OperationId as OperationId, VT.IsChecked as IsChecked FROM #ValuesTable AS VT) AS S

Is it possible to a db constraint in for this rule?

I wish to make sure that my data has a constraint the following check (constraint?) in place
This table can only have one BorderColour per hub/category. (eg. #FFAABB)
But it can have multiple nulls. (all the other rows are nulls, for this field)
Table Schema
ArticleId INT PRIMARY KEY NOT NULL IDENTITY
HubId TINYINT NOT NULL
CategoryId INT NOT NULL
Title NVARCHAR(100) NOT NULL
Content NVARCHAR(MAX) NOT NULL
BorderColour VARCHAR(7) -- Can be nullable.
I'm gussing I would have to make a check constraint? But i'm not sure how, etc.
sample data.
1, 1, 1, 'test', 'blah...', '#FFAACC'
1, 1, 1, 'test2', 'sfsd', NULL
1, 1, 2, 'Test3', 'sdfsd dsf s', NULL
1, 1, 2, 'Test4', 'sfsdsss', '#AABBCC'
now .. if i add the following line, i should get some sql error....
INSERT INTO tblArticle VALUES (1, 2, 'aaa', 'bbb', '#ABABAB')
any ideas?
CHECK constraints are ordinarily applied to a single row, however, you can cheat using a UDF:
CREATE FUNCTION dbo.CheckSingleBorderColorPerHubCategory
(
#HubID tinyint,
#CategoryID int
)
RETURNS BIT
AS BEGIN
RETURN CASE
WHEN EXISTS
(
SELECT HubID, CategoryID, COUNT(*) AS BorderColorCount
FROM Articles
WHERE HubID = #HubID
AND CategoryID = #CategoryID
AND BorderColor IS NOT NULL
GROUP BY HubID, CategoryID
HAVING COUNT(*) > 1
) THEN 1
ELSE 0
END
END
Then create the constraint and reference the UDF:
ALTER TABLE Articles
ADD CONSTRAINT CK_Articles_SingleBorderColorPerHubCategory
CHECK (dbo.CheckSingleBorderColorPerHubCategory(HubID, CategoryID) = 1)
Another option that is available is available if you are running SQL2008. This version of SQL has a feature called filtered indexes.
Using this feature you can create a unique index that includes all rows except those where BorderColour is null.
CREATE TABLE [dbo].[UniqueExceptNulls](
[HubId] [tinyint] NOT NULL,
[CategoryId] [int] NOT NULL,
[BorderColour] [varchar](7) NULL,
)
GO
CREATE UNIQUE NONCLUSTERED INDEX UI_UniqueExceptNulls
ON [UniqueExceptNulls] (HubID,CategoryID)
WHERE BorderColour IS NOT NULL
This approach is cleaner than the approach in my other answer because it doesn't require creating extra computed columns. It also doesn't require you to have a unique column in the table, although you should have that anyway.
Finally, it will also be much faster than the UDF/Check Constraint solutions.
You can also do a trigger with something like this (this is actually overkill - you can make it cleaner by assuming the database is already in a valid state - i.e. UNION instead of UNION all etc):
IF EXISTS (
SELECT COUNT(BorderColour)
FROM (
SELECT INSERTED.HubId, INSERTED.CategoryId, INSERTED.BorderColour
UNION ALL
SELECT HubId, CategoryId, BorderColour
FROM tblArticle
WHERE EXISTS (
SELECT *
FROM INSERTED
WHERE tblArticle.HubId = INSERTED.HubId
AND tblArticle.CategoryId = INSERTED.CategoryId
)
) AS X
GROUP BY HubId, CategoryId
HAVING COUNT(BorderColour) > 1
)
RAISEERROR
If you have a unique column in your table, then you can accomplish this by creating a unique constraint on a computer column.
The following sample created a table that behaved as you described in your requirements and should perform better than a UDF based check constraint. You might also be able to improve the performance further by making the computed column persisted.
CREATE TABLE [dbo].[UQTest](
[Id] INT IDENTITY(1,1) NOT NULL,
[HubId] TINYINT NOT NULL,
[CategoryId] INT NOT NULL,
[BorderColour] varchar(7) NULL,
[BorderColourUNQ] AS (CASE WHEN [BorderColour] IS NULL
THEN cast([ID] as varchar(50))
ELSE cast([HuBID] as varchar(3)) + '_' +
cast([CategoryID] as varchar(20)) END
),
CONSTRAINT [UQTest_Unique]
UNIQUE ([BorderColourUNQ])
)
The one possibly undesirable facet of the above implementation is that it allows a category/hub to have both a Null AND a color defined. If this is a problem, let me know and I'll tweak my answer to address that.
PS: Sorry about my previous (incorrect) answer. I didn't read the question closely enough.

Resources