Replacing a 3 level deep nested cursors in SQL Server - sql

I have three SQL Server tables that I need to loop trhough and update. I did it successfully with a cursor but it is so slow that it is pretty pointless sincethe main table with all the data to loop through is over 1,000 rows long.
The tables are (with some sample data):
-- The PK is InvoiceId and the IsMajorPart is '0' or '1'.
-- The MajorPartId and SubPartId1 to 4 are "technically" FKs for PartId but aren't hooked up and will not be ever due to some external issues outside of scope.
-- The part Id's can be NULL or empty.
-- This table exists elsewhere and is loaded with Id's being varchars but in transfering they will be going in as int's which is the proper way.
CREATE TABLE dbo.Invoices(
InvoicdeId varchar(50),
PartName varchar(255),
IsMajorPart varchar(1)
MajorPartId varchar(50),
SubPartId1 varchar(50),
SubPartId2 varchar(50),
SubPartId3 varchar(50),
SubPartId4 varchar(50));
-- Sampe inserts
INSERT INTO dbo.Invoices VALUES ('1', 'A Part', '0', '', '100', '105', '' ,''):
INSERT INTO dbo.Invoices VALUES ('5', 'E Part', '1', '101', '110', '', '' ,''):
INSERT INTO dbo.Invoices VALUES ('11', 'Z Part', '1', '201', '100', '115', '' ,''):
-- Essentially the old table above is being moved into a normalized, correct tables below.
- The PK is the PartId
CREATE TABLE dbo.Parts
PartsId int,
PartName varchar(255)
-- Sampe inserts (that will be updated or inserted by looping through the first table)
INSERT INTO dbo.Parts VALUES (100,'A Part'):
INSERT INTO dbo.Parts VALUES (110,'B Part'):
INSERT INTO dbo.Parts VALUES (201,'C Part'):
-- The PK is the combination of InvoiceId and PartId
CREATE TABLE dbo.InvoiceToParts
InvoiceId int,
PartsId int,
IsMajorPart bit);
-- Sampe inserts (that will be inserted from the first table but conflicts might occur if an InvoiceId from the first table has 2 PartId's that are the same)
INSERT INTO dbo.Parts VALUES (1, 100, 0):
INSERT INTO dbo.Parts VALUES (5, 100, 1):
INSERT INTO dbo.Parts VALUES (17, 201, 0):
The sample INSERTs above are just samples of the data for seeing what is in the tables.
The rules to move Invoices (I don't care what happens to this table), into the correct tables of Parts and InvoiceToParts are below (and these last two tables are the only ones that I care about.
Loop through Invoices and get all the data.
First, find out if IsMajorPart is '1' and then get the MajorPartId.
Push the MajorPartId with PartName in Parts table if it DOESN'T already exist.
Next check InvoiceToParts to see if the PK of InvoiceId and PartId exist.
If they do, update IsMaorPart to '1'.
If they don't exist, INSERT it.
Next do the same process for all SubPartId1 to SubPartId4.
I have a nested 3-level cursor which performance-wise ran for over 30min before I stopped it as it wasn't even close to finishing and was sucking up all the resources. I am trying to look for a faster way to do this. The Invoices table can have up to about 5,000 rows in it.

You need to unpivot your data and then just do what is called an UPSERT, which has two steps:
If exists, update record(s)
If not exists, insert record(s)
Plenty of examples if you search for examples online for UPSERT
Table Setup
DROP TABLE IF EXISTS #Invoice
DROP TABLE IF EXISTS #Unpivot
DROP TABLE IF EXISTS #InvoiceToParts
DROP TABLE IF EXISTS #Parts
CREATE TABLE #Parts(
PartsId int,
PartName varchar(255)
)
CREATE TABLE #InvoiceToParts(
InvoiceId int,
PartsId int,
IsMajorPart bit
);
CREATE TABLE #Invoice(
InvoiceId varchar(50),
PartName varchar(255),
IsMajorPart varchar(1),
MajorPartsID varchar(50),
SubPartsID1 varchar(50),
SubPartsID2 varchar(50),
SubPartsID3 varchar(50),
SubPartsID4 varchar(50)
);
INSERT INTO #Invoice
VALUES ('1', 'A Part', '0', '', '100', '105', '' ,'')
,('5', 'E Part', '1', '101', '110', '', '' ,'')
,('11', 'Z Part', '1', '201', '100', '115', '' ,'')
SQL to Process Data
Will first unpivot the data, then load into Parts table first so the ID's can be referenced before inserting into the junction table InvoicetoParts
SELECT A.InvoiceId
,B.*
INTO #Unpivot
FROM #Invoice AS A
CROSS APPLY (
VALUES
(NULLIF(MajorPartsID,''),PartName,IsMajorPart)
,(NULLIF(SubPartsID1,''),NULL,0)
,(NULLIF(SubPartsID2,''),NULL,0)
,(NULLIF(SubPartsID3,''),NULL,0)
,(NULLIF(SubPartsID4,''),NULL,0)
) AS B(PartsID,PartName,IsMajorPart)
WHERE B.PartsID IS NOT NULL /*If not data, filter out*/
/*INSERT into table Parts if not exists*/
INSERT INTO #Parts
SELECT PartsID,PartName
FROM #Unpivot AS A
WHERE A.IsMajorPart = 1
AND NOT EXISTS (
SELECT *
FROM #Parts AS DTA
WHERE A.PartsID = DTA.PartsID
)
GROUP BY PartsID,PartName
/*UPSERT into table dbo.InvoiceParts*/
UPDATE #InvoiceToParts
SET IsMajorPart = B.IsMajorPart
FROM #InvoiceToParts AS A
INNER JOIN #Unpivot AS B
ON A.InvoiceId = B.InvoiceId
AND A.PartsId = B.PartsID
INSERT INTO #InvoiceToParts(InvoiceId,PartsId,IsMajorPart)
SELECT InvoiceId
,PartsId
,IsMajorPart
FROM #Unpivot AS A
WHERE NOT EXISTS (
SELECT *
FROM #InvoiceToParts AS DTA
WHERE A.InvoiceId = DTA.InvoiceID
AND A.PartsID = DTA.PartsID
)
SELECT *
FROM #InvoiceToParts
SELECT *
FROM #Parts

