sql server 2005 create temporary table using xml parameter - sql-server

I want to create a temporary table from a xml input parameter.
This is my XML:
<Offices>
<Group id="22807">
<Office>185901</Office>
<Office>185902</Office>
<Office>185944</Office>
</Group>
</Offices>
This is my SQL:
DECLARE #GroupsOfficeIDs xml
SET #GroupsOfficeIDs = '<Offices><Group id="22807"><Office>185901</Office><Office>185902</Office><Office>185944</Office></Group></Offices>'
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
INSERT INTO #GroupOfficeID VALUES (#GroupsOfficeIDs)
SELECT PK,
group.ref.value('#id', 'int') AS GroupID,
group.ref.value('(Office/text())[1]', 'varchar(20)') AS OfficeID
FROM #GroupOfficeID go cross apply go.IdXml.nodes('/Offices/Group') group(ref)
This returns 1 row:
PK GroupID OfficeID
1 22807 185901
I would like it to return the following:
PK GroupID OfficeID
1 22807 185901
2 22807 185902
3 22807 185944
Is it my XML that is wrong or my query?
Thanks!
UPDATE
I got a little bit further...
My query is now this:
DECLARE #GroupsOfficeIDs xml
SET #GroupsOfficeIDs = '<Offices><Group id="22807"><Office>185901</Office><Office>185902</Office><Office>185944</Office></Group></Offices>'
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
INSERT INTO #GroupOfficeID VALUES (#GroupsOfficeIDs)
SELECT PK,
group.ref.value('#id', 'int') AS GroupID,
office.ref.value('(Office/text())[1]', 'varchar(20)') AS OfficeID
FROM #GroupOfficeID go cross apply go.IdXml.nodes('/Offices/Group') group(ref)
cross apply go.IdXml.nodes('/Offices/Group/Office') as office(ref)
It produces this:
PK GroupID OfficeID
1 22807 185901
1 22807 185902
1 22807 185944
Why doesn't the primary key increment by 1?

You got the same value for PK because the temp table stores one XML data in one row with one PK, which means all data that come from the same XML source will have the same primary key :
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- here you associate one XML data to one identity id
You may want to alter temporary table structure to store one GroupID-OfficeID combination as one row instead :
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), GroupID int, OfficeID int)
Then the query for insert would be like so (avoid using keyword like GO and GROUP for alias!) :
INSERT INTO #GroupOfficeID(GroupID,OfficeID)
SELECT
g.value('#id','int') GroupID,
o.value('.','int') OfficeID
FROM #GroupsOfficeIDs.nodes('/Offices/Group') grp(g)
CROSS APPLY g.nodes('Office') office(o)
Then SELECT * FROM #GroupOfficeID will produce the correct expected result :
PK GroupID OfficeID
1 22807 185901
2 22807 185902
3 22807 185944
SQL Fiddle Demo

Related

T-SQL insert and update foreign key without cursor

