Inserting multiple rows with SQL where a record does not exist - sql

I want to insert multiple rows of data into a MySQL database, but only when my order_id field is unique. This is the current query I have, which doesn't work. Lets say a record with an order_id of 2 is already in the table:
INSERT INTO conversion
(user_id,url_id,order_id,sale,commission,transaction_date,process_date)
VALUES (1,1,1,'32',0.3995,'2010-11-15 12:15:18','2010-11-15 12:15:18'),
(3,6,2,'*not-available*',0.001975,'2010-11-15 12:15:18','2010-11-15 12:15:18')
WHERE (order_id <> 3);
Any help is appreciated.
Tom

Solved by using REPLACE.
Example:
REPLACE INTO conversion (user_id,url_id,order_id,sale,commission,transaction_date,process_date) VALUES (1,1,3,'32',0.3995,'2010-11-15 12:50:31','2010-11-15 12:50:31'),(1,2,2,'*not-available*',0.001975,'2010-11-15 12:50:31','2010-11-15 12:50:31');
url: http://dev.mysql.com/doc/refman/5.0/en/replace.html
Thanks all.

INSERT doesn't support the WHERE clause because if you're inserting it implies that the record doesn't currently exist, so therefore there would be nothing for the WHERE clause to look at.
The way to do it in the example you've given is simply not to call the INSERT statement if the order_id field in your insert doesn't match the criteria you want.
If you're calling INSERT multiple times, you'd have some sort of code (either SQL or an external program) which loops through the rows you're inserting; this would be where you'd filter it.

