T-SQL - Insert Data into Parent and Child Tables - sql

Code:
CREATE TYPE dbo.tEmployeeData AS TABLE
(
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
DepartmentType NVARCHAR(10),
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100),
DepartmentTypeBMetadata NVARCHAR(100)
)
GO
CREATE PROC dbo.EmployeeImport
(#tEmployeeData tEmployeeData READONLY)
AS
BEGIN
DECLARE #MainEmployee TABLE
(EmployeeID INT IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50))
DECLARE #ParentEmployeeDepartment TABLE
(EmployeeID INT,
ParentEmployeeDepartmentID INT IDENTITY(1,1),
DepartmentType NVARCHAR(10))
DECLARE #ChildEmployeeDepartmentTypeA TABLE
(ParentEmployeeDepartmentID INT,
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100))
DECLARE #ChildEmployeeDepartmentTypeB TABLE
(ParentEmployeeDepartmentID INT,
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeBMetadata NVARCHAR(100))
-- INSERT CODE GOES HERE
SELECT * FROM #MainEmployee
SELECT * FROM #ParentEmployeeDepartment
SELECT * FROM #ChildEmployeeDepartmentTypeA
SELECT * FROM #ChildEmployeeDepartmentTypeB
END
GO
DECLARE #tEmployeeData tEmployeeData
INSERT INTO #tEmployeeData (FirstName, LastName, DepartmentType,
DepartmentBuilding, DepartmentEmployeeLevel,
DepartmentTypeAMetadata, DepartmentTypeBMetadata)
SELECT
N'Tom_FN', N'Tom_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL
UNION
SELECT
N'Mike_FN', N'Mike_LN', N'B',
N'OpenH', N'XII', NULL, N'Med'
UNION
SELECT
N'Joe_FN', N'Joe_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL
UNION
SELECT
N'Dave_FN', N'Dave_LN', N'B',
N'OpenC', N'XII', NULL, N'Lab'
EXEC EmployeeImport #tEmployeeData
GO
DROP PROC dbo.EmployeeImport
DROP TYPE dbo.tEmployeeData
Notes:
The table variables are replaced by real tables in live environment.
EmployeeID and ParentEmployeeDepartmentID columns' values don't always match each other. Live environment has more records in the udt (tEmployeeData) than just 4
Goal:
The udt (tEmployeeData) will be passed into the procedure
The procedure should first insert the data into the #MainEmployee table (and get the EmployeeIDs)
Next, the procedure should insert the data into the #ParentEmployeeDepartment table (and get the ParentEmployeeDepartmentID) - note EmployeeID is coming from the previous output.
Then, the procedure should split the child level data based on the DepartmentType ("A" = insert into #ChildEmployeeDepartmentTypeA and "B" = insert into #ChildEmployeeDepartmentTypeB).
ParentEmployeeDepartmentID from #ParentEmployeeDepartment should be used when inserting data into either #ChildEmployeeDepartmentTypeA or #ChildEmployeeDepartmentTypeB
The procedure should should run fast (need to avoid row by row operation)
Output:
#MainEmployee:
EmployeeID FirstName LastName
---------------------------------
1 Tom_FN Tom_LN
2 Mike_FN Mike_LN
3 Joe_FN Joe_LN
4 Dave_FN Dave_LN
#ParentEmployeeDepartment:
EmployeeID ParentEmployeeDepartmentID DepartmentType
-------------------------------------------------------
1 1 A
2 2 B
3 3 A
4 4 B
#ChildEmployeeDepartmentTypeA:
ParentEmployeeDepartmentID DepartmentBuilding DepartmentEmployeeLevel DepartmentTypeAMetadata
---------------------------------------------------------------------------------------------------------
1 101 IV Tech/IT
3 101 IV Tech/IT
#ChildEmployeeDepartmentTypeB:
ParentEmployeeDepartmentID DepartmentBuilding DepartmentEmployeeLevel DepartmentTypeAMetadata
----------------------------------------------------------------------------------------------------------
2 OpenH XII Med
4 OpenC XII Lab
I know I can use the OUTPUT clause after the insert and get EmployeeID and ParentEmployeeDepartmentID, but I'm not sure how to insert the right child records into right tables with right mapping to the parent table. Any help would be appreciated.

Here is my solution (based on the same answer I've linked to in the comments):
First, you must add another column to your UDT, to hold a temporary ID for the employee:
CREATE TYPE dbo.tEmployeeData AS TABLE
(
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
DepartmentType NVARCHAR(10),
DepartmentBuilding NVARCHAR(50),
DepartmentEmployeeLevel NVARCHAR(10),
DepartmentTypeAMetadata NVARCHAR(100),
DepartmentTypeBMetadata NVARCHAR(100),
EmployeeId int
)
GO
Populating it with that new employeeId column:
DECLARE #tEmployeeData tEmployeeData
INSERT INTO #tEmployeeData (FirstName, LastName, DepartmentType,
DepartmentBuilding, DepartmentEmployeeLevel,
DepartmentTypeAMetadata, DepartmentTypeBMetadata, EmployeeId)
SELECT
N'Tom_FN', N'Tom_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL, 5
UNION
SELECT
N'Mike_FN', N'Mike_LN', N'B',
N'OpenH', N'XII', NULL, N'Med', 6
UNION
SELECT
N'Joe_FN', N'Joe_LN', N'A',
N'101', N'IV', N'Tech/IT', NULL, 7
UNION
SELECT
N'Dave_FN', N'Dave_LN', N'B',
N'OpenC', N'XII', NULL, N'Lab', 8
Insert part goes here
Then, you use a table variable to map the inserted value from the employee table to the temp employee id in the data you sent to the procedure:
DECLARE #EmployeeidMap TABLE
(
temp_id int,
id int
)
Now, the trick is to populate the employee table with the MERGE statement instead of an INSERT...SELECT because you have to use values from both inserted and source data in the output clause:
MERGE INTO #MainEmployee USING #tEmployeeData AS sourceData ON 1 = 0 -- Always not matched
WHEN NOT MATCHED THEN
INSERT (FirstName, LastName)
VALUES (sourceData.FirstName, sourceData.LastName)
OUTPUT sourceData.EmployeeId, inserted.EmployeeID
INTO #EmployeeidMap (temp_id, id); -- populate the map table
From that point on it's simple, you need to join the data you sent to the #EmployeeidMap to get the actual employeeId:
INSERT INTO #ParentEmployeeDepartment (EmployeeID, DepartmentType)
SELECT Id, DepartmentType
FROM #tEmployeeData
INNER JOIN #EmployeeidMap ON EmployeeID = temp_id
Now you can use the data in #ParentEmployeeDepartment to map the actual values in ParentEmployeeDepartmentID to the data you sent:
Testing the inserts so far
SELECT FirstName,
LastName,
SentData.DepartmentType As [Dept. Type],
DepartmentBuilding As Building,
DepartmentEmployeeLevel As [Emp. Level],
DepartmentTypeAMetadata As [A Meta],
DepartmentTypeBMetadata As [B Meta],
SentData.EmployeeId As TempId, EmpMap.id As [Emp. Id], DeptMap.ParentEmployeeDepartmentID As [Dept. Id]
FROM #tEmployeeData SentData
INNER JOIN #EmployeeidMap EmpMap ON SentData.EmployeeId = temp_id
INNER JOIN #ParentEmployeeDepartment DeptMap ON EmpMap.id = DeptMap.EmployeeID
results:
FirstName LastName Dept. Type Building Emp. Level A Meta B Meta TempId Emp. Id Dept. Id
--------- -------- ---------- -------- ---------- ------ ------ ------ ----------- -----------
Dave_FN Dave_LN B OpenC XII NULL Lab 8 1 1
Joe_FN Joe_LN A 101 IV Tech/IT NULL 7 2 2
Mike_FN Mike_LN B OpenH XII NULL Med 6 3 3
Tom_FN Tom_LN A 101 IV Tech/IT NULL 5 4 4
I'm sure that from this point you can easily figure out the last 2 inserts yourself.

Related

Update temp table with identity after insert

I have a temp table that looks like this:
FirstName
LastName
DOB
Sex
Age
ExternalID
In my stored procedure I'm inserting these values into a regular table that has the following structure:
ID identity(1,1)
FirstName
LastName
So, I do this:
Insert into myTable
select FirstName, LastName from TempTable
During the insert I need to insert primary key from main table back into temp table "ExternalID" column. How can this be achieved?
I tried using OUTPUT statement but it only allows to insert to a separate table and then I have no way to map back to temp table
I need to insert generated IDs to column ExternalID in temp table right after the insert. FirstName and LastName are not unique.
One possible solution would be to use loop and insert one row at a time. This way, I can update temp table row with scope_identity(). But I want to avoid using loops.
Try using MERGE instead of INSERT.
MERGE allows you to output a column you didn't insert, such as an identifier on your temp table. Using this method, you can build another temporary table that maps your temp table to the inserted rows (named #TempIdTable in the sample below).
First, give #TempTable its own primary key. I'll call it TempId. I'll also assume you have a column on #TempTable to store the returned primary key from MyTable, ID.
--Make a place to store the associated ID's
DECLARE #TempIdTable TABLE
([TempId] INT NOT NULL
,[ID] INT NOT NULL)
--Will only insert, as 1 never equals 0.
MERGE INTO myTable
USING #TempTable AS tt
ON 1 = 0
WHEN NOT MATCHED
THEN
INSERT ([FirstName]
,[LastName])
VALUE (t.[FirstName]
,t.[LastName])
OUTPUT tt.[TempId], inserted.[ID] --Here's the magic
INTO #TempIdTable
--Associate the new primary keys with the temp table
UPDATE #TempTable
SET [ID] = t.[ID]
FROM #TempIdTable t
WHERE #TempTable.[TempId] = t.[TempId]
I was working on a similar issue and found this trick over here: Is it possible to for SQL Output clause to return a column not being inserted?
Here's the full code I used in my own testing.
CREATE TABLE [MQ]
([MESSAGEID] INT IDENTITY PRIMARY KEY
,[SUBJECT] NVARCHAR(255) NULL);
CREATE TABLE [MR]
([MESSAGESEQUENCE] INT IDENTITY PRIMARY KEY
,[TO] NVARCHAR(255) NOT NULL
,[CC] NVARCHAR(255) NOT NULL
,[BCC] NVARCHAR(255) NOT NULL);
CREATE TABLE #Messages (
[subject] nvarchar(255) NOT NULL
,[to] nvarchar(255) NOT NULL
,[cc] nvarchar(255) NULL
,[bcc] nvarchar(255) NULL
,[MESSAGEID] INT NULL
,[sortKey] INT IDENTITY PRIMARY KEY
);
INSERT INTO #Messages
VALUES ('Subject1','to1','cc1','bcc1', NULL)
,('Subject2','to2', NULL, NULL, NULL);
SELECT * FROM #Messages;
DECLARE #outputSort TABLE (
[sortKey] INT NOT NULL
,[MESSAGEID] INT NOT NULL
,[subject] NVARCHAR(255)
);
MERGE INTO [MQ]
USING #Messages M
ON 1 = 0
WHEN NOT MATCHED
THEN
INSERT ([SUBJECT])
VALUES (M.[subject])
OUTPUT M.[SORTKEY]
,inserted.[MESSAGEID]
,inserted.[SUBJECT]
INTO #outputSort;
SELECT * FROM #outputSort;
SELECT * FROM [MQ];
UPDATE #Messages
SET MESSAGEID = O.[MESSAGEID]
FROM #outputSort O
WHERE #Messages.[sortKey] = O.[sortKey];
SELECT * FROM #Messages;
DROP TABLE #Messages;
As you said, FirstName and LastName are not unique. This means you cannot use a trigger because there can be the same FirstName + LastName so you cannot join on them.
But you can do the inverse thing: first update your temp table ExternalID (I suggest you to use sequence object and just do update #t set ExternalID = next value for dbo.seq1;) and then just insert your rows including ExternalID into myTable. To be able to insert into identity field you can use set identity_insert myTable on or you can re-design your destination table to contain no identity at all as now you use sequence for the same purpose.
We need a unique column for able to make the comparison at the update operation after the insert. That's why we are using ExternalID column temporarily. ExternalID updated by row_nubmber.
;WITH CTE AS
(
SELECT *, RN = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM #TempTable
)
UPDATE CTE SET ExternalID = RN
We are keeping the output of the insert operation in a temp table. The trick is order by with ExternalID, it will help us for making the unique row number for same first and last name
DECLARE #output TABLE (
ID INT,
FirstName VARCHAR(10),
LastName VARCHAR(10))
Insert into #myTable
OUTPUT inserted.ID, inserted.FirstName, inserted.LastName INTO #output(ID, FirstName, LastName)
select FirstName, LastName from #TempTable T
order by ExternalID
For replacing the ExternalID column with inserted id value, we are making comparing with first name, last name and row number.
;WITH TMP_T AS(
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY FirstName, LastName ORDER BY ExternalID) FROM #TempTable )
,OUT_T AS(
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY FirstName, LastName ORDER BY ID) FROM #output )
UPDATE TMP_T SET ExternalID = OUT_T.ID
FROM
TMP_T INNER JOIN OUT_T ON
TMP_T.FirstName = OUT_T.FirstName
AND TMP_T.LastName = OUT_T.LastName
AND TMP_T.RN = OUT_T.RN
Sample Data:
DECLARE #TempTable TABLE (
FirstName VARCHAR(10),
LastName VARCHAR(10),
DOB VARCHAR(10),
Sex VARCHAR (10),
Age VARCHAR(10),
ExternalID INT)
INSERT INTO #TempTable VALUES
('Serkan1', 'Arslan1', 'A','M','1',NULL),
('Serkan2', 'Arslan2', 'B','M','1',NULL),
('Serkan3', 'Arslan', 'C','M','1',NULL),
('Serkan3', 'Arslan', 'D','M','1',NULL)
DECLARE #myTable TABLE (
ID INT identity(100,1), -- started from 100 for see the difference
FirstName VARCHAR(10),
LastName VARCHAR(10))
Result:
MyTable
ID FirstName LastName
----------- ---------- ----------
100 Serkan1 Arslan1
101 Serkan2 Arslan2
102 Serkan3 Arslan
103 Serkan3 Arslan
TempTable
FirstName LastName DOB Sex Age ExternalID
---------- ---------- ---------- ---------- ---------- -----------
Serkan1 Arslan1 A M 1 100
Serkan2 Arslan2 B M 1 101
Serkan3 Arslan C M 1 102
Serkan3 Arslan D M 1 103
One way to do this is by duplicating the data into a second temp table like so:
SELECT *
INTO #TEMPTABLE
FROM (VALUES (1, 'Adam'), (2, 'Kate'), (3, 'Jess')) AS X (Id, Name)
SELECT TOP 0 CAST(NULL AS INT) AS IdentityValue, *
INTO #NEWTEMPTABLE
FROM #TEMPTABLE
CREATE TABLE #TABLEFORINSERT (
IdentityColumn INT IDENTITY(1,1),
Id INT,
Name VARCHAR(255)
)
INSERT INTO #TABLEFORINSERT (Id, Name)
OUTPUT INSERTED.IdentityColumn, INSERTED.Id, Inserted.Name INTO #NEWTEMPTABLE
SELECT Id, Name FROM #TEMPTABLE
--New temp table with identity values
SELECT * FROM #NEWTEMPTABLE