I have two tables in MS SQL:
CREATE TABLE Table1 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
insert into Table1 (TEXTVal) values('aaa');
insert into Table1 (TEXTVal) values('bbb'); insert into Table1 (TEXTVal) values('ccc');
CREATE TABLE Table2 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
Id are identity columns. I want to copy TEXTVal values from Table1 to Table2:
INSERT INTO Table2 (TEXTVal)
SELECT TEXTVal FROM Table1
where TEXTVal <> 'ccc'
and after that update column Table2Id in Table1 with appropriate values of Id from Table2. I can do this with cursor and SCOPE_IDENTITY().
I am just wondering, is there a way to do it without cursor in T-SQL?
As Jeroen stated in comments, you'll want to use OUTPUT. In the following example if you don't have an AdventureWorks database, just use a test database. You should be able to copy/paste this and just run it to see it in action!
USE AdventureWorks;
GO
----Creating the table which will store permanent table
CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))
----Creating temp table to store ovalues of OUTPUT clause
DECLARE #TmpTable TABLE (ID_New INT, TEXTVal_New VARCHAR(100),ID_Old INT, TEXTVal_Old VARCHAR(100))
----Insert values in real table
INSERT TestTable (ID, TEXTVal)
VALUES (1,'FirstVal')
INSERT TestTable (ID, TEXTVal)
VALUES (2,'SecondVal')
----Update the table and insert values in temp table using Output clause
UPDATE TestTable
SET TEXTVal = 'NewValue'
OUTPUT Inserted.ID, Inserted.TEXTVal, Deleted.ID, Deleted.TEXTVal INTO #TmpTable
WHERE ID IN (1,2)
----Check the values in the temp table and real table
----The values in both the tables will be same
SELECT * FROM #TmpTable
SELECT * FROM TestTable
----Clean up time
DROP TABLE TestTable
GO
ResultSet:
TmpTable:
ID_New TextVal_New ID_Old TextVal_Old
——————— ——————— ——————— ———————
1 NewValue 1 FirstVal
2 NewValue 2 SecondVal
Original Table:
ID TextVal
——————— ———————
1 NewValue
2 NewValue
As you can see it is possible to capture new values, and the values you are updating. In this example I'm just stuffing them into a table variable but you could do whatever you'd like with them. :)

Is there a way to retrieve inserted identity as well as some values from the query in an INSERT SELECT?

I have a situation in which I need to insert some values from a query into a table that has an identity PK. For some of the records, I need also to insert values in another table which has a 1-to-1 (partial) relationship:
CREATE TABLE A (
Id int identity primary key clustered,
Somevalue varchar(100),
SomeOtherValue int)
CREATE TABLE B (Id int primary key clustered,
SomeFlag bit)
DECLARE #inserted TABLE(NewId int, OldId)
INSERT INTO A (Somevalue)
OUTPUT Inserted.Id into #inserted(NewId)
SELECT SomeValue
FROM A
WHERE <certain condition>
INSERT INTO B (Id, SomeFlag)
SELECT
i.NewId, B.SomeFlag
FROM #inserted i
JOIN A ON <some condition>
JOIN B ON A.Id = B.Id
The problem is that the query from A in the first INSERT/SELECT returns records that can only be differentiated by the Id, which I cannot insert. Unfortunately I cannot change the structure of the A table, to insert the "previous" Id which would solve my problem.
Any idea that could lead to a solution?
With INSERT ... OUTPUT ... SELECT ... you can't output columns that are not in the target table. You can try MERGE instead:
MERGE INTO A as tgt
USING (SELECT Id, SomeValue FROM A WHERE <your conditions>) AS src
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (SomeValue)
VALUES (src.SomeValue)
OUTPUT (inserted.Id, src.Id) -- this is your new Id / old Id mapping
INTO #inserted
;
SCOPE_IDENTITY() returns the last identity value generated by the current session and current scope. You could stick that into a #table and use that to insert into B
SELECT SCOPE_IDENTITY() as newid into #c
Though, your INSERT INTO B join conditions implies to me that the value in B is already known ?

Use same GUID twice per row

