How to insert multiple rows into SQL using an Array? - sql

I want to insert multiple rows into a database if they don't already exist, swapping out one columns value for each insertion. Below is some pseudo code of the goal:
IF NOT EXISTS (SELECT [ChangeReason] FROM BudgetAndAuthorizationChangeReasons WHERE [ChangeReason]= ('Other','Scope Change'))
INSERT INTO [dbo].[BudgetAndAuthorizationChangeReasons]
([ChangeReason]
,[IsActive]
,[CreatedByUser]
,[CreatedOn]
,[LastUpdatedByUser]
,[LastUpdatedOn])
VALUES
( ('Other','Scope Change')
,'true'
,'system'
,GETDATE()
,null
,null)
GO
In other words, if a row with ChangeReason='Other' already exists, nothing will happen. If it does not exist, then it will be inserted with all the other values outlined as above (IsActive=true, etc). This will be repeated for each element in the array : ('Other','Scope Change')
EDIT:
I have written a stored procedure to take care of the dirty work for me. Is it possible to call this automatically for every element in the array? Or do I need X different exec statements?
IF EXISTS ( select * from sys.procedures where name='SafeInsert_BudgetAndAuthorizationChangeReasons') begin
DROP PROC SafeInsert_BudgetAndAuthorizationChangeReasons
end;
GO
create procedure SafeInsert_BudgetAndAuthorizationChangeReasons #Reason varchar(50)
as
begin
IF NOT EXISTS (SELECT [ChangeReason] FROM BudgetAndAuthorizationChangeReasons WHERE [ChangeReason]= #Reason)
INSERT INTO [dbo].[BudgetAndAuthorizationChangeReasons]
([ChangeReason]
,[IsActive]
,[CreatedByUser]
,[CreatedOn]
,[LastUpdatedByUser]
,[LastUpdatedOn])
VALUES
(#Reason
,'true'
,'system'
,GETDATE()
,null
,null);
end;
GO
exec SafeInsert_BudgetAndAuthorizationChangeReasons #Reason='Other';
-- goal: auto-exec for every element in ('Other','Scope Change')

SQL doesn't understand arrays, but you can list multiple insert values like below.
INSERT INTO [dbo].[BudgetAndAuthorizationChangeReasons]
([ChangeReason]
,[IsActive]
,[CreatedByUser]
,[CreatedOn]
,[LastUpdatedByUser]
,[LastUpdatedOn])
VALUES
('Other' , 'true', 'system', GETDATE(), null, null)
,('Scope Change' , 'true', 'system', GETDATE(), null, null)

You can create a pseudo-table using values like so:
select v.Id, v.Name from (values (1, 'Jason'), (2, 'Tony'), (3, 'Michelle')) v(Id, Name)
v is any alias you want to give it and you specify the names for the columns in parentheses. You can combine that with the MERGE statement to only insert those rows if they don't exist.
WITH SOURCE_CTE AS (
select ChangeReason, IsActive, CreatedByUser, CreatedOn, LastUpdatedByUser, LastUpdatedOn
from (values ('Other', 'true', 'system', getdate(), null, null),
('Scope Change', 'true', 'system', getdate(), null, null)
) tbl (ChangeReason, IsActive, CreatedByUser, CreatedOn, LastUpdatedByUser, LastUpdatedOn)
)
MERGE into dbo.BudgetAndAuthorizationChangeReasons as t
using SOURCE_CTE as s
on t.ChangeReason = s.ChangeReason
when not matched by target then
insert (
ChangeReason,
IsActive,
CreatedByUser,
CreatedOn,
LastUpdatedByUser,
LastUpdatedOn
)
values
(
s.ChangeReason,
s.IsActive,
s.CreatedByUser,
s.CreatedOn,
s.LastUpdatedByUser,
s.LastUpdatedOn
)
* Edit after stored procedure change *
I'm not sure where your data is coming from and how you're passing it. First it seemed like it was just in a sql script. I find things like above handy for config tables, putting a script in the database project post-deploy to make sure values exist.
If using stored procedures, why not just call the stored procedure for each value?
exec SafeInsert_BudgetAndAuthorizationChangeReasons #Reason='Other';
exec SafeInsert_BudgetAndAuthorizationChangeReasons #Reason='Scope Change';
You could create a UDTT and load that with values and pass it to your stored proc:
create type StringList as table (
value varchar(256)
);
Create Procedure SafeInsert_BudgetAndAuthorizationChangeReasons #Reasons StringList readonly
...

INSERT INTO [dbo].
[BudgetAndAuthorizationChangeReasons]
([ChangeReason]
,[IsActive]
,[CreatedByUser]
,[CreatedOn]
,[LastUpdatedByUser]
,[LastUpdatedOn])
(select 'Other','Scope Change',
,'true'
,'system'
,GETDATE()
,null
,null) where NOT EXISTS (SELECT
[ChangeReason] FROM
BudgetAndAuthorizationChangeReasons WHERE [ChangeReason] IN ('Other','Scope Change'))
or you can have a trigger before insert
Create Trigger name before insert
On
BudgetAndAuthorizationChangeReasons
For each row
As
IIF(SELECT COUNT(*) FROM (SELECT
[ChangeReason] FROM
BudgetAndAuthorizationChangeReasons
WHERE [ChangeReason] IN
('Other','Scope Change'))) >0)
THEN
INSERT......
END IF
END
or you can use a procedure
checkData(ChangeReason IN
BudgetAndAuthorizationChangeReasons.
ChangeReason%TYPE)
AS
IIF(SELECT COUNT(*) FROM
(SELECT
[ChangeReason] FROM
BudgetAndAuthorizationChangeReasons
WHERE [ChangeReason] IN
('Other','Scope Change'))) >0)
THEN
INSERT CHANGEREASON......
END IF
END