TSQL Updating record ID after insert

I have a stored procedure that takes data from a table and creates a record in another table in the following structure:
TableA = Source Data
TableB = Destination 1
First, we query all the data we need from the source table and insert it into TableB. This table has an identity called recordID.
This is done through an INSERT from a Select statement which could contain a variable amount of records.
When this is complete, I need to update a column in TableA called TableBRef with the recordID that was created from the insert in TableB.
I tried using Scope_Identity() but because its inserting multiple records, it only gets the ID of the last record.
I also tried to create a SQLFiddle but it appears the site is having issues as I am getting the error Unknown Error Occurred: XML document structures must start and end within the same entity.: on even the sample fiddle.
Any recommendations to be able to accomplish what I am needing?
Update:
Here is some example code since SQLFiddle is down:
-- This is our source data
DECLARE #source TABLE (recordID INT IDENTITY (1,1), name VARCHAR(100), phone VARCHAR(20));
INSERT INTO #source(name , phone)
VALUES (
'Bob Desk', '123-456-7899',
'Don Mouse', '123-456-5555',
'Mike Keyboard', '123-456-7899',
'Billy Power', '122-222-1134'
)
-- This is the first step in the process - Inserting the records into our table
DECLARE #data1 TABLE (recordID INT IDENTITY (1,1), name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, sourceID INT NULL)
SELECT name, phone
FROM #source;
-- Based on some condition, we take records from #data1 and insert them into #data2
DECLARE #data2 TABLE (recordID INT IDENTITY (1,1), name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT null)
INSERT INTO #data2( name, phone)
SELECT name, phone
FROM #data1
WHERE phone <> '123-456-5555'
-- I now need to update #data1 with the recordID that was created from inserting the data into #data2
UPDATE #data1 SET SOURCEID = 'blah'
Lets setup some tables:
DECLARE #source TABLE
(
recordID INT IDENTITY (1,1),
name VARCHAR(100),
phone VARCHAR(20),
sourceID INT
);
This will store all of the updates coming out of the insert statement:
DECLARE #NewRecord TABLE
(
recordID INT,
name VARCHAR(100),
phone VARCHAR(20)
);
Now we insert the records, and we output the updated record into a table variable to get the new id's:
INSERT INTO #source (name, phone)
OUTPUT inserted.recordid, inserted.name, inserted.phone INTO #NewRecord
VALUES
('Bob Desk', '123-456-7899'),
('Don Mouse', '123-456-5555'),
('Mike Keyboard', '123-456-7899'),
('Billy Power', '122-222-1134')
Here is the output:
SELECT * FROM #NewRecord
recordID name phone
1 Bob Desk 123-456-7899
2 Don Mouse 123-456-5555
3 Mike Keyboard 123-456-7899
4 Billy Power 122-222-1134
Becuase when we are inserting data into data2 with new identity column so we lost source record id so I used name and phone to find what I had inserted --this might not work properly if there are duplicate name and phone seems like there is some design flaw.
but here is some thing you can try this below
-- This is our source data
DECLARE #source TABLE (recordID INT IDENTITY (1,1), name VARCHAR(100), phone VARCHAR(20));
INSERT INTO #source(name , phone)
VALUES (
'Bob Desk', '123-456-7899'),
('Don Mouse', '123-456-5555'),
('Mike Keyboard', '123-456-7899'),
('Billy Power', '122-222-1134')
-- This is the first step in the process - Inserting the records into our table
DECLARE #data1 TABLE (recordID INT IDENTITY (1,1), name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, sourceID INT NULL)
--Capture inserts using another table
DECLARE #cdcapture1 TABLE (recordID INT , name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT null)
INSERT INTO #data1(name,phone)
OUTPUT Inserted.recordID,Inserted.name,Inserted.phone INTO #cdcapture1
SELECT name, phone
FROM #source;
--Capture inserts using another table
DECLARE #cdcapture2 TABLE (recordID INT , name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT null)
-- Based on some condition, we take records from #data1 and insert them into #data2
DECLARE #data2 TABLE (recordID INT IDENTITY(1,1), name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT null)
INSERT INTO #data2( name, phone)
OUTPUT Inserted.* INTO #cdcapture2
SELECT name, phone
FROM #cdcapture1 c1
--WHERE phone <> '123-456-5555'
-- Becuase when we are inserting data into data2 with new identity column so we lost source record id
UPDATE d1
SET d1.sourceid = c2.recordid
FROM #data1 d1 INNER JOIN #cdcapture1 c1 ON d1.recordID = c1.recordID
INNER JOIN #cdcapture2 c2 ON c2.NAME = c1.NAME AND c2.phone = c1.phone
SELECT * FROM #data1
-- This is our source data
DECLARE #source TABLE
(
recordID INT IDENTITY (1,1),
name VARCHAR(100),
phone VARCHAR(20)
);
INSERT INTO #source(name , phone)
VALUES
('Bob Desk', '123-456-7899'),
('Don Mouse', '123-456-5555'),
('Mike Keyboard', '123-456-7899'),
('Billy Power', '122-222-1134');
-- This is the first step in the process - Inserting the records into our table
DECLARE #data1 TABLE
(
recordID INT IDENTITY (1,1),
name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
sourceID INT NULL
)
INSERT INTO #data1 (name, phone )
SELECT name, phone
FROM #source;
-- Based on some condition, we take records from #data1 and insert them into #data2
DECLARE #data2 TABLE
(
recordID INT IDENTITY (1,1),
name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT null,
data1recid INT
)
INSERT INTO #data2( name, phone, data1recid)
SELECT name, phone, recordID
FROM #data1
WHERE phone <> '123-456-5555'
-- I now need to update #data1 with the recordID that was created from inserting the data into #data2
UPDATE m
set m.sourceID = d.recordID
FROM #data1 m
INNER JOIN #data2 d
ON m.recordID = d.data1recid
Here is the output:
SELECT * FROM #data1
recordID name phone sourceID
1 Bob Desk 123-456-7899 1
2 Don Mouse 123-456-5555 NULL
3 Mike Keyboard 123-456-7899 2
4 Billy Power 122-222-1134 3
Is this more along the lines of what you needed?

