Stored procedure to Insert data between tables - sql

I want to insert data from a table called temp_menu into another called menu.
They have the same structure, they store the same data, I want to create a stored procedure to check the differences between the tables. If there are any different rows and the rows don't exist in table menu, I want to insert them into menu; if the rows exists, I want to update the rows in menu if the DateReg column is higher that the DateReg column in the temp_menu table.
The tables have this structure:
CREATE TABLE [dbo].[Menu_Temp]
(
[Date] [datetime] NOT NULL,
[Ref] [int] NOT NULL,
[Art] [char](60) NOT NULL,
[Dish] [char](60) NOT NULL,
[DateReg] [datetime] NOT NULL,
[Zone] [char](60) NOT NULL,
);
I have this code to check for differences between the tables:
SELECT *
INTO #diffs
FROM [Regi].dbo.menu
EXCEPT
SELECT * FROM [Regi].dbo.menu_Temp
IF ##ROWCOUNT = 0
RETURN
SELECT * FROM #diffs

Full details are here : https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql
An example for your situation could be...
MERGE
[Regi].dbo.menu
USING
[Regi].dbo.menu_Temp
ON (menu_Temp.[Ref] = menu.[Ref]) -- Assumes [Ref] is the identifying column?
WHEN
MATCHED AND (menu_Temp.[DateReg] > menu.[DateReg])
THEN
UPDATE SET [Art] = menu_Temp.[Art],
[Dish] = menu_Temp.[Dish],
[Zone] = menu_Temp.[Zone],
[Date] = menu_Temp.[Date],
[DateReg] = menu_Temp.[DateReg]
WHEN
NOT MATCHED
THEN
INSERT (
[Date],
[Ref],
[Art],
[Dish],
[DateReg],
[Zone]
)
VALUES (
menu_Temp.[Date],
menu_Temp.[Ref],
menu_Temp.[Art],
menu_Temp.[Dish],
menu_Temp.[DateReg],
menu_Temp.[Zone]
)
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b47d7e879856ffe6210589f6bb64829f

Related

How to dynamically create columns in SQL based on a list of ids