INSERT INTO [dbo].[BudgetAndAuthorizationChangeReasons] ([ChangeReason]
,[IsActive]
,[CreatedByUser]
,[CreatedOn]
,[LastUpdatedByUser]
,[LastUpdatedOn])
SELECT [ChangeReason],'true','system',getDate(),null,null
FROM BudgetAndAuthorizationChangeReasons
WHERE [ChangeReason]in ('Other','Scope Change')
[dbo].[BudgetAndAuthorizationChangeReasons] table should exist.

You struggle with the code because you struggle to define your goal. And that bleeds into those trying to help. Given a set of reasons, you simply want to all those that do not already exist in the table.
-- declare variables for demo
declare #rsn table (ChangeReason varchar(20) not null primary key,
IsActive varchar(5) not null, CreatedOn datetime not null);
-- add some "existing" data to table
insert #rsn (ChangeReason, IsActive, CreatedOn)
values ('Other', 'true', '20190801 13:01:01');
-- demonstrate the use of a table value constructor
with cte as (select * from (values ('Other'), ('Scope'), ('Change')) as x(rsn))
select * from cte;
-- use a transaction for testing just in case you actually use a real table
-- and not a table variable
begin tran;
merge into #rsn as target
using (values ('Other'), ('Scope'), ('Change')) as source(rsn)
on target.ChangeReason = source.rsn
when not matched by target then
insert (ChangeReason, IsActive, CreatedOn) values (rsn, 'true', getdate())
;
-- verify logic worked correctly
select * from #rsn;
rollback tran;
Here is fiddle to demonstrate. Notice that this makes use of table value constructor which is a very handy thing to know.

Related

Merge not working for insert a record when it's doesn't exist

Can I use Merge to insert a record when it's doesn't exist like below,
MERGE INTO [dbo].[Test] AS [Target]
USING (SELECT DISTINCT [Name] FROM [dbo].[Test]) AS [Source]
ON [Target].[Name] = [Source].[Name]
WHEN NOT MATCHED THEN
INSERT ([Id], [Name])
VALUES (NEWID(), 'Hello');
If the record with value Hello does not exists in table Test, insert it otherwise don't do anything. With above code record is not inserted even I don't have this record in table. And there are no errors.
I know how to accomplish this using insert ... where not exists (...) but am specifically wanting to know how to do it using a merge statement.
The reason your merge statement wasn't working is that you were merging the same table, dbo.Test, back onto itself, so of course there is no missing record.
You can insert a single missing record as follows, where you create a source query to contain the record(s) you wish to insert:
declare #Test table (id uniqueidentifier, [Name] nvarchar(64))
select * from #Test
-- Returns
-- id | Name
-- ----------------------------------------------
MERGE INTO #Test AS [Target]
USING (select 'Hello' [Name]) AS [Source]
ON [Target].[Name] = [Source].[Name]
WHEN NOT MATCHED THEN
INSERT ([Id], [Name])
VALUES (NEWID(), [Name]);
select * from #Test
-- Returns
-- id | Name
-- ----------------------------------------------
-- C1C87CD5-F745-436D-BD8D-55B2AF431BED | Hello
I agree with the answer from Dale K. Its correct.
If I suppose you might have a source_table from where the data needs to get inserted and not to get inserted if the record already exists then you can do the following.
Instead of the MERGE you can
insert
into dbo.Test
(id
,name
)
select top 1
newID()
,'Hello'
from dbo.Test a
where not exists(select 1
from dbo.Test b
where b.name='Hello')