MS SQL Stored procedure to get the value

I have 3 tables
Staff table:
EmpId CandidateId
------------------------
1 2
Candidate table:
CandidateId Firstname Last name CountryId PassportCountry
--------------------------------------------------------------------
1 Mark Antony 2 3
2 Joy terry 1 3
Country:
CountryId Name
---------------------------
1 USA
2 UK
3 Australia
User will pass the EmpId in the querystring I need to show the candidate details according to the empId. I have only one country table and using that table for country, passportport country. So I need to get the country name when I get the candidate value.
How to write the stored procedure to get the candidate details. Im not good in sql. Can you guys help me on this. Thanks in advance.
Hi I tried the below script to get the country name and passport country name. I can get the country name, but not the passport country.
SELECT
FirstName,
LastName,
PassportCountry,
Country.CountryName as Country
from Candidate
inner join Country
on country.CountryId=candidate.country
where CandidateId=#CandidateId;
This should get you going in the right direction.
Basically, since you're referencing the Country table twice, you need to join it twice.
declare #staff table (EmpId int, CandidateId int)
insert into #staff values (1, 2 )
declare #Candidate table (CandidateId int, Firstname nvarchar(50), Lastname nvarchar(50), CountryId int, PassportCountry int)
insert into #Candidate
values (1, 'Mark', 'Antony', 2, 3),
(2, 'Joy', 'Terry', 1, 3)
declare #Country table (CountryId int, Name nvarchar(50))
insert into #Country
values (1, 'USA'),
(2, 'UK'),
(3, 'Australia')
declare #empID int
set #empID = 1
SELECT
FirstName,
LastName,
t2.Name as PersonCountry,
t3.Name as PassportCountry
from #staff s
inner join #Candidate t1 on s.CandidateId = t1.CandidateId
inner join #Country t2 on t1.CountryId=t2.CountryId
inner join #Country t3 on t1.PassportCountry=t3.CountryId
where s.EmpId=#empID;

