Computed Columns in INSERT Statement - sql

Created a table:
CREATE TABLE [dbo].[tblPacks]
(
[ID] [int] NOT NULL,
[BatchNumber] [varchar](30) NULL,
[PackID] AS (CONVERT([varchar](50), 'PK' + case when len([ID]) <= (3) then CONVERT([varchar](20), right((0.001) * [ID], (3)), 0) else CONVERT([varchar](20), [ID], 0) end, 0)) PERSISTED,
[Status] [int] NULL
)
Now I Had a New Requirement that Other than this sequence also can be added into this table So that I Will Remove Computed Logic and Create Table As:
CREATE TABLE [dbo].[tblPacks]
(
[ID] [int] NOT NULL,
[BatchNumber] [varchar](30) NULL,
[PackID] VARCHAR(50),
[Status] [int] NULL
)
Now I Want Two Insert Statements which 'PackID' Should have logic Which is Similar to this:
AS (CONVERT([varchar](50),'PK'+case when len([ID])<=(3) then
CONVERT([varchar](20),right((0.001)*[ID],(3)),0) else
CONVERT([varchar](20),[ID],0) end,0))
And A Normal Insert statement which has no logic How can I DO that?

Since your ID column is not an identity column, you can simply use the statement in your computed column with your ID value (for better readability I assume you have a parameter #ID for your ID value):
INSERT INTO tblPacks (ID, PackID)
VALUES (
#ID,
CONVERT([varchar](50),'PK'+case when len(#ID)<=(3) then CONVERT([varchar](20),right((0.001)*#ID,(3)),0) else CONVERT([varchar](20),#ID,0) end,0)
)
If your ID column actually is an identity column, you need SCOPE_IDENTITY() to get your inserted ID value. However, since SCOPE_IDENTITY() only gives you the ID after the value has been inserted, you need two steps. It is a good idea to use a transaction for these steps to make your operation atomic:
BEGIN TRAN
INSERT INTO tblPacks (ID) VALUES (#ID)
UPDATE tblPacks
SET PackID = CONVERT([varchar](50),'PK'+case when len(SCOPE_IDENTITY())<=(3) then CONVERT([varchar](20),right((0.001)*SCOPE_IDENTITY(),(3)),0) else CONVERT([varchar](20),SCOPE_IDENTITY(),0) end,0)
WHERE ID = SCOPE_IDENTITY()
COMMIT
You can also use a trigger on your table instead of a computed column that sets the value if you have not set it before:
CREATE TRIGGER TR_tblPacks ON tblPacks
AFTER INSERT
AS
UPDATE tblPacks
SET PackID = CONVERT([varchar](50),'PK'+case when len(inserted.ID)<=(3) then CONVERT([varchar](20),right((0.001)*inserted.ID,(3)),0) else CONVERT([varchar](20),inserted.ID,0) end,0)
FROM inserted
WHERE tblPacks.ID = inserted.ID AND tblPacks.PackID IS NULL
With this trigger you only add your custom value to the INSERT statement if you have it. If you don't have it, the trigger will set the default value.

You could set user defined function as computed column.like this..
ALTER TABLE TableName ADD YourColumn AS dbo.TestFunction(otherColumn);
then, you can include all your logic for this computed column in that user defined function.

Related

Stored procedure to Insert data between tables

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

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!

'An explicit value for the identity column can only be specified...' during Merge

There are quite a few questions which cover this error, but I'm having the issue that:
A) I'm currently doing a Merge (with an Insert)
B) I'm not explicitly setting the identity column!
My stored procedure (table names and properties obfuscated):
Table Definition
CREATE TABLE MyTable (
[ID] BIGINT IDENTITY(1, 1) NOT NULL,
[Value] NVARCHAR (256) NOT NULL,
[Property] NVARCHAR (256) NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [IX_MyTable] UNIQUE NONCLUSTERED ([Value] ASC)
);
Stored Procedure Definition
CREATE TYPE TempType as Table (
[TempValue] nvarchar(256) NOT NULL,
[TempProperty] nvarchar(256) NOT NULL,
);
GO
CREATE PROCEDURE [Name]
#Source TempType READONLY
AS
BEGIN
MERGE [MyTable] as target
USING (SELECT * FROM #Source) as source (TempValue, TempProperty)
ON (target.Value = source.TempValue)
WHEN MATCH THEN
UPDATE SET Value = source.TempValue, Property = source.TempProperty
WHEN NOT MATCHED THEN
INSERT ([Value], [Property])
VALUES (source.TempValue, source.TempProperty)
OUTPUT deleted.*, $action, inserted.* INTO [MyTable];
END
From what I can see, I'm not anywhere explicitly specifying the IDENTITY column. I am specifying a UNIQUE-ly constrained column though.
Lastly, contrary to what the error says, specifying IDENTITY_INSERT ON does nothing.
Edit: I should also specify, I'm getting this error while deploying from a .dacpac via C#.
I would simply get rid of the MERGE statement (has lots of issues by design) and use a simple insert statement as follows:
CREATE PROCEDURE [Name]
#Source TempType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [MyTable] ([Value], [Property])
SELECT s.TempValue, s.TempProperty
FROM #Source s
WHERE EXISTS (SELECT 1
FROM [MyTable]
WHERE Value = s.TempValue)
END
To read about issues with MERGE statement have a look at this article Use Caution with SQL Server's MERGE Statement
Since you have added an update into your Merge statement, now I would also add an update statement and wrap the update and insert into one transaction but still I would not recommend using merge statement.
CREATE PROCEDURE [Name]
#Source TempType READONLY
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
UPDATE T
SET T.[Property] = S.[Property]
FROM [MyTable] T
INNER JOIN #Source s ON T.Value = s.TempValue
INSERT INTO [MyTable] ([Value], [Property])
SELECT s.TempValue, s.TempProperty
FROM #Source s
WHERE NOT EXISTS (SELECT 1
FROM [MyTable]
WHERE Value = s.TempValue)
COMMIT TRANSACTION;
END

Check CONSTRAINT Not work in SQL Server

I have to restrict to insert duplicate data in my table with condition
Here is SQL Server Table
CREATE TABLE [dbo].[temptbl](
[id] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
[DSGN] [varchar](500) NULL,
[RecordType] [varchar](1000) NULL
)
I want to put condition on RecordType, if RecordType is 'SA' Than check CONSTRAINT (means if DSGN = 0 and RecordType = 'SA' Exist than i don't want to insert that data.
if DSGN = 1 and RecordType = 'SA' Not Exist than i want to insert that data.
If RecordType is other than 'SA' than insert any data
For that i create constraint but it is not work
ALTER TABLE temptbl WITH CHECK ADD CONSTRAINT chk_Stuff CHECK (([dbo].[chk_Ints]([DSGN],[RecordType])=(0)))
GO
ALTER FUNCTION [dbo].[chk_Ints](#Int_1 int,#Int_2 varchar(20))
RETURNS int
AS
BEGIN
DECLARE #Result INT
BEGIN
IF #Int_2 = 'SA'
BEGIN
IF NOT EXISTS (SELECT * FROM [temptbl] WHERE DSGN = #Int_1 AND RecordType = #Int_2)
BEGIN
SET #Result = 0
END
ELSE
BEGIN
SET #Result = 1
END
END
ELSE
BEGIN
SET #Result = 0
END
END
RETURN #Result
END
But it is not working. please suggest me
Ditch the function and the check constraint:
CREATE UNIQUE INDEX IX_temptbl_SA ON temptbl (DSGN) WHERE RecordType='SA'
This is known as a filtered index.
Your check constraint wasn't working as you thought it would because when a check constraint is evaluated for any particular row, that row is already visible within the table (within the context of that transaction) and so each row was effectively blocking its own insertion.
Use With NOCheck for only new data. Here is the link provided.
https://technet.microsoft.com/en-us/library/ms179491(v=sql.105).aspx

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.