SQL Server: Create Trigger For Audit / History Table

I am creating my first SQL Server Trigger, and looking to INSERT into a "History" table after insert into another table. I think I have most of the code written, but can't seem to get the syntax finished. The current format states that the "HistoryColumnName" and "HistoryNewValue" are invalid. I have tried a JOIN to the variable table #HistoryRecord but it doesnt really make sense as they are independent.
Code below:
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
DECLARE
#HardwareAssetID UNIQUEIDENTIFIER,
#HardwareAssetTitle VARCHAR(256),
#HardwareAssetSerialNumber VARCHAR(256)
SET #HardwareAssetID = (SELECT HardwareAssetID FROM inserted)
SET #HardwareAssetTitle = (SELECT HardwareAssetTitle FROM inserted)
SET #HardwareAssetSerialNumber = (SELECT HardwareAssetSerialNumber FROM inserted)
DECLARE #HistoryRecord TABLE (HistoryColumnName VARCHAR(256) NOT NULL, HistoryNewValue VARCHAR(256) NOT NULL)
INSERT #HistoryRecord(HistoryColumnName,HistoryNewValue) VALUES('Asset Name', #HardwareAssetTitle)
INSERT #HistoryRecord(HistoryColumnName,HistoryNewValue) VALUES('Serial Number', #HardwareAssetSerialNumber)
BEGIN
WHILE EXISTS(SELECT HistoryColumnName,HistoryNewValue FROM #HistoryRecord)
INSERT INTO HardwareAssetHistory
(HardwareAssetHistoryChangeTypeID, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue, HardwareAssetHistoryHardwareAssetID)
SELECT '1', HardwareAssetCreatedByID, HistoryColumnName, '', HistoryNewValue, HardwareAssetID
FROM HardwareAsset
WHERE HardwareAssetID = #HardwareAssetID
END
GO
Any suggestions or help would be appreciated.
You can accomplish this without your temp table and referring back original HardwareAsset table by picking another value:
NOTE: as pointed by #nick.mcdermaid, below will not work when there are multiple rows as we are using variables.
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
BEGIN
DECLARE #HardwareAssetID UNIQUEIDENTIFIER,
#HardwareAssetTitle VARCHAR(256),
#HardwareAssetSerialNumber VARCHAR(256),
#HardwareAssetCreatedByID INT --CHANGE TO WHAT IS DATA TYPE OF THIS
SELECT #HardwareAssetID = HardwareAssetID, #HardwareAssetTitle = HardwareAssetTitle
, #HardwareAssetSerialNumber = HardwareAssetSerialNumber
, #HardwareAssetCreatedByID = HardwareAssetCreatedByID FROM inserted
INSERT INTO HardwareAssetHistory(HardwareAssetHistoryChangeTypeID
, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName
, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue
, HardwareAssetHistoryHardwareAssetID)
VALUES ('1', #HardwareAssetCreatedByID, 'Asset Name', '', #HardwareAssetTitle
, #HardwareAssetID),
('1', #HardwareAssetCreatedByID, 'Serial Number', '', #HardwareAssetSerialNumber
, #HardwareAssetID)
END
GO
UPDATE: This will work with multiple rows as well:
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
BEGIN
DECLARE #insertedTemp AS TABLE (HardwareAssetID UNIQUEIDENTIFIER, HardwareAssetTitle VARCHAR(256), HardwareAssetSerialNumber VARCHAR(256), HardwareAssetCreatedByID INT)
INSERT INTO #insertedTemp(HardwareAssetID, HardwareAssetTitle, HardwareAssetSerialNumber, HardwareAssetCreatedByID)
SELECT HardwareAssetID, HardwareAssetTitle, HardwareAssetSerialNumber, HardwareAssetCreatedByID FROM inserted
INSERT INTO HardwareAssetHistory(HardwareAssetHistoryChangeTypeID
, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName
, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue
, HardwareAssetHistoryHardwareAssetID)
SELECT '1', HardwareAssetCreatedByID, 'Asset Name', '', HardwareAssetTitle, #HardwareAssetID
FROM #insertedTemp
UNION
SELECT '1', HardwareAssetCreatedByID, 'Serial Number', '', HardwareAssetSerialNumber, HardwareAssetID
FROM #insertedTemp
END
GO

Selecting row data from one table, then insert into another table using TableAdapter

I'm using TableAdapter Query Configuration Wizard. I want to select a data from one table and insert into another as shown in the statement below.
SELECT a.StudentID FROM [dbo].[Student] AS a WHERE [Email] = #Email;
INSERT INTO [dbo].[Registration] ([StudentID], [UniformOptionID], [Cost])
VALUES (a.StudentID, #Param1, #Param2);
When I call out the function on my application, The error message prompts:
Error message: The multi-part identifier "a.StudentID" could not be bound
Why is this not possible?
INSERT INTO [dbo].[registration]
([studentid],
[uniformoptionid],
[cost])
SELECT a.studentid,
#Param1,
#Param2
FROM [dbo].[student] AS a
WHERE [email] = #Email;
you cannot use a.StudentID in your Insert statement. you can declare a variable then use it. Like this
Declare #studentID int
SELECT #studentID=a.StudentID FROM [dbo].[Student] AS a WHERE [Email] = #Email;
INSERT INTO [dbo].[Registration] ([StudentID], [UniformOptionID], [Cost])
VALUES (#studentID, #Param1, #Param2);
Try this:
INSERT INTO [dbo].[Registration] ([StudentID], [UniformOptionID], [Cost])
SELECT a.StudentID, #Param1, #Param2 FROM [dbo].[Student] AS a WHERE [Email] = #Email;
i.e, you can use the select statement to insert the values in your table. The #Param1, #Param2 can be provided directly in the select statement.

How to get The QueryString Without EXEC and Print Function

I have other table #table3 where the #qur will be stored and using that #qur i want to retrieve the data.
so it is possible to get data without set all query in other variable and execute this query directly.
this string of #qur not fixed it will different for different person.
and yes i use sql server 2010
CREATE TABLE #Table1
([Name] varchar(5), [DateVal] date, [TimeVal] time, [Item] varchar(5))
;
INSERT INTO #Table1
([Name], [DateVal], [TimeVal], [Item])
VALUES
('Lisa', '2015-04-21', '10:20:06', 'Item1'),
('John', '2015-04-21', '10:25:30', 'Item2'),
('Peter', '2015-03-18', '13:35:32', 'Item3'),
('Ralf', '2015-04-03', '09:26:52', 'Item4')
;
CREATE TABLE #Table2
([ID] int, [Name] varchar(5))
;
INSERT INTO #Table2
([ID],[Name])
VALUES
(1,'Lisa' ),
(2,'John' ),
(3,'Peter'),
(4,'Ralf')
;
DECLARE #qur VARCHAR(2000)='([Item] in (''Item1,Item2'')) and [Name]=''Lisa'') '
SELECT DateVal FROM #Table1
WHERE [Name] in (SELECT [Name] FROM #Table2)
AND #qur
Maybe You can use the query bellow in SQLCMD mode:
:setvar qur "and ([Item] in ('Item1','Item2')) and [Name]='Lisa' "
SELECT DateVal FROM #Table1
WHERE [Name] in (SELECT [Name] FROM #Table2)
$(qur)

Formulation of insert into keyword in SQL Server

I want to insert some values to the table, there is an order such as 1,2,3....n
Insert Into table_name VALUES ( '1', 'A' )
Insert Into table_name VALUES ( '2', 'AA' )
Insert Into table_name VALUES ( '3', 'AAC' )
Insert Into table_name VALUES ( '.', '....' )
Insert Into table_name VALUES ( '.', '....' )
Insert Into table_name VALUES ( 'n', '....' )
How can I formulate this INSERT statement?
If you want to insert a series of rows - sure, you can use a loop - but how do you know what other values (other than the index) to get??
DECLARE #index INT
SET #index = 0
WHILE #index < 10
BEGIN
INSERT INTO dbo.table_name(Index)
VALUES( CAST(#index AS VARCHAR(50)) ) -- or whatever type you need....
SET #index = #index + 1
END
The usual way to do this is to select the values to insert from somewhere else:
INSERT INTO company1.new_customers (id, name, address)
SELECT
NULL -- this will trigger the DB to auto-generate the new id's
,name
,address
FROM company2.old_customers
If you have to use a loop in SQL you're doing it wrong.
SQL works with sets.