What the best way to get values in multiple rows and set variables with out cursors in SQL Server? (PIVOT more or less)

I could not think of a better question for the problem, but here it is.
I have 3 tables in a many to many relationship like:
Students -> StudentTasks <- Tasks
The student task has a column called "Marked", and when a student is created a create a record for possible tasks and Marked = 0, Ex:
Student ---------
Given
"StudentA -> Id = 1"
Tasks ------------
Given
"Task A -> Id = 1"
"Task B -> Id = 2"
StudentTasks ------
StudentId -- TaskId -- Marked
1 -- 1 -- 0
1 -- 2 -- 0
What I need is for each row in student task I have to set variable #TaskA, #TaskB
with the respective "Marked" value, so I can update another table's column with it.
Another way to solve the problem would be to update the table directly, so given the same scenario, but with the addition of a table like so:
StudentId -- TaskA -- TaskB
I would like to see it filled like this:
StudentId -- TaskA -- TaskB
1 -- 0 -- 0
and if we had "StudentB -< Id = 2" with TaskB marked a 1 we would have:
StudentId -- TaskA -- TaskB
1 -- 0 -- 0
2 -- 0 -- 1
The way I am doing is not efficient at all, its taking 40 seconds to go through 3300 records (I am using cursors to walk through the list of students), any suggestions are welcome.
UPDATES:
Using the idea #djangojazz did with the self extracting queries
DECLARE #Student TABLE ( StudentID INT IDENTITY, Name VARCHAR(50));
INSERT INTO #Student VALUES ('Brett'),('Sean')
DECLARE #StudentTasks TABLE (StudentID INT, TaskId INT, Marked BIT);
INSERT INTO #StudentTasks VALUES (1,1,0),(1,2,0),(2,1,0),(2,2,1)
DECLARE #Tasks TABLE (TaskID INT IDENTITY, NAME VARCHAR(50));
INSERT INTO #Tasks VALUES ('Study'),('Do Test')
SELECT * FROM #Student
SELECT * FROM #StudentTasks
SELECT * FROM #Tasks
-- THIS IS WHAT I NEED IN THE RESULT
DECLARE #ResultTable TABLE (StudentId INT, Study BIT, DoTest BIT);
INSERT INTO #ResultTable VALUES (1,0,0),(2,0,1)
SELECT * FROM #ResultTable
UPDATED 6-13-13. I don't even think you need the 'Students' table right off the bat as you just want to pivot on the identifier. The problem may become though if you use repeat values for anything in the future this logic will break. Meaning if you have a table where a Student Id may be repeated for another value with a type other than bit you would then need to perform more operations to see which was the most current by an identity or such. Saying that though I can give you what you say you wanted.
DECLARE #Student TABLE ( StudentID INT IDENTITY, Name VARCHAR(50));
INSERT INTO #Student VALUES ('Brett'),('Sean')
DECLARE #StudentTasks TABLE (StudentID INT, TaskId INT, Marked BIT);
INSERT INTO #StudentTasks VALUES (1,1,0),(1,2,0),(2,1,0),(2,2,1)
DECLARE #Tasks TABLE (TaskID INT IDENTITY, NAME VARCHAR(50));
INSERT INTO #Tasks VALUES ('Study'),('Do Test')
DECLARE #ResultTable TABLE (StudentId INT, Study BIT, DoTest BIT);
INSERT INTO #ResultTable VALUES (1,0,0),(2,0,1)
select 'Results you wanted'
SELECT *
FROM #ResultTable
select 'Method A'
;
-- with case when forcing a pivot on an expression
With x as
(
select
st.StudentID
, cast(st.Marked as tinyint) as Marked
, t.NAME
from #StudentTasks st
join #Tasks t on st.TaskId = t.TaskID
)
Select
x.StudentID
, max(case when NAME = 'Study' then Marked end) as Study
, max(case when NAME = 'Do Test' then Marked end) as DoTest
FROM x
group by x.StudentID
Select 'Method B'
;
-- with traditional pivot you need to translate names I believe
With x as
(
select
st.StudentID
, cast(st.Marked as tinyint) as Marked
, case when t.NAME = 'Study' then 0 else 1 end as Name
from #StudentTasks st
join #Tasks t on st.TaskId = t.TaskID
)
Select
pvt.StudentID
, [0] as Study
, [1] as 'Do Test'
FROM x
pivot(max(x.Marked) for x.Name in ([0], [1])) as pvt
PIVOT the StudentTasks table for the 2 Tasks - A and B, JOIN it with the 'Additional table' on StudentID and Update it ..

