I have a table (Table1) with an auto_increment ID column as the primary key, and it's working well. I also have another table (Table2) with a foreign key using the ID column from Table1.
Now when I view records from Table2 I see NULL in the ID column and I don't get the auto generated numbers. I also tried with Identity(1,1) and see the same result.
How I can fix this?
As a follow-up, lets say I add a new column to an existing table and want to give it values from 1 to 20. How can I do that?
As Joel mentions, constraint values are not automatically inserted from one table to another. If the foreign key in Table2 is the primary key value from Table1, then you will need to capture #Table1's primary key value before inserting a new row into #Table2.
Here's a common way of doing this. You can run this example in SSMS.
/* Create a mock-up of Table1 and Table2 */
DECLARE #Table1 TABLE (
Table1_Value VARCHAR(50), pk_Table1 INT IDENTITY (1,1) NOT NULL PRIMARY KEY
);
DECLARE #Table2 TABLE (
fk_Table1 INT, Table2_Value VARCHAR(50), pk_Table2 INT IDENTITY (999,1) NOT NULL PRIMARY KEY
);
Declare a table variable (#out) to capture the primary key value of Table1 upon a row insert:
/* Declare an OUTPUT table to capture the PK of the value inserted into #Table1 */
DECLARE #out TABLE ( pk_Table1 INT );
Once we have these things we are ready to insert a new row into Table1 and capture its primary key value.
/* Insert a value into #Table1 and capture its PK */
INSERT INTO #Table1 ( Table1_Value )
OUTPUT inserted.pk_Table1 INTO #out
VALUES
( 'Table1_Value01' );
What's of note here is this guy:
OUTPUT inserted.pk_Table1 INTO #out
This allows us to capture any value inserted into Table1, but in this case, what we care about is pk_Table1.
If we view the resultset of #out:
/* Show the results captured in #out */
SELECT * FROM #out;
We see the following:
+-----------+
| pk_Table1 |
+-----------+
| 1 |
+-----------+
Viewing the resultset of #Table1:
/* Show the #Table1 resultset */
SELECT * FROM #Table1 ORDER BY pk_Table1;
Returns:
+----------------+-----------+
| Table1_Value | pk_Table1 |
+----------------+-----------+
| Table1_Value01 | 1 |
+----------------+-----------+
Now that we've captured the primary key of Table1's new row, we can insert a new row into #Table2 with a value that ties it back to #Table1.
/* Insert a new value into #Table2 with a foreign key value pointing to #Table1 */
INSERT INTO #Table2 ( fk_Table1, Table2_Value )
VALUES ( ( SELECT pk_Table1 FROM #out ), 'Table2 value related to Table1 value.' );
This guy...
( SELECT pk_Table1 FROM #out )
Returns the primary key value we captured when inserting a row into #Table1.
If we look at the resultset of #Table2:
/* Show the #Table2 resultset */
SELECT * FROM #Table2 ORDER BY pk_Table2;
We see:
+-----------+---------------------------------------+-----------+
| fk_Table1 | Table2_Value | pk_Table2 |
+-----------+---------------------------------------+-----------+
| 1 | Table2 value related to Table1 value. | 999 |
+-----------+---------------------------------------+-----------+
And finally, we can join the two tables together based on their relationship:
/* Join the two tables based on their relationship */
SELECT
t1.pk_Table1,
t1.Table1_Value,
t2.fk_Table1,
t2.pk_Table2,
t2.Table2_Value
FROM #Table1 AS t1
INNER JOIN #Table2 AS t2
ON t1.pk_Table1 = t2.fk_Table1
ORDER BY
t1.pk_Table1;
Which returns:
+-----------+----------------+-----------+-----------+---------------------------------------+
| pk_Table1 | Table1_Value | fk_Table1 | pk_Table2 | Table2_Value |
+-----------+----------------+-----------+-----------+---------------------------------------+
| 1 | Table1_Value01 | 1 | 999 | Table2 value related to Table1 value. |
+-----------+----------------+-----------+-----------+---------------------------------------+
Obviously, this isn't a deep dive into relating two tables based on a unique value, however, it's easy to understand.
Here is the full example you can run in SSMS:
/* Create a mock-up of Table1 and Table2 */
DECLARE #Table1 TABLE (
Table1_Value VARCHAR(50), pk_Table1 INT IDENTITY (1,1) NOT NULL PRIMARY KEY
);
DECLARE #Table2 TABLE (
fk_Table1 INT, Table2_Value VARCHAR(50), pk_Table2 INT IDENTITY (999,1) NOT NULL PRIMARY KEY
);
/* Declare an OUTPUT table to capture the PK of the value inserted into #Table1 */
DECLARE #out TABLE ( pk_Table1 INT );
/* Insert a value into #Table1 and capture its PK */
INSERT INTO #Table1 ( Table1_Value )
OUTPUT inserted.pk_Table1 INTO #out
VALUES
( 'Table1_Value01' );
/* Show the results captured in #out */
SELECT * FROM #out;
/* Show the #Table1 resultset */
SELECT * FROM #Table1 ORDER BY pk_Table1;
/* Insert a new value into #Table2 with a foreign key value pointing to #Table1 */
INSERT INTO #Table2 ( fk_Table1, Table2_Value )
VALUES ( ( SELECT pk_Table1 FROM #out ), 'Table2 value related to Table1 value.' );
/* Show the #Table2 resultset */
SELECT * FROM #Table2 ORDER BY pk_Table2;
/* Join the two tables based on their relationship */
SELECT
t1.pk_Table1,
t1.Table1_Value,
t2.fk_Table1,
t2.pk_Table2,
t2.Table2_Value
FROM #Table1 AS t1
INNER JOIN #Table2 AS t2
ON t1.pk_Table1 = t2.fk_Table1
ORDER BY
t1.pk_Table1;
Related
I have one table (Table1) that has several columns used in combination: Name, TestName, DevName, Dept. When each of these 4 columns have values, the record is inserted into Table2. I need to confirm that all of the records with existing values in each of these fields within Table1 were correctly copied into Table 2.
I have created a query for it:
SELECT DISTINCT wr.Name,wr.TestName, wr.DEVName ,wr.Dept
FROM table2 wr
where NOT EXISTS (
SELECT NULL
FROM TABLE1 ym
WHERE ym.Name = wr.Name
AND ym.TestName = wr. TestName
AND ym.DEVName = wr.DEVName
AND ym. Dept = wr. Dept
)
My counts are not adding up, so I believe that this is incorrect. Can you advise me on the best way to write this query for my needs?
You can use the EXCEPT set operator for this one if the table definitions are identical.
SELECT DISTINCT ym.Name, ym.TestName, ym.DEVName, ym.Dept
FROM table1 ym
EXCEPT
SELECT DISTINCT wr.Name, wr.TestName, wr.DEVName, wr.Dept
FROM table2 wr
This returns distinct rows from the first table where there is not a match in the second table. Read more about EXCEPT and INTERSECT here: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/set-operators-except-and-intersect-transact-sql?view=sql-server-2017
Your query should do the job. It checks anything that are in Table1, but not Table2
SELECT ym.Name, ym.TestName, ym.DEVName, ym.Dept
FROM Table1 ym
WHERE NOT EXISTS (
SELECT 1
FROM table2
WHERE ym.Name = Name AND ym.TestName = TestName AND ym.DEVName = DEVName AND ym. Dept = Dept
)
If the structure of both tables are the same, EXCEPT is probably simpler.
IF OBJECT_ID(N'tempdb..#table1') IS NOT NULL drop table #table1
IF OBJECT_ID(N'tempdb..#table2') IS NOT NULL drop table #table2
create table #table1 (id int, value varchar(10))
create table #table2 (id int)
insert into #table1(id, value) VALUES (1,'value1'), (2,'value2'), (3,'value3')
--test here. Comment next line
insert into #table2(id) VALUES (1) --Comment/Uncomment
select * from #table1
select * from #table2
select #table1.*
from #table1
left JOIN #table2 on
#table1.id = #table2.id
where (#table2.id is not null or not exists (select * from #table2))
In my application there is a table to store text and another table to store it's respective images..
My table structure goes as follows (tbl_article):
article_id | Page_ID | article_Content
-----------+---------+-----------------
1 | 1 | hello world
2 | 1 | hello world 2
where article_id is the pk and auto incremented.
Now in my other table (tbl_img):
image_id| image_location|article_id | page_id
--------+---------------+-----------+---------
1 | imgae locat | 1 | 1
2 | image loc2 | 2 | 1
where image_id is the pk and auto incremented.
In both table I am inserting data through table valued parameter, and in second table article_id is referencing article_id of the first table.
To get auto incremented column value I am using output clause:
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT
)
INSERT INTO tbl_article(page_id, article_content)
OUTPUT Inserted.article_id, #pageId INTO #TableOfIdentities (IdentValue, PageId)
SELECT page_id, slogan_body_header
FROM #dtPageSlogan
INSERT INTO tbl_img(page_id, image_location)
SELECT page_id, image_location
FROM #dtPageImageContent
But now I have to insert values from #TableOfIdentities into article_id of tbl_img - how to do that?
You need an additional column , a temporary article id generated from your code to link images and related articles properly. So you can use MERGE with OUTPUT, because with merge you can refer to columns from both the target and the source and build your TableOfIdentities tvp properly, then join it with dtPageImageContent to insert on tbl_img.
CREATE TABLE tbl_article (
article_id INT IDENTITY(1, 1) PRIMARY KEY
, Page_ID INT
, article_Content NVARCHAR(MAX)
);
CREATE TABLE tbl_img (
image_id INT IDENTITY(1, 1) PRIMARY KEY
, image_location VARCHAR(256)
, article_id INT
, Page_ID INT
);
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT,
tmp_article_id INT
);
DECLARE #dtPageSlogan TABLE(
tmp_article_id INT -- generated in your code
, page_id INT
, slogan_body_header NVARCHAR(MAX)
);
DECLARE #dtPageImageContent TABLE (
page_id INT
, image_location VARCHAR(256)
, tmp_article_id INT -- needed to link each image to its article
)
-- create sample data
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (10, 1, 'hello world');
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (20, 1, 'hello world 2');
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc1', 10);
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc2', 20);
-- use merge to insert tbl_article and populate #TableOfIdentities
MERGE INTO tbl_article
USING (
SELECT ps.page_id, ps.slogan_body_header, ps.tmp_article_id
FROM #dtPageSlogan as ps
) AS D
ON 1 = 2
WHEN NOT MATCHED THEN
INSERT(page_id, article_content) VALUES (page_id, slogan_body_header)
OUTPUT Inserted.article_id, Inserted.page_id, D.tmp_article_id
INTO #TableOfIdentities (IdentValue, PageId, tmp_article_id)
;
-- join using page_id and tmp_article_id fields
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi
ON pic.page_Id = toi.PageId AND pic.tmp_article_id = toi.tmp_article_id
;
You can try it on fiddle
You need to join the #dtPageImageContent table variable with the #TableOfIdentities table variable on their common page_id to get those values:
-- add the third column "article_id" to your list of insert columns
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi ON pic.page_Id = toi.page_id
I'm currently working on a stored procedure on SQL Server 2016. In my Database I have a table structure and need to add another table, which references to the same table as an existing one.
Thus, I have 2 times a 1:1 relation to the same table.
The occuring problem is, I reference the same keys from 2 different origin tables twice in the same target table.
Target table:
FK_Tables | Text
----------------
1 | Table One Text Id: 1
1 | Table Two Text Id: 1 // The error: Same FK_Tables 2 times
Table One:
ID | OtherField
---------
1 | 42
Table Two:
ID | CoolField
---------
1 | 22
Table One and Table Two are currently referencing to the table Reference Table.
Do you know how I can solve this problem, of the same ID twice?
Thanks!!
You need to add a column for each table you're referencing, otherwise you wouldn't know where the ID is coming from if they were all inserted into the same field. Something like this:
/*
CREATE TEST TABLES
*/
DROP TABLE IF EXISTS tbOne;
CREATE TABLE tbOne ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS tbTwo;
CREATE TABLE tbTwo ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS Target;
CREATE TABLE Target ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, FKTB1 INT
, FKTB2 INT
, TXT VARCHAR(100)
);
-- 1st FK tbOne
ALTER TABLE Target ADD CONSTRAINT FK_One FOREIGN KEY (FKTB1) REFERENCES tbOne (ID);
--2nd FK tbTwo
ALTER TABLE Target ADD CONSTRAINT FK_Two FOREIGN KEY (FKTB2) REFERENCES tbTwo (ID);
-- Populate test tables
INSERT INTO tbOne (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO tbTwo (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO [Target] (FKTB1, FKTB2, TXT)
SELECT 1, 1, 'Test - constraint'
-- Check result set
SELECT *
FROM tbTwo
SELECT *
FROM tbOne
SELECT *
FROM [Target] T
INNER JOIN tbOne TB1
ON T.FKTB1 = TB1.ID
INNER JOIN tbTwo TB2
ON T.FKTB2 = TB2.ID
I have a situation where I have a store procedure that contains a merge statement. The procedure grabs data from table A and uses the merge statement to update certain records in table B. The procedure works fine but occasionally, there are instances where there are duplicate records in table A. The store procedure is in a package with error notification set up and the package runs in a job and it gives the error below. Are there any ways that we can debug this? Like some where in the store procedure say if it gives an error then insert the source data into a table? Any input is appreciated.
Thanks
Error :
failed with the following error: "The MERGE statement attempted to
UPDATE or DELETE the same row more than once. This happens when a
target row matches more than one source row. A MERGE statement cannot
UPDATE/DELETE the same row of the target table multiple times. Refine
the ON clause to ensure a target row matches at most one source row,
or use the GROUP BY clause to group the source rows.". Possible
failure reasons: Problems with the query, "ResultSet" property not set
correctly, parameters not set correctly, or connection not established
correctly.
You could add something like this to the beginning of your merge procedure:
if exists (
select 1
from a
group by a.OnColumn
having count(*)>1
)
begin;
insert into merge_err (OnColumn, OtherCol, rn, cnt)
select
a.OnColumn
, a.OtherCol
, rn = row_number() over (
partition by OnColumn
order by OtherCol
)
, cnt = count(*) over (
partition by OnColumn
)
from a
raiserror( 'Duplicates in source table a', 0, 1)
return -1;
end;
test setup: http://rextester.com/EFZ77700
create table a (OnColumn int, OtherCol varchar(16))
insert into a values
(1,'a')
, (1,'b')
, (2,'c')
create table b (OnColumn int primary key, OtherCol varchar(16))
insert into b values
(1,'a')
, (2,'c')
create table merge_err (
id int not null identity(1,1) primary key clustered
, OnColumn int
, OtherCol varchar(16)
, rn int
, cnt int
, ErrorDate datetime2(7) not null default sysutcdatetime()
);
go
dummy procedure:
create procedure dbo.Merge_A_into_B as
begin
set nocount, xact_abort on;
if exists (
select 1
from a
group by a.OnColumn
having count(*)>1
)
begin;
insert into merge_err (OnColumn, OtherCol, rn, cnt)
select
a.OnColumn
, a.OtherCol
, rn = row_number() over (
partition by OnColumn
order by OtherCol
)
, cnt = count(*) over (
partition by OnColumn
)
from a
raiserror( 'Duplicates in source table a', 0, 1)
return -1;
end;
/*
merge into b
using a
on b.OnColumn = a.OnColumn
...
--*/
end;
go
execute test proc and check error table:
exec dbo.Merge_A_into_B
select *
from merge_err
where cnt > 1
results:
+----+----------+----------+----+-----+---------------------+
| id | OnColumn | OtherCol | rn | cnt | ErrorDate |
+----+----------+----------+----+-----+---------------------+
| 1 | 1 | a | 1 | 2 | 05.02.2017 17:22:39 |
| 2 | 1 | b | 2 | 2 | 05.02.2017 17:22:39 |
+----+----------+----------+----+-----+---------------------+
I have 2 tables:
Order (with a identity order id field)
OrderItems (with a foreign key to order id)
In a stored proc, I have a list of orders that I need to duplicate. Is there a good way to do this in a stored proc without a cursor?
Edit:
This is on SQL Server 2008.
A sample spec for the table might be:
CREATE TABLE Order (
OrderID INT IDENTITY(1,1),
CustomerName VARCHAR(100),
CONSTRAINT PK_Order PRIMARY KEY (OrderID)
)
CREATE TABLE OrderItem (
OrderID INT,
LineNumber INT,
Price money,
Notes VARCHAR(100),
CONSTRAINT PK_OrderItem PRIMARY KEY (OrderID, LineNumber),
CONSTRAINT FK_OrderItem_Order FOREIGN KEY (OrderID) REFERENCES Order(OrderID)
)
The stored proc is passed a customerName of 'fred', so its trying to clone all orders where CustomerName = 'fred'.
To give a more concrete example:
Fred happens to have 2 orders:
Order 1 has line numbers 1,2,3
Order 2 has line numbers 1,2,4,6.
If the next identity in the table was 123, then I would want to create:
Order 123 with lines 1,2,3
Order 124 with lines 1,2,4,6
On SQL Server 2008 you can use MERGE and the OUTPUT clause to get the mappings between the original and cloned id values from the insert into Orders then join onto that to clone the OrderItems.
DECLARE #IdMappings TABLE(
New_OrderId INT,
Old_OrderId INT)
;WITH SourceOrders AS
(
SELECT *
FROM Orders
WHERE CustomerName = 'fred'
)
MERGE Orders AS T
USING SourceOrders AS S
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (CustomerName )
VALUES (CustomerName )
OUTPUT inserted.OrderId,
S.OrderId INTO #IdMappings;
INSERT INTO OrderItems
SELECT New_OrderId,
LineNumber,
Price,
Notes
FROM OrderItems OI
JOIN #IdMappings IDM
ON IDM.Old_OrderId = OI.OrderID