I am building an application to transfer data from an SQL server to an offsite location via ftp and XML files.
I am building the XML data for each file via a query with FOR XML PATH('path'), TYPE.
I'm going to use a GUID to generate the filename as well as use as an identiifier within the file, currently my SQL to get the table is as follows (simplified):
SELECT LVL1.inv_account_no
, LVL1.cus_postcode
, CONVERT(varchar(255),NEWID()) + '.xml' as FileName
, (SELECT (SELECT CONVERT(varchar(255),NEWID()) FOR XML PATH('ident'), TYPE), (
SELECT.... [rest of very long nested select code for generating XML]
SQL Fiddle Example
This is giving me:
Account Postcode FileName xCol
AD0001 B30 3HX 2DF21466-2DA3-4D62-8B9B-FC3DF7BD1A00 <ident>656700EA-8FD5-4936-8172-0135DC49D200</ident>
AS0010 NN12 8TN 58339997-8271-4D8C-9C55-403DE98F06BE <ident>78F8078B-629E-4906-9C6B-2AE21782DC1D</ident>
Basically different GUID's for each row/use of NEWID().
Is there a way I can insert the same GUID into both columns without incrementing a cursor or doing two updates?
Try something like this:
SELECT GeneratedGuid, GeneratedGuid
FROM YourTable
LEFT JOIN (SELECT NEWID() AS GeneratedGuid) AS gg ON 1 = 1
"GeneratedGuid" has a different GUID for every row.
You could use a Common Table Expression to generate the NEWID for each resulting row.
Here is the SQL Fiddle : http://www.sqlfiddle.com/#!3/74c0c/1
CREATE TABLE TBL (
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
GCOL VARCHAR(255),
XCOL XML)
create table tbl2 (
id int identity(1,1) not null primary key,
foo int not null )
insert into tbl2 (foo) values
(10),(20),(30)
; WITH cte_a as ( select NEWID() as ID )
INSERT INTO TBL
SELECT CONVERT(varchar(255),cte_a.ID )
, (SELECT CONVERT(varchar(255),cte_a.ID) FOR XML PATH('Output'), TYPE)
from tbl2, cte_a
Create a temporary variable and assign a NEWID() to it. Then you can use this variable as many times in your SELECT or INSERT queries. Value of temporary variable remain same till it's scope. In following example #gid is a temporary variable and assigned a GUID value as VARCHAR(36)
DECLARE #gid varchar(36)
SET #gid = CAST(NEWID() AS VARCHAR(36))
INSERT INTO [tableName] (col1, col2) VALUES(#gid, #gid)
after executing the above query col1 and col2 from [tablename] will have same guid value.

Inserting Multiple values of int type into one column

Hey so I am new to sql and am building an inventory management system. So for the database i am getting stuck at this point where I need to insert the various user id's to different Teams in the company, hence my problem arises when I am trying to assign multiple int values to a particular team.The DB is made in a manner where it requires a TeamId and the corresponding UserId's to go with it.
Possible this be helpful for you -
DECLARE #temp TABLE (ID INT)
-- For 2008 and higher
INSERT INTO #temp (ID)
VALUES (1), (2), (3)
-- For 2005 and higher
INSERT INTO #temp (ID)
SELECT ID
FROM (
SELECT ID = 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
) t
SELECT *
FROM #temp
Update (comment #Sivakumar: "1,19 is not an integer. It is a varchar."):
DECLARE #temp TABLE (txt varchar(500))
INSERT INTO #temp (txt)
VALUES ('1,19'), ('2,18')
SELECT t.c.value('.', 'INT')
FROM (
SELECT txml = CAST('<t>' + REPLACE(txt, ',', '</t><t>') + '</t>' AS XML)
FROM #temp
) a
CROSS APPLY txml.nodes('/t') AS t(c)
It sounds like a one-to-many relationship: a Team can have zero or more Users.
So create a foreign key column in User that references the Team primary key.
Something like this (check my syntax):
create table team (
id int not null auto_increment,
primary key(id);
);
create table user (
id int not null auto_increment,
tid int,
primary key(id),
foreign key tid references(team);
);
select *
from team t
join user u
on t.id = u.tid;

Split table and insert with identity link

I have 3 tables similar to the sctructure below
CREATE TABLE [dbo].[EmpBasic](
[EmpID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Name] [varchar](50),
[Address] [varchar](50)
)
CREATE TABLE [dbo].[EmpProject](
[EmpID] [int] NOT NULL primary key, // referencing column with EmpBasic
[EmpProject] [varchar](50) )
CREATE TABLE [dbo].[EmpFull_Temp](
[ObjectID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[T1Name] [varchar](50) ,
[T1Address] [varchar](50) ,
[T1EmpProject] [varchar](50)
)
The EmpFull_Temp table has the records with a dummy object ID column... I want to populate the first 2 tables with the records in this table... But with EmpID as a reference between the first 2 tables.
I tried this in a stored procedure...
Create Table #IDSS (EmpID bigint, objID bigint)
Insert into EmpBasic
output Inserted.EmpID, EmpFull_Temp.ObjectID
into #IDSS
Select T1Name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from #IDSS as A, EmpFull_Temp as B
Where A.ObjID = B.ObjectID
But it says.. The multi-part identifier "EmpFull_Temp.ObjectID" could not be bound.
Could you please help me in achieving this...
Edit : There is no guarantee that [Name]+[Address] would be unique across [EmpBasic] Table
With your EmpProject join table, you probably don't want the primary key constraint on only the EmpID column
DECLARE #Count int
DECLARE #NextEmpID int
DECLARE #StartObjectID int
DECLARE #EndObjectID int
-- range of IDs to transfer (inclusive)
SET #StartObjectID = 1
SET #EndObjectID = 105
BEGIN TRAN
-- lock tables so IDENT_CURRENT is valid
SELECT #Count = COUNT(*) FROM [EmpBasic] WITH (TABLOCKX, HOLDLOCK)
SELECT #Count = COUNT(*) FROM [EmpProject] WITH (TABLOCKX, HOLDLOCK)
SELECT #NextEmpID = IDENT_CURRENT('EmpBasic')
SET IDENTITY_INSERT [EmpBasic] ON
INSERT [EmpBasic] ([EmpID], [Name], [Address])
SELECT #NextEmpID + ROW_NUMBER() OVER(ORDER BY ObjectID), [T1Name], [T1Address]
FROM [EmpFull_Temp]
WHERE [ObjectID] BETWEEN #StartObjectID AND #EndObjectID
SET IDENTITY_INSERT [EmpBasic] OFF
INSERT [EmpProject]([EmpID], [EmpProject])
SELECT #NextEmpID + ROW_NUMBER() OVER(ORDER BY ObjectID), [T1EmpProject]
FROM [EmpFull_Temp]
WHERE [ObjectID] BETWEEN #StartObjectID AND #EndObjectID
COMMIT TRAN
The solution to this problem depends on whether the "parent" table (i.e. the one with the IDENTITY column) has a natural key (i.e. one or more fields which, when combined, are guaranteed to be unique, other than the surrogate primary key).
For example, in this case, is the combinaton of Name and Address aways going to be unique?
If the answer is yes then you can simply insert into EmpBasic without bothering to output and store the generated IDs. You can then insert into EmpProject joining back on to EmpBasic using the natural key (e.g. name and address) to fnd the correct EmpID.
Insert into EmpBasic
Select T1Name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from EmpBasic as A, EmpFull_Temp as B
Where A.Name = B.Name And A.Address = B.Address
If the answer is no then there is no easy solution I know of - in SQL Server 2005 (I've no idea if this is any different in 2008), it's not possible to OUTPUT values that are not inserted. I've got around this issue in the past by using one of the other fields (e.g. Name) to temporarily store the original ID (in this case, ObjectID), use that to join when inserting the child records as described above and then gone back to update the parent records o remove/replace the temporary values. It's not nice but I've not found a better way.
Insert into EmpBasic
Select cast(ObjectID as varchar(50)) as name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from EmpBasic as A, EmpFull_Temp as B
Where A.Name = cast(B.ObjectID as varchar(50))
Update EmpBasic
Set Name = B.T1Name
from EmpBasic as A, EmpFull_Temp as B
Where A.Name = cast(B.ObjectID as varchar(50))
Please note: I've not tested the sample SQL given above but I hope it gives you an idea of how you might approach this.
Add an ObjectID column to the EmpBasic table to facilitate the data transfer then drop it when you're done. I'm assuming this is a one-time operation, I don't recommend adding and dropping a column if this is on-going
I have used the Stack Exchange Data Explorer to investigate alternative solutions. The only one with promise at the moment is shown here. It is effectively #ScotHauder's answer, except using a temporary table that has the ObjectID column and using IDENTITY_INSERT to move the generated EmpId values into EmpBasic.
If you have to do this multiple times you need to get the EmpBasic_Temp EmpId IDENTITY starting value to be Max(EmpBasic.EmpID)+1.

Resources