Related

Update Id on INSERT INTO SELECT FROM statement

i want to copy specific columns from Table [From] to Table [To] but also want to insert the foreign key from [To] in [From]
Table definitions:
[From]
Id (int)
pic (varbinary(MAX))
picId (int)
[To]
Id (int)
pic (varbinary(MAX))
My copy query works perfectly but i dont know how to update the "picId" column inside of the Table [From]
INSERT INTO dbo.[To] (Id,pic)
SELECT
isnull(T.m, 0) + row_number() over (order by F.pic),
F.pic
FROM dbo.[From] AS F
outer apply (select max(pic) as m from dbo.[To]) as T;
Now i want to copy the inserted [To].Id to [From].picId.
Can anyone help me please?
2 changes would make your solution much easier, but assuming you can't control anything about the dbo.[TO] table, here is a solution that will work for you.
The first improvement would be making dbo.[to].ID an identity column. Then you could drop your whole "row_number() over" line and let SQL manage the ID. What you're doing works, but it's like cutting wood with a chain saw by dragging the (not running) saw back and forth over the wood. You can do it, but it's a lot easier if you start the engine and let it do the work.
The second change is adding a column dbo.[to].FromID and populating it when you insert the row. The OUTPUT statement can only reference fields in the row being inserted (or deleted, but that's not relevant here) so you can't get the ID of the row in dbo.[from] that you want to update unless you have it in the row in dbo.[to] you just inserted. If you do this, you can use a plain old INSERT with an OUTPUT clause. The trick here is using a MERGE statement, and you can see a full explanation here: Is it possible to for SQL Output clause to return a column not being inserted? I strongly urge you to upvote that answer if you find this useful. I could not have provided you with this without that answer!
Anyway, here is the solution:
--Create some fake data, you'll already have dbo.[From]
CREATE TABLE #TestFrom (FromID INT, Pic nvarchar(1000), ToID INT NULL)
CREATE TABLE #TestTo (ToID INT, Pic nvarchar(1000))
--It would be much easier if your TO used an IDENTITY column instead of managing the ID manually
CREATE TABLE #TestToIdent (ToID INT IDENTITY(1,1), Pic nvarchar(1000))
INSERT INTO #TestFrom
VALUES (1, 'Test 1', NULL)
, (3, 'Test 3', NULL)
, (7, 'Test 7', NULL)
, (13, 'Test 13', NULL)
--Define a table variable to hold your OUTPUT, you'll need this
DECLARE #Mapping as table (FromID INT, ToID INT);
with cteIns as (
SELECT
isnull(T.m, 0) + row_number() over (order by F.pic) as ToID
, F.pic, F.FromID
FROM #TestFrom AS F
outer apply (select max(ToID) as m from #TestTo ) as T
) --From https://stackoverflow.com/questions/10949730/is-it-possible-to-for-sql-output-clause-to-return-a-column-not-being-inserted
MERGE INTO #TestTo as T --Put results here
USING cteIns as F --Source is here
on 0=1 --But this is never true so never merge, just INSERT
WHEN NOT MATCHED THEN --ALWAYS because 0 never = 1
INSERT (ToID, pic)
VALUES (F.ToID, F.pic)
OUTPUT F.FromID, inserted.ToID
INTO #Mapping (FromID, ToID );
--SELECT * FROM #Mapping --Test the mapping if you're curious
--SELECT * FROM #TestTo --Test the insedert if you're curious
--Update the dbo.[FROM] with the ID of the [TO] that got inserted
UPDATE #TestFrom SET ToID = M.ToID
FROM #TestFrom as F
INNER JOIN #Mapping as M
ON M.FromID = F.FromID
--View the results
SELECT * FROM #TestFrom
DROP TABLE #TestFrom
DROP TABLE #TestTo
DROP TABLE #TestToIdent

INSERT inside an INSERT statement and use its ID in the outer INSERT [duplicate]

Very simplified, I have two tables Source and Target.
declare #Source table (SourceID int identity(1,2), SourceName varchar(50))
declare #Target table (TargetID int identity(2,2), TargetName varchar(50))
insert into #Source values ('Row 1'), ('Row 2')
I would like to move all rows from #Source to #Target and know the TargetID for each SourceID because there are also the tables SourceChild and TargetChild that needs to be copied as well and I need to add the new TargetID into TargetChild.TargetID FK column.
There are a couple of solutions to this.
Use a while loop or cursors to insert one row (RBAR) to Target at a time and use scope_identity() to fill the FK of TargetChild.
Add a temp column to #Target and insert SourceID. You can then join that column to fetch the TargetID for the FK in TargetChild.
SET IDENTITY_INSERT OFF for #Target and handle assigning new values yourself. You get a range that you then use in TargetChild.TargetID.
I'm not all that fond of any of them. The one I used so far is cursors.
What I would really like to do is to use the output clause of the insert statement.
insert into #Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from #Source as S
But it is not possible
The multi-part identifier "S.SourceID" could not be bound.
But it is possible with a merge.
merge #Target as T
using #Source as S
on 0=1
when not matched then
insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;
Result
TargetID SourceID
----------- -----------
2 1
4 3
I want to know if you have used this? If you have any thoughts about the solution or see any problems with it? It works fine in simple scenarios but perhaps something ugly could happen when the query plan get really complicated due to a complicated source query. Worst scenario would be that the TargetID/SourceID pairs actually isn't a match.
MSDN has this to say about the from_table_name of the output clause.
Is a column prefix that specifies a table included in the FROM clause of a DELETE, UPDATE, or MERGE statement that is used to specify the rows to update or delete.
For some reason they don't say "rows to insert, update or delete" only "rows to update or delete".
Any thoughts are welcome and totally different solutions to the original problem is much appreciated.
In my opinion this is a great use of MERGE and output. I've used in several scenarios and haven't experienced any oddities to date.
For example, here is test setup that clones a Folder and all Files (identity) within it into a newly created Folder (guid).
DECLARE #FolderIndex TABLE (FolderId UNIQUEIDENTIFIER PRIMARY KEY, FolderName varchar(25));
INSERT INTO #FolderIndex
(FolderId, FolderName)
VALUES(newid(), 'OriginalFolder');
DECLARE #FileIndex TABLE (FileId int identity(1,1) PRIMARY KEY, FileName varchar(10));
INSERT INTO #FileIndex
(FileName)
VALUES('test.txt');
DECLARE #FileFolder TABLE (FolderId UNIQUEIDENTIFIER, FileId int, PRIMARY KEY(FolderId, FileId));
INSERT INTO #FileFolder
(FolderId, FileId)
SELECT FolderId,
FileId
FROM #FolderIndex
CROSS JOIN #FileIndex; -- just to illustrate
DECLARE #sFolder TABLE (FromFolderId UNIQUEIDENTIFIER, ToFolderId UNIQUEIDENTIFIER);
DECLARE #sFile TABLE (FromFileId int, ToFileId int);
-- copy Folder Structure
MERGE #FolderIndex fi
USING ( SELECT 1 [Dummy],
FolderId,
FolderName
FROM #FolderIndex [fi]
WHERE FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT
(FolderId, FolderName)
VALUES (newid(), 'copy_'+FolderName)
OUTPUT d.FolderId,
INSERTED.FolderId
INTO #sFolder (FromFolderId, toFolderId);
-- copy File structure
MERGE #FileIndex fi
USING ( SELECT 1 [Dummy],
fi.FileId,
fi.[FileName]
FROM #FileIndex fi
INNER
JOIN #FileFolder fm ON
fi.FileId = fm.FileId
INNER
JOIN #FolderIndex fo ON
fm.FolderId = fo.FolderId
WHERE fo.FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT ([FileName])
VALUES ([FileName])
OUTPUT d.FileId,
INSERTED.FileId
INTO #sFile (FromFileId, toFileId);
-- link new files to Folders
INSERT INTO #FileFolder (FileId, FolderId)
SELECT sfi.toFileId, sfo.toFolderId
FROM #FileFolder fm
INNER
JOIN #sFile sfi ON
fm.FileId = sfi.FromFileId
INNER
JOIN #sFolder sfo ON
fm.FolderId = sfo.FromFolderId
-- return
SELECT *
FROM #FileIndex fi
JOIN #FileFolder ff ON
fi.FileId = ff.FileId
JOIN #FolderIndex fo ON
ff.FolderId = fo.FolderId
I would like to add another example to add to #Nathan's example, as I found it somewhat confusing.
Mine uses real tables for the most part, and not temp tables.
I also got my inspiration from here: another example
-- Copy the FormSectionInstance
DECLARE #FormSectionInstanceTable TABLE(OldFormSectionInstanceId INT, NewFormSectionInstanceId INT)
;MERGE INTO [dbo].[FormSectionInstance]
USING
(
SELECT
fsi.FormSectionInstanceId [OldFormSectionInstanceId]
, #NewFormHeaderId [NewFormHeaderId]
, fsi.FormSectionId
, fsi.IsClone
, #UserId [NewCreatedByUserId]
, GETDATE() NewCreatedDate
, #UserId [NewUpdatedByUserId]
, GETDATE() NewUpdatedDate
FROM [dbo].[FormSectionInstance] fsi
WHERE fsi.[FormHeaderId] = #FormHeaderId
) tblSource ON 1=0 -- use always false condition
WHEN NOT MATCHED
THEN INSERT
( [FormHeaderId], FormSectionId, IsClone, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
VALUES( [NewFormHeaderId], FormSectionId, IsClone, NewCreatedByUserId, NewCreatedDate, NewUpdatedByUserId, NewUpdatedDate)
OUTPUT tblSource.[OldFormSectionInstanceId], INSERTED.FormSectionInstanceId
INTO #FormSectionInstanceTable(OldFormSectionInstanceId, NewFormSectionInstanceId);
-- Copy the FormDetail
INSERT INTO [dbo].[FormDetail]
(FormHeaderId, FormFieldId, FormSectionInstanceId, IsOther, Value, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
SELECT
#NewFormHeaderId, FormFieldId, fsit.NewFormSectionInstanceId, IsOther, Value, #UserId, CreatedDate, #UserId, UpdatedDate
FROM [dbo].[FormDetail] fd
INNER JOIN #FormSectionInstanceTable fsit ON fsit.OldFormSectionInstanceId = fd.FormSectionInstanceId
WHERE [FormHeaderId] = #FormHeaderId
Here's a solution that doesn't use MERGE (which I've had problems with many times I try to avoid if possible). It relies on two memory tables (you could use temp tables if you want) with IDENTITY columns that get matched, and importantly, using ORDER BY when doing the INSERT, and WHERE conditions that match between the two INSERTs... the first one holds the source IDs and the second one holds the target IDs.
-- Setup... We have a table that we need to know the old IDs and new IDs after copying.
-- We want to copy all of DocID=1
DECLARE #newDocID int = 99;
DECLARE #tbl table (RuleID int PRIMARY KEY NOT NULL IDENTITY(1, 1), DocID int, Val varchar(100));
INSERT INTO #tbl (DocID, Val) VALUES (1, 'RuleA-2'), (1, 'RuleA-1'), (2, 'RuleB-1'), (2, 'RuleB-2'), (3, 'RuleC-1'), (1, 'RuleA-3')
-- Create a break in IDENTITY values.. just to simulate more realistic data
INSERT INTO #tbl (Val) VALUES ('DeleteMe'), ('DeleteMe');
DELETE FROM #tbl WHERE Val = 'DeleteMe';
INSERT INTO #tbl (DocID, Val) VALUES (6, 'RuleE'), (7, 'RuleF');
SELECT * FROM #tbl t;
-- Declare TWO temp tables each with an IDENTITY - one will hold the RuleID of the items we are copying, other will hold the RuleID that we create
DECLARE #input table (RID int IDENTITY(1, 1), SourceRuleID int NOT NULL, Val varchar(100));
DECLARE #output table (RID int IDENTITY(1,1), TargetRuleID int NOT NULL, Val varchar(100));
-- Capture the IDs of the rows we will be copying by inserting them into the #input table
-- Important - we must specify the sort order - best thing is to use the IDENTITY of the source table (t.RuleID) that we are copying
INSERT INTO #input (SourceRuleID, Val) SELECT t.RuleID, t.Val FROM #tbl t WHERE t.DocID = 1 ORDER BY t.RuleID;
-- Copy the rows, and use the OUTPUT clause to capture the IDs of the inserted rows.
-- Important - we must use the same WHERE and ORDER BY clauses as above
INSERT INTO #tbl (DocID, Val)
OUTPUT Inserted.RuleID, Inserted.Val INTO #output(TargetRuleID, Val)
SELECT #newDocID, t.Val FROM #tbl t
WHERE t.DocID = 1
ORDER BY t.RuleID;
-- Now #input and #output should have the same # of rows, and the order of both inserts was the same, so the IDENTITY columns (RID) can be matched
-- Use this as the map from old-to-new when you are copying sub-table rows
-- Technically, #input and #output don't even need the 'Val' columns, just RID and RuleID - they were included here to prove that the rules matched
SELECT i.*, o.* FROM #output o
INNER JOIN #input i ON i.RID = o.RID
-- Confirm the matching worked
SELECT * FROM #tbl t

SQL - Copy Data Within Same Table

I'm not that great with SQL Server, but I'm trying to do some behind the scenes work to create some functionality that our EMR system lacks - copying forms (and all their data) between patients.
In SQL Server 2008 R2 I have three tables that deal with these forms that have been filled out:
**Table 1**
encounter_id patient_id date time etc etc etc etc
1234 112233 2014-01-02 14:25:01:00 a b c d
**Table 2**
encounter_id page recorded_on recorded_by etc etc
1234 1 2014-01-02 134 asdf asdf
1234 2 2014-01-02 134 jkl; jkl;
**Table 3**
encounter_id page keyname keyvalue
1234 1 key1 aaa
1234 1 key2 bbb
1234 1 key3 ccc
1234 1 key4 ddd
1234 2 key5 eee
1234 2 key6 fff
1234 2 key7 ggg
As you can see, they all match together with the encounter_id, which is linked to the patient_id (In the first table).
What I'm trying to be able to do is copy all the rows in these three tables for a particular encounter_id back into the same table they come from, but with a different (system generated) encounter_id for a patient_id that I would specify. In essence, copying the form from one patient to another.
Any help on this is greatly appreciated.
I always like creating sample tables in [tempdb] so that the syntax is correct. I created tables [t1], [t2], and [t3]. There are primary and foreign keys.
If you have a well developed schema, ERD (entity relationship diagram) http://en.wikipedia.org/wiki/Entity-relationship_diagram , these relationships should be in place.
-- Playing around
use tempdb
go
--
-- Table 1
--
-- Remove if it exists
if object_id('t1') > 0
drop table t1
go
-- Create the first table
create table t1
(
encounter_id int,
patient_id int,
the_date date,
the_time time,
constraint pk_t1 primary key (encounter_id)
);
go
-- Add one row
insert into t1 values (1234, 112233, '2014-01-02', '14:25:01:00');
go
-- Show the data
select * from t1
go
--
-- Table 2
--
-- Remove if it exists
if object_id('t2') > 0
drop table t2
go
-- Create the second table
create table t2
(
encounter_id int,
the_page int,
recorded_on date,
recorded_by int,
constraint pk_t2 primary key (encounter_id, the_page)
);
go
-- Add two rows
insert into t2 values
(1234, 1, '2014-01-02', 134),
(1234, 2, '2014-01-02', 134);
go
-- Show the data
select * from t2
go
--
-- Table 3
--
-- Remove if it exists
if object_id('t3') > 0
drop table t3
go
-- Create the third table
create table t3
(
encounter_id int,
the_page int,
key_name1 varchar(16),
key_value1 varchar(16),
constraint pk_t3 primary key (encounter_id, the_page, key_name1)
);
go
-- Add seven rows
insert into t3 values
(1234, 1, 'key1', 'aaa'),
(1234, 1, 'key2', 'bbb'),
(1234, 1, 'key3', 'ccc'),
(1234, 1, 'key4', 'ddd'),
(1234, 2, 'key5', 'eee'),
(1234, 2, 'key6', 'fff'),
(1234, 2, 'key7', 'ggg');
go
-- Show the data
select * from t3
go
--
-- Foreign Keys
--
alter table t2 with check
add constraint fk_t2 foreign key (encounter_id)
references t1 (encounter_id);
alter table t3 with check
add constraint fk_t3 foreign key (encounter_id, the_page)
references t2 (encounter_id, the_page);
Here comes the fun part, a stored procedure to duplicate the data.
--
-- Procedure to duplicate one record
--
-- Remove if it exists
if object_id('usp_Duplicate_Data') > 0
drop procedure t1
go
-- Create the procedure
create procedure usp_Duplicate_Data #OldId int, #NewId int
as
begin
-- Duplicate table 1's data
insert into t1
select
#NewId,
patient_id,
the_date,
the_time
from t1
where encounter_id = #OldId;
-- Duplicate table 2's data
insert into t2
select
#NewId,
the_page,
recorded_on,
recorded_by
from t2
where encounter_id = #OldId;
-- Duplicate table 3's data
insert into t3
select
#NewId,
the_page,
key_name1,
key_value1
from t3
where encounter_id = #OldId;
end
Last but not least, we have to call the stored procedure to make sure it works.
-- Sample call
exec usp_Duplicate_Data 1234, 7777
In summary, I did not add any error checking or accounted for a range of Id's. I leave these tasks for you to learn.
Made a little fiddle as an example, here (link)
The solution is perhaps needlessly complex but it offers a good variety of other useful stuff as well, I just wanted to test how to build that dynamically. The script does print out the commands, making it relatively easy to remove the TSQL and just produce the plain-SQL to do as you wish.
What it does, is that it requires an encounter_id, which it will then use to dynamically fetch the columns (with the assumption that encounter_id is the PK for TABLE_1) to insert a new record in TABLE_1, store the inserted.encounter_id value, and use that value to fetch and copy the matching rows from TABLE_2 and TABLE_3.
Basically, as long as the structure is correct (TABLE_1 PK is encounter_id which is an identity type), you should be able to just change the table names referenced in the script and it should work directly regardless of which types of columns (and how many of them) your particular tables have.
The beef of the script is this:
/* Script begins here */
DECLARE #ENCOUNTER_ID INT, #NEWID INT, #SQL VARCHAR(MAX), #COLUMNS VARCHAR(MAX)
IF OBJECT_ID('tempdb..##NEW_ID') IS NOT NULL
DROP TABLE ##NEW_ID
CREATE TABLE ##NEW_ID (ID INT)
/* !!! SET YOUR DESIRED encounter_id RECORDS TO BE COPIED, HERE !!! */
SET #ENCOUNTER_ID = 1234
IF EXISTS (SELECT TOP 1 1 FROM TABLE_1 WHERE encounter_id = #ENCOUNTER_ID)
BEGIN
SELECT #COLUMNS = COALESCE(#COLUMNS+', ', 'SELECT ')+name
FROM sys.columns WHERE OBJECT_NAME(object_id) = 'TABLE_1' AND name <> 'encounter_id'
SET #COLUMNS = 'INSERT INTO TABLE_1 OUTPUT inserted.encounter_id INTO ##NEW_ID '+#COLUMNS+' FROM TABLE_1 WHERE encounter_id = '+CAST(#ENCOUNTER_ID AS VARCHAR(25))
EXEC(#COLUMNS)
PRINT(#COLUMNS)
SELECT TOP 1 #NEWID = ID, #COLUMNS = NULL FROM ##NEW_ID
SELECT #COLUMNS = COALESCE(#COLUMNS+', ', '')+name
FROM sys.columns WHERE OBJECT_NAME(object_id) = 'TABLE_2'
SET #COLUMNS = 'INSERT INTO TABLE_2 ('+#COLUMNS+') SELECT '+REPLACE(#COLUMNS,'encounter_id',''+CAST(#NEWID AS VARCHAR(25))+'')
+' FROM TABLE_2 WHERE encounter_id = '+CAST(#ENCOUNTER_ID AS VARCHAR(25))
EXEC(#COLUMNS)
PRINT(#COLUMNS)
SET #COLUMNS = NULL
SELECT #COLUMNS = COALESCE(#COLUMNS+', ', '')+name
FROM sys.columns WHERE OBJECT_NAME(object_id) = 'TABLE_3'
SET #COLUMNS = 'INSERT INTO TABLE_3 ('+#COLUMNS+') SELECT '+REPLACE(#COLUMNS,'encounter_id',''+CAST(#NEWID AS VARCHAR(25))+'')
+' FROM TABLE_3 WHERE encounter_id = '+CAST(#ENCOUNTER_ID AS VARCHAR(25))
EXEC(#COLUMNS)
PRINT(#COLUMNS)
IF OBJECT_ID('tempdb..##NEW_ID') IS NOT NULL
DROP TABLE ##NEW_ID
END
declare #oldEncounterID int
set #oldEncounterID = 1234
declare #newEncounterID int
set #newEncounterID = 2345
insert into table1(encounter_id, patient_id, date, time, etc)
select newEncounterID, patient_id, date, time, etc
from table1 where encounter_id = oldEncounterID
and so on... problem with this approach you must know in advantage what all the columns are, and if they change you may change the columns accordingly
Another approach:
declare #oldEncounterID int
set #oldEncounterID = 1234
declare #newEncounterID int
set #newEncounterID = 2345
select * into #table1 from table1 where encounter_id = oldEncounterID
update #table1 set encounter_id = newEncounterID
insert into table1 select * from #table1
and so on... this second approach however may need a little adjustment if there is an identity column then you'll have to set identity_insert to on
Psuedo code, not tested:
DECLARE #patient_id INT, #date datetime, #time ??
SET #patient_id = 112244 --your patient id
INSERT INTO [**Table 1**] (patient_id, date, time, etc, etc, etc, etc)
VALUES (#patient_id, #date, #time, 'a', 'b', 'c', 'd')
DECLARE #encounter_id int
SET #encounter_id = SCOPE_IDENTITY -- or select #encounter_id = encounter_id from [**Table 1**] where patientId = #patient_id
INSERT INTO [**Table 2**] (encounter_id, page, recorded_on, recorded_by, etc, etc2)
SELECT #encounter_id, page, recorded_on, recorded_by, etc, etc2
FROM [**Table 2**]
WHERE encounter_id = 1234
INSERT INTO [**Table 3**] (encounter_id, page, keyname, keyvalue)
SELECT #encounter_id, page, keyname, keyvalue
FROM [**Table 3**]
WHERE encounter_id = 1234

Dynamically display rows as columns

I couldn't think of a good way to word the title, if anyone can come up with something better please feel free. Basically there is an old VB6 app that pulls data from a db that I have more or less completely restructured and gives the user a dump of all of the product information at once. So I need to do some inner joins to get all of these tables together. I know how to do basic inner joins but I am stuck on one detail. There are a few tables where there are multiple entries for each item. For example, the CrossReference table may have multiple cross reference numbers for an item, or it may only have one, or it may have none at all. Is it possible to have those placed dynamically into separate columns. so
this:
Item CrossReferenceNumber
XXXXX crossref1
XXXXX crossref2
XXXXX crossref3
could become this (after a join with some other tables):
Item BasePart Size CrossReferenceNumber1 CrossReferenceNumber2 CrossReferenceNumber3
XXXX XXXX Large crossref1 crossref2 crossref3
But if there were no cross references, there would be no cross reference columns. Is something like that possible or am I dreaming?
Oracle 11g and Sql Server 2005+ both contain a pivot command that will accomplish what you want.
http://www.orafaq.com/wiki/PIVOT
http://msdn.microsoft.com/en-us/library/ms177410.aspx
Otherwise you would need to build a dynamic sql statement to achieve this.
Edit - Here you go (SQL Server version).
/* Begin Set up of test data */
IF EXISTS (SELECT 1 from sys.tables WHERE name = N'Item')
DROP TABLE Item
GO
IF EXISTS (SELECT 1 from sys.tables WHERE name = N'CrossReference')
DROP TABLE CrossReference
GO
CREATE TABLE Item
(
Item varchar(20),
BasePart varchar(20),
Size varchar(20)
);
CREATE Table CrossReference
(
Item varchar(20),
CrossReferenceNumber varchar(20)
);
INSERT INTO Item VALUES ('item1', 'b1', 'Large');
INSERT INTO Item VALUES ('item2', 'bxx1', 'Large');
INSERT INTO Item VALUES ('item3', 'bddf1', 'Small');
INSERT INTO Item VALUES ('item4', 'be3f1', 'Small');
INSERT INTO Item VALUES ('item5', 'b13vx1', 'Small');
INSERT INTO CrossReference VALUES( 'item1', 'crossRef1')
INSERT INTO CrossReference VALUES('item1', 'crossRef2')
INSERT INTO CrossReference VALUES('item1', 'crossRef3')
INSERT INTO CrossReference VALUES('item1', 'crossRef4')
INSERT INTO CrossReference VALUES('item2', 'crossRef1')
INSERT INTO CrossReference VALUES('item2', 'crossRef1')
INSERT INTO CrossReference VALUES('item3', 'crossRef1')
INSERT INTO CrossReference VALUES('item4', 'crossRef2')
INSERT INTO CrossReference VALUES('item5', 'crossRef5')
INSERT INTO CrossReference VALUES('item5', 'crossRef1')
INSERT INTO CrossReference VALUES('item5', 'crossRef2')
INSERT INTO CrossReference VALUES('item5', 'crossRef3')
/* End of test data setup */
/* Begin of actual query */
DECLARE #xRefs VARCHAR(2000),
#query VARCHAR(8000)
SELECT #xRefs = STUFF((SELECT DISTINCT '],[' + ltrim(CrossReferenceNumber)
FROM CrossReference
ORDER BY '],[' + ltrim(CrossReferenceNumber)
FOR XML PATH('')
), 1, 2, '') + ']'
SET #query =
'SELECT *
FROM Item i
INNER JOIN
(
SELECT *
FROM
(
SELECT Item, CrossReferenceNumber
FROM CrossReference
) t
PIVOT (MAX(CrossReferenceNumber) FOR CrossReferenceNumber IN (' + #xRefs + ')) as pvt
) xRefs
ON i.Item = xRefs.Item
ORDER BY i.Item'
EXECUTE (#query)
/* end */

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