Data deduplificaton

The code below explains best what I'm trying to accomplish. I know that I can use a cursor or other looping routine to loop through the records to find the duplicates and create my notes records based on what is found. I'm trying to avoid that, unless there's no better option.
DROP TABLE #orig
DROP TABLE #parts
DROP TABLE #part_notes
CREATE TABLE #orig(partnum VARCHAR(20), notes VARCHAR(100));
INSERT INTO #orig VALUES ('A123', 'To be used on Hyster models only')
INSERT INTO #orig VALUES ('A123', 'Right Hand model only')
INSERT INTO #orig VALUES ('A125', 'Not to be used by Jerry')
INSERT INTO #orig VALUES ('A125', NULL)
INSERT INTO #orig VALUES ('A125', 'asdfasdlfj;lsdf')
INSERT INTO #orig VALUES ('A128', 'David test')
INSERT INTO #orig VALUES ('A129', 'Fake part')
SELECT COUNT(*) FROM #orig
-- SHOW ME UNIQUE PARTS, MY PARTS TABLE SHOULD BE UNIQUE!
SELECT DISTINCT partnum FROM #orig
CREATE TABLE #parts(id INT IDENTITY(1,1), partnum VARCHAR(20));
INSERT INTO #parts
SELECT DISTINCT partnum FROM #orig
SELECT * FROM #parts
CREATE TABLE #part_notes(id INT IDENTITY(1,1), part_id INT, line_number INT, notes VARCHAR(100));
/*
HOW DO I AT THIS POINT POPULATE the #part_notes table so that it looks like this:
(note: any NULL or empty note strings should be ignored)
id part_id line_number notes
1 1 1 To be used on Hyster models only
2 1 2 Right Hand model only
3 2 1 Not to be used by Jerry
4 2 2 asdfasdlfj;lsdf
6 3 1 David test
7 4 1 Fake part
*/
The below just arbitrarily chooses line_numbers as there doesn't seem to be anything suitable to order by in the data.
SELECT p.id part_id,
p.partnum ,
ROW_NUMBER() over (partition BY p.id ORDER BY (SELECT 0)) line_number,
notes
FROM #parts p
JOIN #orig o
ON o.partnum=p.partnum
WHERE notes IS NOT NULL
AND notes <> ''
ORDER BY part_id