I am developing a stored procedure in SQL that receives a list of Ids (INT) from the backend. I need to dynamically create columns based on these and give the columns for each ID a name. Below is an example of how to do this with a fixed number of known Ids
SELECT DISTINCT
SE.SE_optionCode AS OptionCode,
LTRIM(RTRIM(DO.DO_Description)) AS Description,
LTRIM(RTRIM(CASE WHEN DG.DG_Description IS NULL THEN '' ELSE DG.DG_Description END)) AS GenericDescription,
LTRIM(RTRIM(CASE WHEN DG.DG_GenericCode IS NULL THEN 0 ELSE DG.DG_GenericCode END)) as GenericCode,
DCS.DC_CatCode AS Category,
LTRIM(RTRIM(DCS.DC_Description)) AS Category,
DCG.DC_CatCode AS GenericCategory,
LTRIM(RTRIM(DCG.DC_Description)) AS GenericCategory,
(SELECT DISTINCT 1 FROM NVDStandardEquipment WHERE SE_id = 93762 AND SE_optionCode = SE.SE_optionCode AND SE.SE_effectiveTo IS NULL) as Vehicle1,
(SELECT DISTINCT 1 FROM NVDStandardEquipment WHERE SE_id = 93786 AND SE_optionCode = SE.SE_optionCode AND SE.SE_effectiveTo IS NULL) as Vehicle2,
(SELECT DISTINCT 1 FROM NVDStandardEquipment WHERE SE_id = 93787 AND SE_optionCode = SE.SE_optionCode AND SE.SE_effectiveTo IS NULL) as Vehicle3,
(SELECT DISTINCT 1 FROM NVDStandardEquipment WHERE SE_id = 93801 AND SE_optionCode = SE.SE_optionCode AND SE.SE_effectiveTo IS NULL) as Vehicle4
FROM NVDStandardEquipment SE (NOLOCK)
INNER JOIN NVDDictionaryOption DO (NOLOCK) ON DO.DO_OptionCode = SE.SE_optionCode
INNER JOIN NVDDictionaryOptionGenericCatLink DOGCL (NOLOCK) ON DOGCL.OGCL_OptionCatCode = DO.DO_CatCode
LEFT JOIN NVDDictionaryOptionGenericLink DOGL ON DOGL.ogl_optioncode = SE.SE_optionCode
LEFT JOIN NVDDictionaryGeneric DG ON DG.DG_GenericCode = DOGL.OGL_genericCode
INNER JOIN NVDDictionaryCategory DCS (NOLOCK) ON DCS.DC_CatCode = DO.DO_CatCode
INNER JOIN NVDDictionaryCategory DCG (NOLOCK) ON DCG.DC_CatCode = DOGCL.OGCL_GenericCatCode
WHERE SE.SE_id in (93762,93786,93787,93801)
This will return a result set as in the attached image.
In my stored procedure I am declaring a temporary table where I store all the Ids received from the backend, somehow I would have to loop through these (Not sure if thats the best approach), I don't know whether is possible to generate the same output based on a ID list to generate the columns as per the example, has anyone done something similar or have some ideas about how I could achieve this?
Thanks in advance!
Let's say you need to update/maintain the following table.
CREATE TABLE [dbo].[Test1](
[MemberID] [varchar](50) NULL,
[InpatientDays] [varchar](50) NULL,
[ERVisits] [varchar](50) NULL,
[OfficeVisits] [varchar](50) NULL,
[Narcotics] [varchar](50) NULL,
[DaysSinceLastERVisit] [varchar](50) NULL,
[Pain] [varchar](50) NULL,
[TotalVisits] [varchar](50) NULL
) ON [PRIMARY]
GO
You have a file with a list of the following IDs.
AcuteDrugGapSmall
ClaimLines
MedicalClaims
PoorCare
ProviderCount
StartedOnCombination
Create a table to save the IDs.
CREATE TABLE [dbo].[Test2](
[ID] [varchar](50) NULL
) ON [PRIMARY]
GO
After you insert the IDs run the following to generate the TSQL script to add the new columns. I'm assuming they're all VARCHAR(50) in this example. You can tweak to code given more information regarding data_type and character_maximum_length. You can take a similar approach to drop columns if needed. If you had a flag in table Test2 indicating whether to add or drop the column.
DECLARE #SQL VARCHAR(MAX)
SELECT #SQL = STRING_AGG('ALTER TABLE [dbo].[Test1] ADD ' + ID + ' VARCHAR(50)', ' ')
FROM [dbo].[Test3]
EXECUTE (#SQL)
The above script generates and executes the following. Please mark as solution if this works for you.
ALTER TABLE [dbo].[Test1] ADD AcuteDrugGapSmallVARCHAR(50)
ALTER TABLE [dbo].[Test1] ADD ClaimLinesVARCHAR(50)
ALTER TABLE [dbo].[Test1] ADD MedicalClaimsVARCHAR(50)
ALTER TABLE [dbo].[Test1] ADD PoorCareVARCHAR(50)
ALTER TABLE [dbo].[Test1] ADD ProviderCountVARCHAR(50)
ALTER TABLE [dbo].[Test1] ADD StartedOnCombinationVARCHAR(50)

SQL Server: update rows that take part in grouping

I'm using SQL Server 2017 (v14.0).
I have two tables with one-to-many relationship. I need to group the rows in the "Orders" table and by this info create the row in the "Transactions" table, then I need set the relationship - for a created transaction I need set the TransactionId to related order's rows in one query/transaction flow to keep the consistency.
I correctly insert the new row by grouped data, but can't update the related rows in "Orders" table to set the relationship (TransactionId) for related "Transactions".
Can you, please, help with composing the query statement or get the clue to move in the right direction?
CREATE TABLE [dbo].[Orders]
(
[OrderId] INT NOT NULL,
[TransactionId] INT NULL,
[OrderVolume] DECIMAL(18, 8) NOT NULL,
[OrderCurrencyId] INT NOT NULL,
)
CREATE TABLE [dbo].[Transactions]
(
[TransactionId] INT NULL,
[Volume] DECIMAL(18, 8) NOT NULL,
)
INSERT INTO Transactions (Volume)
OUTPUT INSERTED.[TransactionId] --also need to update the rows in "Orders" that take a part in grouping to set the relationship
SELECT
SUM(OrderVolume) AS OrderVolume,
FROM Orders
GROUP BY Orders.OrderCurrencyId
The problem with the OUTPUT clause in an INSERT statement is that it doesn't allow you to select any field from the source table.
You can achieve this using MERGE statement instead:
DECLARE #t TABLE([TransactionId] INT, [OrderCurrencyId] INT)
MERGE Transactions trgt
USING
(
SELECT
SUM(OrderVolume) AS OrderVolume
, Orders.OrderCurrencyId AS OrderCurrencyId
FROM
Orders
GROUP BY
Orders.OrderCurrencyId
) AS src ON (1=0)
WHEN NOT MATCHED THEN
INSERT ( [Volume] ) VALUES (src.OrderVolume)
OUTPUT [inserted].[TransactionId], src.[OrderCurrencyId]
INTO #t;
UPDATE Orders
SET TransactionId = t.TransactionId
FROM Orders
JOIN #t t ON Orders.OrderCurrencyId = t.OrderCurrencyId
Demo here

How to use CHECKSUM in a trigger

I'm trying to create my own logic for tables synchronization in SQL Server Express 2019. I was hoping that such simple task would work:
Have a Customers table
Have a Synchronization table
CREATE TABLE [dbo].[Synchronization]
(
[PK] [uniqueidentifier] NOT NULL,
[TableName] [nchar](50) NOT NULL,
[RecordPK] [uniqueidentifier] NOT NULL,
[RecordChecksum] [int] NOT NULL,
[RecordDate] [datetime] NOT NULL,
[RecordIsDeleted] [bit] NOT NULL
)
Have a trigger on Customers:
CREATE TRIGGER trgCustomers_INSERT
ON Customers
AFTER INSERT
AS
INSERT INTO Synchronization(PK, TableName, RecordPK, RecordChecksum,
RecordDate, RecordIsDeleted)
VALUES (NEWID(), 'Customers',
(SELECT PK FROM inserted),
(SELECT CHECKSUM(*) FROM inserted),
GETDATE(), 0)
... but I got an error about the SELECT CHECKSUM(*) FROM inserted part:
Cannot use CHECKSUM(*) in a computed column, constraint, default definition, or INSERT statement.
Is there any other way to add new Customer's CHECKSUM or some hash to the Synchronization table?
Don't use the VALUES syntax when inserting and you won't get an error using CHECKSUM while inserting.
Example:
declare #t table (val int)
-- works
insert into #t select checksum(*) from ( select ID from (select 1 as ID union select 2) b ) a
-- reproduce error
insert into #t
values
((select top 1 checksum(*) C from ( select ID from (select 1 as ID union select 2) b ) a))
Implementing the concept in your trigger:
CREATE TRIGGER trgCustomers_INSERT
ON Customers
AFTER INSERT
AS
begin
INSERT INTO Synchronization(PK, TableName, RecordPK, RecordChecksum,
RecordDate, RecordIsDeleted)
select NEWID() as PK,
'Customers' as TableName,
PK as RecordPK,
checksum(*) as RecordChecksum,
GETDATE() as RecordDate,
0 as RecordIsDeleted
from inserted
end

Multi Parent-Child Insertion

I'm trying to write a stored procedure to pull information from an XML string and use it to create multiple parent-child relationships. I am trying to push this XML into actual database tables. Basically, the local client will send an XML file to the database and store it as a string. I then need to pull the information out of that string and update the appropriate tables. If this was just a Table-A to Table-B, this wouldn't be so difficult. The problem I'm running into is it need to go from Table-A to Table-B to Table-C to Table-D where applicable. Below is a sample XML:
<RunRecordFile>
<Competition>
<Name>Daily</Name>
<StartDate>11/9/2015 12:40:07 AM</StartDate>
<Runs>
<Id>123</Id>
<Name>Daily Run</Name>
<RunDate>11/9/2015 12:40:07 AM</RunDate>
<CompetitionId>1</CompetitionId>
<RunRecords>
<Id>001</Id>
<Number>007</Number>
<ElapsedTime>23.007</ElapsedTime>
<RunId>123</RunId>
</RunRecords>
</Runs>
<Runs>
<Id>456</Id>
<Name>Daily Run</Name>
<RunDate>11/9/2015 12:47:07 AM</RunDate>
<CompetitionId>1</CompetitionId>
<RunRecords>
<Id>002</Id>
<Number>700</Number>
<ElapsedTime>23.707</ElapsedTime>
<RunId>456</RunId>
<RunRecordSpecialty>
<Id>1</Id>
<Handicap>17</Handicap>
<TeamPoints>50000</TeamPoints>
<RunRecordId>002</RunRecordId>
</RunRecordSpecialty>
</RunRecords>
</Runs>
</Competition>
</RunRecordFile>
I've attempted to use a DECLARED table to hold each of the created Primary Keys and to use SQL OUTPUT in order to gather those. When I run my SQL I'm getting (0) Rows Updated. Here's what I've tried in SQL:
CREATE PROC [dbo].[RaceFilePush]
AS
DECLARE #CompetitionIdMapping TABLE ( CompetitionId bigint )
DECLARE #RunIdMapping TABLE ( RunId bigint )
DECLARE #RunRecordIdMapping TABLE ( RunRecordId bigint )
BEGIN
DECLARE #rrXML AS XML
DECLARE #rrfId AS BIGINT
SET #rrfId = (SELECT TOP 1 Id FROM RunRecordFile WHERE Submitted IS NULL)
SET #rrXML = (SELECT TOP 1 RaceFile FROM RunRecordFile WHERE Id = #rrfId)
BEGIN TRAN Competitions
BEGIN TRY
INSERT INTO Competition (
Name
,StartDate
)
OUTPUT INSERTED.Id INTO #CompetitionIdMapping(CompetitionId)
SELECT
xCompetition.value('(Name)[1]', 'varchar(225)') AS Name
,xCompetition.value('(StartDate)[1]', 'datetime') AS StartDate
,#rrfId AS RunRecordFileId
FROM
#rrXML.nodes('/RunRecordFile/Competition') AS E(xCompetition)
INSERT INTO Run (
Name
,RunDate
,CompetitionId
)
OUTPUT INSERTED.Id INTO #RunIdMapping(RunId)
SELECT
xRuns.value('(Name)[1]','varchar(80)') AS Name
,xRuns.value('(RunDate)[1]','datetime') AS RunDate
,(SELECT CompetitionId FROM #CompetitionIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs') AS E(xRuns)
INSERT INTO RunRecord (
Number
,ElapsedTime
,RunId
)
OUTPUT INSERTED.Id INTO #RunRecordIdMapping(RunRecordId)
SELECT
xRunRecords.value('(Number)[1]','varchar(10)') AS Number
,xRunRecords.value('(ElapsedTime)[1]','numeric(10,5)') AS ElapsedTime
,(SELECT RunId FROM #RunIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs/RunRecords') AS E(xRunRecords)
INSERT INTO RunRecordSpecialty (
Handicap
,TeamPoints
,RunRecordId
)
SELECT
xRunRecordSpecialty.value('(Handicap)[1]','numeric(10,5)') AS Handicap
,xRunRecordSpecialty.value('(TeamPoints)[1]','numeric(10,5)') AS TeamPoints
,(SELECT RunRecordId FROM #RunRecordIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs/RunRecordSpecialty') AS E(xRunRecordSpecialty)
UPDATE RunRecordFile SET Submitted = GETDATE() WHERE Id = #rrfId
COMMIT TRAN Competitions
END TRY
BEGIN CATCH
ROLLBACK TRAN Competitions
END CATCH
END
With this SQL you get the whole thing into a flat declared table #tbl:
Remark: I placed the XML from your question into a variable called #xml. Adapt this to your needs...
DECLARE #tbl TABLE (
[Competition_Name] [varchar](max) NULL,
[Competition_StartDate] [datetime] NULL,
[Run_Id] [int] NULL,
[Run_Name] [varchar](max) NULL,
[Run_RunDate] [datetime] NULL,
[Run_CompetitionId] [int] NULL,
[RunRecords_Id] [int] NULL,
[RunRecords_Number] [int] NULL,
[RunRecords_ElapsedTime] [float] NULL,
[RunRecords_RunId] [int] NULL,
[RunRecordSpecialty_Id] [int] NULL,
[RunRecordSpecialty_Handicap] [int] NULL,
[RunRecordSpecialty_TeamPoints] [int] NULL,
[RunRecordSpecialty_RunRecordId] [int] NULL
);
INSERT INTO #tbl
SELECT Competition.value('Name[1]','varchar(max)') AS Competition_Name
,Competition.value('StartDate[1]','datetime') AS Competition_StartDate
,Run.value('Id[1]','int') AS Run_Id
,Run.value('Name[1]','varchar(max)') AS Run_Name
,Run.value('RunDate[1]','datetime') AS Run_RunDate
,Run.value('CompetitionId[1]','int') AS Run_CompetitionId
,RunRecords.value('Id[1]','int') AS RunRecords_Id
,RunRecords.value('Number[1]','int') AS RunRecords_Number
,RunRecords.value('ElapsedTime[1]','float') AS RunRecords_ElapsedTime
,RunRecords.value('RunId[1]','int') AS RunRecords_RunId
,RunRecordSpecialty.value('Id[1]','int') AS RunRecordSpecialty_Id
,RunRecordSpecialty.value('Handicap[1]','int') AS RunRecordSpecialty_Handicap
,RunRecordSpecialty.value('TeamPoints[1]','int') AS RunRecordSpecialty_TeamPoints
,RunRecordSpecialty.value('RunRecordId[1]','int') AS RunRecordSpecialty_RunRecordId
FROM #xml.nodes('/RunRecordFile/Competition') AS A(Competition)
OUTER APPLY Competition.nodes('Runs') AS B(Run)
OUTER APPLY Run.nodes('RunRecords') AS C(RunRecords)
OUTER APPLY RunRecords.nodes('RunRecordSpecialty') AS D(RunRecordSpecialty)
;
SELECT * FROM #tbl
If you need generated IDs, you just add the columns to #tbl and write them there, either "on the flow" or afterwards wiht UPDATE statement.
It should be easy to work through this flat table, select just the needed data level with DISTINCT and insert the rows, then the next level and so on...
Good luck!

INSERT or UPDATE table from another table with composite primary key

I am looking for the correct syntax and way to do the following directly from SQL: insert or update (if data already exists inside) TableMain from data contained in TableA with both having same composite primary key.
Both tables are defined as :
CREATE TABLE TableA (
[TID0] [int] NOT NULL,
[TID1] [int] NOT NULL,
[language] [nvarchar](2) NOT NULL,
[TID2] [nvarchar](200) NOT NULL,
[text] [nvarchar](max) NULL,
[updatedOn] [datetime] NOT NULL DEFAULT (getdate())
PRIMARY KEY (
[TID0],
[TID1],
[language],
[TID2],
)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
TableA will be periodically deleted and filled.
TableMain as the same definition but will contain many more rows of data and what I need is to insert never seen values from TableA into TableMain, and update already existing rows.
I used to do this kind of insert but I do not know how to handle update and composite primary keys :
INSERT INTO TableMain
SELECT * FROM TableA
EDIT : i am using SQL Server 9.00.5000
EDIT : another way inspired by MERGE and mimick it
DECLARE #updatedIDs TABLE(
[TID0] [int],
[TID1] [int],
[language] [nvarchar](2),
[TID2] [nvarchar](200),
PRIMARY KEY ([TID0], [TID1], [language], [TID2]) -- as stated by Nikola Markovinović above, thanks
);
-- First update records
update TableMain
set [text] = source.[text],
[updatedOn] = source.[updatedOn]
OUTPUT
inserted.[TID0]
inserted.[TID1]
inserted.[language]
inserted.[TID2]
INTO #updatedIDs
from
TableMain AS main
, TableA AS source
WHERE
TableMain.[TID0] = source.[TID0]
and TableMain.[TID1] = source.[TID1]
and TableMain.[language] = source.[language]
and TableMain.[TID2] = source.[TID2]
-- And then insert
insert into TableMain
select *
from TableA AS source
where not exists
(
select 1
from #updatedIDs AS i
where i.[TID0] = source.[TID0]
and i.[TID1] = source.[TID1]
and i.[language] = source.[language]
and i.[TID2] = source.[TID2]
)
you should use a merge statment
something like this:
merge TableMain AS target
using TableA as source
ON <join tables here>
WHEN MATCHED THEN <update>
WHEN NOT MATCHED BY TARGET <Insert>
WHEN NOT MATCHED BY SOURCE <delete>
Here is a script you might use to upsert your data:
-- On error transaction is automatically rolled back
set xact_abort on
begin transaction
-- First update records
update TableMain
set [text] = source.[text],
[updatedOn] = source.[updatedOn]
from TableMain
inner join TableA source
on TableMain.[TID0] = source.[TID0]
and TableMain.[TID1] = source.[TID1]
and TableMain.[language] = source.[language]
and TableMain.[TID2] = source.[TID2]
-- And then insert
insert into TableMain ([TID0], [TID1], [language], [TID2], [text], [updatedOn])
select [TID0], [TID1], [language], [TID2], [text], [updatedOn]
from TableA source
where not exists
(
select *
from TableMain
where TableMain.[TID0] = source.[TID0]
and TableMain.[TID1] = source.[TID1]
and TableMain.[language] = source.[language]
and TableMain.[TID2] = source.[TID2]
)
commit transaction
You might rewrite not exists() as left join ... where TableMain.TID0 is null if performance is not satisfactory.
you can use the Merge command from SQLServer 2008. It allows you to merge data from another data source into your main data source and define specific behavior when there is a key match (and you'll probably like to update your table) or there is not match and you'll like to insert the new record.
http://blog.sqlauthority.com/2010/06/08/sql-server-merge-operations-insert-update-delete-in-single-execution/
you can visit this link to get few code samples.