I'm trying to write a SQL script to import data from an XML. In the database, I prepared 2 tables:
* CodeGroup with the following fields: Code / Description
CodeValue with the following fields: Code / Description / CodeGroupId (FK - int)
=> 1 CodeGroup have many CodeValue
Here's a part of the XML:
<MASTERDATA plant="SXB">
<CODEGROUPS>
<CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
<CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
</CODEGROUPS>
</MASTERDATA>
I don't know if it's possible to write in a single MERGE INTO statement the ability to insert the values in CodeGroup and in CodeValue:
MERGE INTO [dbo].[CodeGroup] AS TARGET
USING
(
SELECT DISTINCT
d.x.value('../../#plant[1]', 'nvarchar(15)') as PlantId,
d.x.value('INGRP[1]', 'nchar(3)') as Code,
d.x.value('INGRPX[1]', 'nvarchar(20)') as Name
FROM #data.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC')as d(x)
)
AS SOURCE ON (SOURCE.Code= TARGET.Code
AND SOURCE.PlantId = TARGET.PlantId)
WHEN MATCHED
THEN UPDATE SET SOURCE.Name= TARGET.Name
WHEN NOT MATCHED BY TARGET
THEN INSERT (PlantId, Code, Name)
VALUES (SOURCE.PlantId, SOURCE.Code, SOURCE.Name)
WHEN NOT MATCHED BY SOURCE AND (TARGET.PlantId in (SELECT PlantId FROM #TempTable))
THEN DELETE;
That's what I prepared but I didn't find out how to and the "children" (CodeValue of a CodeGroup)
Thank you for your help
You are targeting two different tables. This cannot be done in a single statement.
Try this code:
DECLARE #xml XML=
'<MASTERDATA plant="SXB">
<CODEGROUPS>
<CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
<CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
</CODEGROUPS>
</MASTERDATA>';
SELECT #xml.value('(/MASTERDATA/#plant)[1]','varchar(100)') AS Masterdata_plant
,cGr.value('(CODEGROUP/text())[1]','varchar(100)') AS CodeGroup
,cGr.value('(CODEGROUPX/text())[1]','varchar(100)') AS CodeGroupX
,cVl.value('(CODEVALUE/text())[1]','varchar(100)') AS CodeValue
,cVl.value('(CODEVALUEX/text())[1]','varchar(100)') AS CodeValueX
INTO #tmpXmlData
FROM #xml.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC') A(cGr)
OUTER APPLY cGr.nodes('CODEVALUES/CODEVALUEC') B(cVl);
SELECT * FROM #tmpXmlData;
Use the result you find in #tmpXmlData to run MERGE statements against both tables separately.
Shnugo through out the challenge, when he said it was impossible to do this in one MERGE statement. And he was right, I think. However, it got me thinking because I have used the OUTPUT clause many times to solve this type of problem, which I think is a MASTER - DETAIL merge, when the key of the master table is not the business key and is an IDENTITY() or NEWID().
What is the most efficient way of doing what you want to do? Shnugo gave you a partial solution, so I have enhanced it (thanks and credit to Shnugo) to give you what you want but it uses an INSERT into temp table, then two MERGE statements. One for the master and one for the detail table.
The first MERGE uses a trick that allows you to retrieve the new IDENTITY field of the new rows, so that you don't have to join back to the master table by the business keys when doing the detail table. I hope this helps.
Here's my code:
Assumptions:
I have put NOT NULLs on several fields.
I have put a CASCADE DELETE on the foreign key reference.
create table dbo.CodeGroup (
CodeGroupId int identity(1,1) primary key,
Masterdata_plant varchar(100) not null,
Code varchar(100) not null,
Description varchar(100) not null
)
go
CREATE NONCLUSTERED INDEX [IX_CodeGroup] ON dbo.CodeGroup
(
[Masterdata_plant] ASC,
[Code] ASC
)
go
create table dbo.CodeValue
(
Code varchar(100) not null,
CodeGroupId int not null,
Description varchar(100) not null,
CONSTRAINT PK_CodeValue PRIMARY KEY CLUSTERED
(
Code ASC,
CodeGroupId ASC
)
)
go
ALTER TABLE dbo.CodeValue WITH CHECK ADD CONSTRAINT FK_CodeValue_CodeGroup FOREIGN KEY(CodeGroupId)
REFERENCES dbo.CodeGroup (CodeGroupId)
ON DELETE CASCADE
go
ALTER TABLE dbo.CodeValue CHECK CONSTRAINT FK_CodeValue_CodeGroup
go
delete from dbo.CodeGroup
go
delete from dbo.CodeValue
go
DECLARE #data XML=
'<MASTERDATA plant="SXB">
<CODEGROUPS>
<CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
<CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
<CODEVALUES>
<CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
<CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
</CODEVALUES>
</CODEGROUPC>
</CODEGROUPS>
</MASTERDATA>'
if object_id('tempdb..#tmpXmlData') is not null Begin
drop table #tmpXmlData
End
create table #tmpXmlData (
ExistingCodeGroupId int,
Plant varchar(100),
CodeGroup varchar(100),
Description varchar(100),
CodeValue varchar(100),
CodeValueDescription varchar(100)
)
insert into #tmpXmlData
SELECT gc.CodeGroupId
,#data.value('(/MASTERDATA/#plant)[1]','varchar(100)') AS Plant
,cGr.value('(CODEGROUP/text())[1]','varchar(100)') AS CodeGroup
,cGr.value('(CODEGROUPX/text())[1]','varchar(100)') AS Description
,cVl.value('(CODEVALUE/text())[1]','varchar(100)') AS CodeValue
,cVl.value('(CODEVALUEX/text())[1]','varchar(100)') AS CodeValueDescription
FROM #data.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC') A(cGr)
OUTER APPLY cGr.nodes('CODEVALUES/CODEVALUEC') B(cVl)
left join CodeGroup gc
on gc.Masterdata_plant = #data.value('(/MASTERDATA/#plant)[1]','varchar(100)')
and gc.Code = cGr.value('(CODEGROUP/text())[1]','varchar(100)')
SELECT * FROM #tmpXmlData;
if object_id('tempdb..#IDs') is not null Begin
drop table #IDs
end
create table #IDs(
CodeGroupID int,
Plant varchar(100),
Code varchar(100),
Description varchar(100)
)
go
CREATE NONCLUSTERED INDEX [IX_CodeGroup] ON #IDs
(
[Plant] ASC,
[Code] ASC
)
;MERGE INTO dbo.CodeGroup AS TARGET
USING
(
select ExistingCodeGroupId, Plant, CodeGroup,Description
from #tmpXmlData
group by ExistingCodeGroupId, Plant, CodeGroup,Description
)
AS SOURCE ON (SOURCE.ExistingCodeGroupId= TARGET.CodeGroupId)
--Plant, and CodeGroup must have matched already in left join above.
WHEN MATCHED
and TARGET.Description <> SOURCE.Description
THEN UPDATE SET TARGET.Description = SOURCE.Description
WHEN NOT MATCHED BY TARGET
THEN INSERT (Masterdata_plant, Code, Description)
VALUES (SOURCE.Plant, SOURCE.CodeGroup, SOURCE.Description)
WHEN NOT MATCHED BY SOURCE
THEN DELETE
output inserted.*
into #IDs; --This gets us the new CodeGroupId's
select * from #IDs
;MERGE INTO dbo.CodeValue AS TARGET
USING
(
select CodeGroupID=coalesce(d.ExistingCodeGroupId,i.CodeGroupID), CodeValue, CodeValueDescription
from #tmpXmlData d
left join #IDs i
on i.Plant = d.Plant
and i.Code = d.CodeGroup
group by coalesce(d.ExistingCodeGroupId,i.CodeGroupID), CodeValue, CodeValueDescription
)
AS SOURCE ON (SOURCE.CodeGroupID= TARGET.CodeGroupID
and SOURCE.CodeValue = Target.Code)
WHEN MATCHED
and TARGET.Description <> SOURCE.CodeValueDescription
THEN UPDATE SET TARGET.Description = SOURCE.CodeValueDescription
WHEN NOT MATCHED BY TARGET
THEN INSERT (CodeGroupID, Code, Description)
VALUES (SOURCE.CodeGroupID, SOURCE.CodeValue, SOURCE.CodeValueDescription)
WHEN NOT MATCHED BY SOURCE
THEN DELETE
output inserted.*;
select * from dbo.CodeGroup
select * from dbo.CodeValue
Related
I have a table (for example) with products and second with descriptions of these products.
I've created in procedure with one temp table which contains all ID of products that are NOT in table with description. Now I want to add these all descriptions for Ids from temp table. How can I achieve this? Loop cant be I think cause it will never end.
Please help ;)
I cannot use a trigger, and I'm using SQL Server 2016.
IF OBJECT_ID('tempdb.dbo.#Pr') IS NOT NULL
DROP TABLE #Pr;
CREATE TABLE #Pr
(
[Id] int
);
INSERT INTO #Pr
SELECT
[Id]
FROM
[db].[Products]
IF OBJECT_ID('tempdb.dbo.#Pr2') IS NOT NULL
DROP TABLE #Pr2;
CREATE TABLE #Pr2
(
[Id] int
);
INSERT INTO #Pr2
SELECT
[Id]
FROM
#Pr p1
WHERE
NOT EXISTS (SELECT [Id]
FROM [db].[Descriptions] p2
WHERE p1.[Id] = p2.[Id])
IF EXISTS (SELECT NULL FROM #Pr2)
You seem to want insert with a check that something is not already in the table. NOT EXISTS can be used for this purpose:
insert into products (product, description)
select product, description
from temp_products tp
where not exists (select 1 from products p where p.product = tp.product);
I have table of transaction which contains a column transactionId that has values like |H000021|B1|.
I need to make a join with table Category which has a column CategoryID with values like H000021.
I cannot apply join unless data is same.
So I want to split or remove the unnecessary data contained in TransctionId so that I can join both tables.
Kindly help me with the solutions.
Create a computed column with the code only.
Initial scenario:
create table Transactions
(
transactionId varchar(12) primary key,
whatever varchar(100)
)
create table Category
(
transactionId varchar(7) primary key,
name varchar(100)
)
insert into Transactions
select'|H000021|B1|', 'Anything'
insert into Category
select 'H000021', 'A category'
Add computed column:
alter table Transactions add transactionId_code as substring(transactionid, 2, 7) persisted
Join using the new computed column:
select *
from Transactions t
inner join Category c on t.transactionId_code = c.transactionId
Get a straighforward query plan:
You should fix your data so the columns are the same. But sometimes we are stuck with other people's bad design decisions. In particular, the transaction data should contain a column for the category -- even if the category is part of the id.
In any case:
select . . .
from transaction t join
category c
on transactionid like '|' + categoryid + |%';
Or if the category id is always 7 characters:
select . . .
from transaction t join
category c
on categoryid = substring(transactionid, 2, 7)
You can do this using query :
CREATE TABLE #MyTable
(PrimaryKey int PRIMARY KEY,
KeyTransacFull varchar(50)
);
GO
CREATE TABLE #MyTransaction
(PrimaryKey int PRIMARY KEY,
KeyTransac varchar(50)
);
GO
INSERT INTO #MyTable
SELECT 1, '|H000021|B1|'
INSERT INTO #MyTable
SELECT 2, '|H000021|B1|'
INSERT INTO #MyTransaction
SELECT 1, 'H000021'
SELECT * FROM #MyTable
SELECT * FROM #MyTransaction
SELECT *
FROM #MyTable
JOIN #MyTransaction ON KeyTransacFull LIKE '|'+KeyTransac+'|%'
DROP TABLE #MyTable
DROP TABLE #MyTransaction
I'm new in query and SQL server, and I wanted to know about DDL.
I have an attribute in my Table which has more than 1 value, such as
Size = {'S', 'M', 'L'}.
How could i make the attribute in my Table with query, so i can insert multi values to one of my attribute?
You don't want to do this, because this is denormalizing your data. But, if you must.
declare #table table (id int identity(1,1), size varchar(16))
insert into #table
values
('S')
,('M')
select * from #table
update #table
set size = size + ',M'
where id = 1
select * from #table
Here is a one to many approach with a foreign key
create table #items (id int identity(1,1), descrip varchar(64))
insert into #items
values
('shirt'),
('pants')
create table #item_sizes (id int identity(1,1), size char(1), item_id int)
alter table #item_sizes
add constraint FK_item foreign key (item_id) references #items(id)
insert into #item_sizes
values
('S',1)
,('M',1)
,('L',1)
,('S',2)
select
ItemID = i.id
,i.descrip
,isiz.size
from #items i
inner join #item_sizes isiz
on isiz.item_id = i.id
drop table #items, #item_sizes
As per your requirement :
Product (...,ProductSize);
INSERT INTO Product (ProductSize) VALUES ('S')
How to query the row of ProductSize so i can insert more than one values in SQL? –
you can query like below
select * from Product where ProductSize like '%S%' or ProductSize like '%L%' or ProductSize like '%M%'
PSeudo Code:
Create Procedure SP_GetAllData
(#Count,#EmailID)
Create table #tempTable
(
Id Int Not Null Identity (1,1)
Message nvarchar (max)
)
Insert into SecondTable
(EmailID,Message,Subject,MessageID,)
(#EmailID,'Select Message from #tempTable','Message Subjest','Select ID from #tempTable')
How to insert the data into temptable and then into second table?
Here in the above statement i want to insert both the records from FirstTable into SecondTable along with its existing columns
I think this is something what you need, you do not need a temp table for this operation anyway, but you do need to order by some column otherwise TOP Clause without an ORDER BY is pretty meaningless.
Create Procedure SP_GetAllData
#Count INT
,#EmailID INT
AS
BEGIN
SET NOCOUNT ON;
Insert into SecondTable (EmailID,[Message], [Subject] ,MessageID)
Select top (#Count) #EmailID,[Message], [Subject] ,ID
from FirstTable
-- ORDER BY SomeColumn
END
Let us say I have a table (everything is very much simplified):
create table OriginalData (
ItemName NVARCHAR(255) not null
)
And I would like to insert its data (set based!) into two tables which model inheritance
create table Statements (
Id int IDENTITY NOT NULL,
ProposalDateTime DATETIME null
)
create table Items (
StatementFk INT not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk)
)
Statements is the parent table and Items is the child table. I have no problem doing this with one row which involves the use of IDENT_CURRENT but I have no idea how to do this set based (i.e. enter several rows into both tables).
Thanks.
Best wishes,
Christian
Another possible method that would prevent the use of cursors, which is generally not a best practice for SQL, is listed below... It uses the OUTPUT clause to capture the insert results from the one table to be used in the insert to the second table.
Note this example makes one assumption in the fact that I moved your IDENTITY column to the Items table. I believe that would be acceptable, atleast based on your original table layout, since the primary key of that table is the StatementFK column.
Note this example code was tested via SQL 2005...
IF OBJECT_ID('tempdb..#OriginalData') IS NOT NULL
DROP TABLE #OriginalData
IF OBJECT_ID('tempdb..#Statements') IS NOT NULL
DROP TABLE #Statements
IF OBJECT_ID('tempdb..#Items') IS NOT NULL
DROP TABLE #Items
create table #OriginalData
( ItemName NVARCHAR(255) not null )
create table #Statements
( Id int NOT NULL,
ProposalDateTime DATETIME null )
create table #Items
( StatementFk INT IDENTITY not null,
ItemName NVARCHAR(255) null,
primary key (StatementFk) )
INSERT INTO #OriginalData
( ItemName )
SELECT 'Shirt'
UNION ALL SELECT 'Pants'
UNION ALL SELECT 'Socks'
UNION ALL SELECT 'Shoes'
UNION ALL SELECT 'Hat'
DECLARE #myTableVar table
( StatementFk int,
ItemName nvarchar(255) )
INSERT INTO #Items
( ItemName )
OUTPUT INSERTED.StatementFk, INSERTED.ItemName
INTO #myTableVar
SELECT ItemName
FROM #OriginalData
INSERT INTO #Statements
( ID, ProposalDateTime )
SELECT
StatementFK, getdate()
FROM #myTableVar
You will need to write an ETL process to do this. You may want to look into SSIS.
This also can be done with t-sql and possibly temp tables. You may need to store unique key from OriginalTable in Statements table and then when you are inserting Items - join OriginalTable with Statements on that unique key to get the ID.
I don't think you could do it in one chunk but you could certainly do it with a cursor loop
DECLARE #bla char(10)
DECLARE #ID int
DECLARE c1 CURSOR
FOR
SELECT bla
FROM OriginalData
OPEN c1
FETCH NEXT FROM c1
INTO #bla
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO Statements(ProposalDateTime) VALUES('SomeDate')
SET #ID = SCOPE_IDENTITY()
INSERT INTO Items(StateMentFK,ItemNAme) VALUES(#ID,#bla)
FETCH NEXT FROM c1
INTO #bla
END
CLOSE c1
DEALLOCATE c1