If I am in a similar situation, I would create a stored procedure to handle the logic of figuring out whether an order_id already exists.
--Run this first
--It will create a stored procedure call InsertConversion
--Begin of stored procedure
CREATE PROCEDURE InsertConversion
#user_id int,
#url_id int,
#order_id int,
#sale varchar(5),
#commission money,
#transaction_date datetime,
#process_date datetime
AS
BEGIN
SET NOCOUNT ON;
if not exists(select order_id from conversion where order_id = #order_id)
begin
INSERT INTO conversion(user_id, url_id, order_id, sale, commission, transaction_date, process_date)
VALUES(#user_id, #url_id, #order_id, #sale, #commission, #transaction_date, #process_date)
end
END
GO
--End of stored procedure
Once the store procedure created, you can execute it and pass in the same values as you would pass into an INSERT/VALUES statement:
exec InsertConversion 1,1,1,'32',0.3995,'2010-11-15 12:15:18','2010-11-15 12:15:18'
exec InsertConversion 3,6,2,'*not-available*',0.001975,'2010-11-15 12:15:18','2010-11-15 12:15:18'
If you want to be fancy, you can include a couple of 'print' statement in the store procedure to tell you whether it inserts the record.

Related

Insert Data into a Table if a condition is met with SQL Server Stored Procedure

I need to learn how to build a stored procedure in SQL Server 2014 that will insert data into a table - but only if a condition is met.
I will use a basic example:
I have 2 tables, customers and orders with a simple one-to-many relationship on the CustomerID.
Customers table contains:
CustomerID, CustomerName, TermStartDate, TermEndDate
Orders table contains:
contains OrderID, CustomerID, ProductID, OrderDate
I would like my stored procedure to insert a new record into the orders table with the parameters CustomerID, ProductID, OrderDate
ASK:
However, I would only like to insert the record if the OrderDate is between the customer's TermStartDate and TermEndDate
I'm obviously a SQL stored procedure newbie and I don't understand my options in terms of what could happen if the condition is not met, e.g. the record is not inserted and an error message is returned(?). I would also like the stored procedure to consider whether another user is also making changes to the underlying data when the stored procedure is executing (if that needs considering)
Going with the return value of the inserted identity value or -1 when not inserted, you can create a stored procedure with the following definition:
CREATE PROCEDURE dbo.Orders_Insert
#CustomerId INT,
#ProductId INT,
#OrderDate DATETIME2
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN;
IF EXISTS ( SELECT NULL
FROM Customers
WHERE CustomerId = #CustomerId AND
#OrderDate BETWEEN TermStartDate AND TermEndDate )
BEGIN
INSERT INTO Orders
(CustomerID,
ProductID,
OrderDate)
SELECT #CustomerId,
#ProductId,
#OrderDate;
SELECT SCOPE_IDENTITY();
END;
ELSE
BEGIN
SELECT -1;
END;
COMMIT TRAN;
END;
GO
Note the explicit transaction isolation level definition and the condition and insert wrapped in a transaction will ensure concurrent calls will not interfere with each other.
I would only like to insert the record if the OrderDate is between the customer's TermStartDate and TermEndDate
create proc usp_orders
(
#CustomerID int
#ProductID int,
# OrderDate datetime
)
as
begin
if exists(select 1 from dbo.customers where customerid=#customerid and #orderdate >= termstartdate and #orderdate<= termenddate)
Begin
Insert into Dbo.orders
select #customerid,#productid,#orderdate
End
End
I don't understand my options in terms of what could happen if the condition is not met, e.g. the record is not inserted and an error message is returned(?)
if the condition is not met ,no record will be inserted
I would also like the stored procedure to consider whether another user is also making changes to the underlying data when the stored procedure is executing (if that needs considering)
When two users execute same procedure at same time,SQL will take care of concurrency issues for you.

Stored procedure for Insert won't execute and throws 2 errors

I wrote a stored procedure and it looks to be set up properly, here is the stored procedure:
CREATE PROCEDURE sp_Insert$Order$For$Travel
(
#CardHolderName varchar(50),--Userprofile
#CardNumber int, --Userprofile
#SecurityCode int, --Userprofile
#ExpiryDate date, --Userprofile
#DeparturePoint int, --Order
#DestinationPoint int, --Order
#DepartureTime datetime, --Order
#DestinationTime datetime, --Order
#Passengers int, --Order
#RoundTrip char(3), --Order
#ReturnDate datetime, --Order
#OrderNumber int --order
)
AS
Declare #UserIdentity int
Insert into [UserProfile]
(CardHolderName, CardHolderName, SecurityCode, ExpiryDate)
values
(#CardHolderName, #CardNumber, #SecurityCode, #ExpiryDate)
set #UserIdentity = ##IDENTITY
Insert into [Order]
(DeparturePoint, DestinationPoint, DepartureTime, DestinationPoint,DestinationTime,Passengers,RoundTrip, ReturnDate, OrderNumber, UserID)
values
(#DeparturePoint, #DestinationPoint, #DepartureTime, #DestinationPoint, #DestinationTime,#Passengers, #RoundTrip, #ReturnDate, #OrderNumber, #UserIdentity)
when I go to execute it I am receiving these 2 errors.
Msg 264, Level 16, State 1, Procedure sp_Insert$Order$For$Travel, Line 25
The column name 'CardHolderName' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
Msg 264, Level 16, State 1, Procedure sp_Insert$Order$For$Travel, Line 31
The column name 'DestinationPoint' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
I have looked over my stored procedure and look at others that I have done in the past and it looks to be ok, I just don't see where the issue is.
Anyone see what the issue is that I am having? I have checked my parameters and they are all correct based on the columns in the tables that I want to insert them in...
Any ideas?
It's exactly what the error says:
Insert into [UserProfile]
(CardHolderName, CardHolderName, SecurityCode, ExpiryDate)
values
(#CardHolderName, #CardNumber, #SecurityCode, #ExpiryDate)
... has CardHolderName twice in the destination columns. Presumably the second one should be CardNumber.
The other is similar.
Try this instead:
Insert into [UserProfile](CardHolderName, CardNumber, SecurityCode, ExpiryDate)
values (#CardHolderName, #CardNumber, #SecurityCode, #ExpiryDate);
set #UserIdentity = SCOPE_IDENTITY();
I think you want CardNumber for the second variable rather than CardHolderName.
I also changed the ##IDENTITY to SCOPE_IDENTITY(), which is safer.
EDIT:
I think the documentation does a good job of explaining it here.
The basic idea is that the four ways of getting the most recent identity (##IDENTITY, SCOPE_IDENTIY(), IDENT_CURRENT(), and the OUTPUT clause). The safest is the OUTPUT clause. The other three depend on three things: the "table", the "connection", and the "scope".
The first two return the most recent identity regardless of table, but on the same connection. If there is an insert trigger on the table, the trigger might insert a value into another table. ##IDENTITY will return the id from that table (same connection, different scope). SCOPE_IDENTITY() will return the id from the intended table (same connection, same scope). IDENT_CURRENT() is just inviting a race condition with other connections that might also be adding rows onto the table.
The OUTPUT clause doesn't suffer from these interpretations. It just puts the values from the insert into a temporary table -- at the expense of a bit more coding and learning something new.
You have errors in the following lines:
Insert into [UserProfile]
(CardHolderName, CardHolderName, SecurityCode, ExpiryDate)
Repeated column name "CardHolderName" twice in insert statement to table "UserProfile"
Insert into [Order]
(DeparturePoint, DestinationPoint, DepartureTime, DestinationPoint)
Repeated column name "DestinationPoint" twice in insert statement to table "Order".

Stored Procedure - Knowing the next ID

I am creating a stored procedure to create a new customer so for instance,
CREATE PROCEDURE Customer_Create
#customer_arg
#type_arg
AS
BEGIN
INSERT INTO Customer (Customer_id, Type_id)
VALUES (#Customer_arg,#type_arg)
End;
If I have several foreign keys in my statement and they are all ID's is there a way for me to pull the NEXT ID number automatically without having to know what it would be off the top of my head when I run the execute statement? I would like to just have it pull the fact that the ID will be 2 because the previous record was 1
EXECUTE Customer_Create 16,2
Is it something wnith output? If so how does this work code wise
I suspect that what you want to do is return the new id after the record is inserted. For that:
CREATE PROCEDURE Customer_Create (
#customer_arg,
#type_arg,
#NewCustomerId int output
) AS
BEGIN
INSERT INTO Customer(Customer_id, Type_id)
VALUES (#Customer_arg, #type_arg);
#NewCustomerId = scope_identity();
End;
There are several other choices for getting the identity, which are explained here.
To get to the last inserted IDENTITY value you should use the OUTPUT clause like this:
DECLARE #IdentValues TABLE(v INT);
INSERT INTO dbo.IdentityTest
OUTPUT INSERTED.id INTO #IdentValues(v)
DEFAULT VALUES;
SELECT v AS IdentityValues FROM #IdentValues;
There are several other mechanisms like ##IDENTITY but they all have significant problems. See my Identity Crisis article for details.
In your case you can also experiment with #IDENTITY like this
DECLARE #NextID int
--insert statement goes here
SET #NextID = ##Identity`
Here are couple good resources for getting familiar with this
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
http://blog.sqlauthority.com/2013/03/26/sql-server-identity-fields-review-sql-queries-2012-joes-2-pros-volume-2-the-sql-query-techniques-tutorial-for-sql-server-2012/

Stored procedure return the primary key after insert?

I want a stored procedure to return the primary key of the new record after it gets executed. I think it will be returned by OUT parameter in the procedure. But how to select the newly inserted row ID ? I don't want to use select MAX(row_id) as it is a multi user environment.
Any procedure sample will be appreciated.
My platform is ISeries DB2 V5 R4. Thanks.
Edit
The row id Column is not an identity column. It uses a sequence for the key which gets generated via a trigger before insert on table.
Edit
Here is what I am trying to do
Begin Stored procedure
Insert into Employees;
(row id gets automatically generated by trigger)
Return row id ;
I want to avoid a select in returning row id.
just set the out parameter to the column that contains the PK.
CREATE PROCEDURE DB2TBL.DO_STUFF (IN Param1 INT, IN Param2 CHAR(32),OUT Param3 INT)
/* Param1 is primary key */
LANGUAGE SQL
P1: BEGIN
DECLARE OUTPARAM INT;
/* Do the stored procedure */
SET OUTPARAM = Param1;
--UPDATED---
Hi Popo,
First off could you give more detail on what you mean when you say the rowid is assigned by a trigger?
If you had a real identity column you would use the IDENTITY_VAL_LOCAL() function like this right after the INSERT: SELECT IDENTITY_VAL_LOCAL() INTO myrowid FROM SYSIBM.SYSDUMMY1; I'm not 100% on that syntax because I generally use embedded SQL and it works differently there so you might have to play with it. IBM documentation is at http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/db2/rbafzscaidentity.htm.
However since you are doing something more complicated, I think this alternate method might work. You'll need to re-format your INSERT to be wrapped in a SELECT.
SELECT myrowid
INTO myrowid
FROM FINAL TABLE (
INSERT INTO myfile (myrowid, other_stuff) VALUES (default, 'blah')
)
You'll need to adjust for the proper field names and so on but I think this will do the trick. There's not much documentation but if you want to see it go to http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/db2/rbafzbackup.htm and scroll all the way down to the bottom of the page.
Cheers
CREATE PROCEDURE ASF_InsertNewAuthorRequest
(IN #REQUESTTYPE CHAR(1), IN #UserID VARCHAR(18), IN #DATECREATED TIMESTAMP, IN #REQUESTSTATUS CHAR(1))
LANGUAGE SQL
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE cursor1 CURSOR WITH RETURN for
SELECT IDENTITY_VAL_LOCAL FROM SYSIBM.SYSDUMMY1;
INSERT INTO AFS_REQUEST
( REQUESTTYPE, "UserID", DATECREATED, REQUESTSTATUS )
VALUES
( #REQUESTTYPE, #UserID, #DATECREATED, #REQUESTSTATUS );
OPEN cursor1;
END P1
INSERT INTO [User] (columns)
OUTPUT inserted.userId
VALUES (#values)
This will return the newly created userId column value... very simple.

SQL Server - Get Inserted Record Identity Value when Using a View's Instead Of Trigger

For several tables that have identity fields, we are implementing a Row Level Security scheme using Views and Instead Of triggers on those views. Here is a simplified example structure:
-- Table
CREATE TABLE tblItem (
ItemId int identity(1,1) primary key,
Name varchar(20)
)
go
-- View
CREATE VIEW vwItem
AS
SELECT *
FROM tblItem
-- RLS Filtering Condition
go
-- Instead Of Insert Trigger
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
SELECT Name
FROM inserted;
END
go
If I want to insert a record and get its identity, before implementing the RLS Instead Of trigger, I used:
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = SCOPE_IDENTITY();
With the trigger, SCOPE_IDENTITY() no longer works - it returns NULL. I've seen suggestions for using the OUTPUT clause to get the identity back, but I can't seem to get it to work the way I need it to. If I put the OUTPUT clause on the view insert, nothing is ever entered into it.
-- Nothing is added to #ItemIds
DECLARE #ItemIds TABLE (ItemId int);
INSERT INTO vwItem (Name)
OUTPUT INSERTED.ItemId INTO #ItemIds
VALUES ('MyName');
If I put the OUTPUT clause in the trigger on the INSERT statement, the trigger returns the table (I can view it from SQL Management Studio). I can't seem to capture it in the calling code; either by using an OUTPUT clause on that call or using a SELECT * FROM ().
-- Modified Instead Of Insert Trigger w/ Output
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
OUTPUT INSERTED.ItemId
SELECT Name
FROM inserted;
END
go
-- Calling Code
INSERT INTO vwItem (Name)
VALUES ('MyName');
The only thing I can think of is to use the IDENT_CURRENT() function. Since that doesn't operate in the current scope, there's an issue of concurrent users inserting at the same time and messing it up. If the entire operation is wrapped in a transaction, would that prevent the concurrency issue?
BEGIN TRANSACTION
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = IDENT_CURRENT('tblItem');
COMMIT TRANSACTION
Does anyone have any suggestions on how to do this better?
I know people out there who will read this and say "Triggers are EVIL, don't use them!" While I appreciate your convictions, please don't offer that "suggestion".
You could try SET CONTEXT_INFO from the trigger to be read by CONTEXT_INFO() in the client.
We use it the other way to pass info into the trigger but would work in reverse.
Have you in this case tried ##identity? You mentioned both scope_Identity() and identity_current() but not ##identity.