Need help in converting INSERT\UPDATE to MERGE - sql-server-2008

Is this a good candidate for a MERGE command?
Must the source data also be another table or can it be variables that are passed?
If it must be a table, is inserting the passed variables into a temp table sane?
Could you help me with the syntax?
CREATE PROCEDURE [dbo].[usp_ConvertToMerge]
#GL_DT date
,#SRC_SYS_ID varchar(60)
,#MLR_SRC_SYS_CD char(3)
,#TRSRY_FEED_DT date
,#Data varchar(20)
AS
BEGIN
IF NOT EXISTS (SELECT
#GL_DT
FROM
MLR_REBATE_IBOR_INFO_2
WHERE
[GL_DT] = #GL_DT
AND [SRC_SYS_ID] = #SRC_SYS_ID
AND [MLR_SRC_SYS_CD] = #MLR_SRC_SYS_CD
AND [TRSRY_FEED_DT] = #TRSRY_FEED_DT)
BEGIN
INSERT INTO [dbo].[MLR_REBATE_IBOR_INFO_2]
([GL_DT],
[SRC_SYS_ID],
[MLR_SRC_SYS_CD],
[TRSRY_FEED_DT],
[Data])
SELECT
#GL_DT
,#SRC_SYS_ID
,#MLR_SRC_SYS_CD
,#TRSRY_FEED_DT
,#Data
END
ELSE
BEGIN
UPDATE [dbo].[MLR_REBATE_IBOR_INFO_2]
SET [Data] = #Data
WHERE [GL_DT] = #GL_DT
AND [SRC_SYS_ID] = #SRC_SYS_ID
AND [MLR_SRC_SYS_CD] = #MLR_SRC_SYS_CD
AND [TRSRY_FEED_DT] = #TRSRY_FEED_DT
END
END
GO

I think I did it:
CREATE PROCEDURE MyMergeTest
#GL_DT date
,#SRC_SYS_ID char(20)
,#MLR_SRC_SYS_CD char(3)
,#TRSRY_FEED_DT date
,#Data varchar(20)
AS
BEGIN
MERGE MLR_REBATE_IBOR_INFO_2 AS target
USING
(
SELECT
#GL_DT
,#SRC_SYS_ID
,#MLR_SRC_SYS_CD
,#TRSRY_FEED_DT
,#Data
) AS source
(
GL_DT
,SRC_SYS_ID
,MLR_SRC_SYS_CD
,TRSRY_FEED_DT
,Data
)
ON (
target.GL_DT = source.GL_DT AND
target.SRC_SYS_ID = source.SRC_SYS_ID AND
target.MLR_SRC_SYS_CD = source.MLR_SRC_SYS_CD AND
target.TRSRY_FEED_DT = source.TRSRY_FEED_DT
)
WHEN MATCHED THEN
UPDATE SET Data = source.Data
WHEN NOT MATCHED THEN
INSERT
(
[GL_DT],
[SRC_SYS_ID],
[MLR_SRC_SYS_CD],
[TRSRY_FEED_DT],
[Data]
)
VALUES
(
[GL_DT], --<<it looks like these can eiether be the variable eg, #GL_DT, or prefixed by 'source.'
[SRC_SYS_ID],
[MLR_SRC_SYS_CD],
[TRSRY_FEED_DT],
[Data]
);
END

Related

Set update row id to OUT parameter in MySQL

The table tbtable contains the following columns.
The procedure to create or update an entry in tbtable is the following.
CREATE PROCEDURE `createOrUpdateTbTable` (
IN `this_pid` INT UNSIGNED,
IN `this_sid` INT UNSIGNED,
IN `this_ri` LONGBLOB,
IN `this_defaults` TINYINT,
IN `this_approved` TINYINT,
OUT `id` INT UNSIGNED
)
BEGIN
UPDATE `tbtable` SET
`ri` = this_ri, `defaults` = this_defaults, `approved` = this_approved
WHERE `pid` = this_pid AND `sid` = this_sid;
IF ROW_COUNT() = 0
THEN
INSERT INTO `tbtable` (`pid`, `sid`, `ri`, `defaults`, `approved`)
VALUES (this_pid, this_sid, this_ri, this_defaults, this_approved);
SET id = LAST_INSERT_ID();
END IF;
END
Right now I don't have any way to get the id of an entry when an update occurs. To what script should I change my current createOrUpdate method so that I can also retrieve the id when an update happens?
I checked other similar questions but they don't have any OUT parameter, so not applicable for my case.
Thanks.
EDIT:
BEGIN
IF EXISTS (SELECT*FROM `tbtable` WHERE `pid` = this_pid AND `sid` = this_sid)
THEN
UPDATE `tbtable`
SET
`ri` = this_ri, `defaults` = this_defaults, `approved` = this_approved
WHERE `pid` = this_pid AND `sid` = this_sid;
SET id = `id` ;
ELSE
INSERT INTO `tbtable` (`pid`, `sid`, `ri`, `defaults`, `approved`)
VALUES (this_pid, this_sid, this_ri, this_defaults, this_approved);
SET id = LAST_INSERT_ID();
END IF;
END
I tried this approach as well, but the id is null when there is an update.
We could run a SELECT t.myid INTO v_id FROM t WHERE ... statement to store a value into a local procedure variable.
Or, we could set a user-defined variable.
Note that the same identifier might be used for a routine parameter, a local variable and a column. A routine parameter takes precedence over a table column.
In the general case, an UPDATE statement can affect more than one row, so we could have multiple rows. The procedure argument is a scalar, so we would need to decide which of the rows we want to return the id from.
Assuming that id column is guaranteed to be non-NULL in the (unfortunately named) tbtable table...
BEGIN
DECLARE lv_id BIGINT DEFAULT NULL;
-- test if row(s) exist, and fetch lowest id value of from matching rows
SELECT t.id
INTO lv_id -- save retrieved id value into procedure variable
FROM tbtable t
WHERE t.pid = this_pid
AND t.sid = this_sid
ORDER BY t.id
LIMIT 1
;
-- if we got a non-NULL value returned
IF lv_id IS NOT NULL THEN
-- do the update
UPDATE `tbtable` t
SET t.ri = this_ri
, t.defaults = this_defaults
, t.approved = this_approved
WHERE t.pid = this_pid
AND t.sid = this_sid
;
ELSE
INSERT INTO `tbtable` (`pid`, `sid`, `ri`, `defaults`, `approved`)
VALUES (this_pid, this_sid, this_ri, this_defaults, this_approved)
;
SET lv_id = LAST_INSERT_ID();
END IF;
-- set OUT parameter
SET id = lv_id ;
END$$
Note that this procedure is subject to a race condition, with a simultaneous DELETE operation from another session. Our SELECT statement could return an id for a matching row, and another session could DELETE that row, and then our update runs, and doesn't find the row. Timing here is pretty tight, it would be difficult to demonstrate this without adding a delay into the procedure, like a SELECT WAIT(15); right before the UPDATE (to give us fifteen seconds to run a delete from another session.)
You try to return a single value but your update statement could be executed in multiple rows. So when you return the id from that type of updated statement , you need to loop through the updated rows and return any one of those updated row values (because you expect that the combination of pid and sid is unique). Here is sample code without the rid columns as i do not want to create a temporary database with that :)
CREATE PROCEDURE createOrUpdateTbTable (
IN this_pid INT UNSIGNED,
IN this_sid INT UNSIGNED,
IN this_ri LONGBLOB,
IN this_defaults TINYINT,
IN this_approved TINYINT,
OUT id INT UNSIGNED
)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE updated_id INT;
DECLARE updatedIds CURSOR FOR SELECT tbtable.id FROM tbtableWHERE pid = this_pid AND sid = this_sid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
IF EXISTS (SELECT*FROM `tbtable` WHERE `pid` = this_pid AND `sid` = this_sid)
THEN
UPDATE `tbtable`
SET
`defaults` = this_defaults, `approved` = this_approved
WHERE `pid` = this_pid AND `sid` = this_sid;
OPEN updatedIds;
read_loop: LOOP
FETCH updatedIds INTO updated_id;
SET id = updated_id;
IF done THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE updatedIds;
ELSE
INSERT INTO `tbtable` (`pid`, `sid`, `defaults`, `approved`)
VALUES (this_pid, this_sid, this_defaults, this_approved);
SET id = LAST_INSERT_ID();
END IF;END
You need explicit return the value at the end:
IF ROW_COUNT() = 0
THEN
INSERT INTO `tbplanhassurface` (`planid`, `surfaceid`, `roi`, `defaultsurface`, `approved`)
VALUES (this_planid, this_surfaceid, this_roi, this_defaultsurface, this_approved);
SET id = LAST_INSERT_ID();
ELSE
SELECT #id = your_id_field
FROM `tbplanhassurface`
WHERE `planid` = this_planid
AND `surfaceid` = this_surfaceid;
END IF;
SELECT #id;
END

MySQL stored procedure will execute but conditions not working

MySQL stored procedure will execute but conditions are not working. Could someone clear up this issue?
The empty value is not checking with or condition. Does it need a replacement for or?
DELIMITER $$
CREATE DEFINER=`rebar`#`%` PROCEDURE `SearchInProgress`(
ClientID bigint,
GCName varchar(250),
TeamID int,
USPMID Bigint,
JobReceivedDate datetime,
importanceID Bigint
)
begin
select * from jobdetails
where
(clientid = ClientID or ClientID = "") and
(GCName = GCName or GCName ="") and
(TeamID = TeamID or TeamID ="") and
(ReceivedDate = JobReceivedDate or JobReceivedDate = "") and
(ImportanceID = importanceID or importanceID = "") and
(JobID in (select jobid from JobCoordinatorDetails where USProjectManagerID = USPMID) );
end
I think it could be brackets
This (clientid = ClientID or ClientID = "") could be written like this
(clientid = ClientID) OR (ClientID = "" )
Its justa thought. I might be wrong
DELIMITER $$
DROP PROCEDURE if exists SearchInProgress $$
CREATE PROCEDURE `SearchInProgress`(
aclientID bigint,
aGCName varchar(250),
aTeamID int,
aUSProjectManagerID bigint,
aReceivedDate datetime,
aImportanceID bigint
)
begin
select * from jobdetails
where
(clientid = aclientID or aclientID = '') and
(GCName = aGCName or aGCName = '') and
(TeamID = aTeamID or aTeamID = '') and
(ReceivedDate = aReceivedDate or aReceivedDate = '0000-00-00 00:00:00') and
(ImportanceID = aImportanceID or aImportanceID = '') and
(JobID in (select jobid
from JobCoordinatorDetails
where USProjectManagerID = aUSProjectManagerID) or aUSProjectManagerID = '')
;
end
The parameter name and the column name must be different. I've prepended a to the parameter names.
An empty datetime value is replaced by '0000-00-00 00:00:00': https://dev.mysql.com/doc/refman/5.7/en/datetime.html

Use of transaction in stored procedure

I create a stored procedure and I want to use transaction in this. So how can I use transaction in this stored procedure? Which part will start the transaction because INSERT and UPDATE both are together in this stored procedure.
This is my stored procedure:
ALTER PROCEDURE [dbo].[usp_InsertUpdateADAlertRecipient]
#AD_User_ID int,
#AD_Client_ID int,
#AD_Org_ID int,
#IsActive bit,
#Created datetime,
#Createdby int,
#Updated datetime,
#Updatedby int,
#AD_Alertrecipient_ID int,
#AD_AlertRule_ID int,
#AD_Role_ID int,
#Sendemail bit
AS
SET NOCOUNT ON
IF EXISTS(SELECT [AD_Alertrecipient_ID] FROM [dbo].[AD_AlertRecipient] WHERE [AD_Alertrecipient_ID] = #AD_Alertrecipient_ID)
BEGIN
UPDATE [dbo].[AD_AlertRecipient] SET
[AD_User_ID] = #AD_User_ID,
[AD_Client_ID] = #AD_Client_ID,
[AD_Org_ID] = #AD_Org_ID,
[IsActive] = #IsActive,
[Created] = #Created,
[Createdby] = #Createdby,
[Updated] = #Updated,
[Updatedby] = #Updatedby,
[AD_AlertRule_ID] = #AD_AlertRule_ID,
[AD_Role_ID] = #AD_Role_ID,
[Sendemail] = #Sendemail
WHERE
[AD_Alertrecipient_ID] = #AD_Alertrecipient_ID
select #AD_Alertrecipient_ID
END
ELSE
BEGIN
INSERT INTO [dbo].[AD_AlertRecipient] (
[AD_User_ID],
[AD_Client_ID],
[AD_Org_ID],
[IsActive],
[Created],
[Createdby],
[Updated],
[Updatedby],
--[AD_Alertrecipient_ID],
[AD_AlertRule_ID],
[AD_Role_ID],
[Sendemail]
) VALUES (
#AD_User_ID,
#AD_Client_ID,
#AD_Org_ID,
#IsActive,
#Created,
#Createdby,
#Updated,
#Updatedby,
--#AD_Alertrecipient_ID,
#AD_AlertRule_ID,
#AD_Role_ID,
#Sendemail
)
SELECT SCOPE_IDENTITY()
END
Thanks and waiting for your reply .

mySQL: Query performance with User Defined Functions

I'm trying to make a multi-language mySQL database.
I was considering using user defined functions to determine which column in a table to read - where different columns will store the different translations - however I was concerned about query performance.
For example, if I wanted to return a list of Cities, and the name of country - with the latter returned in multiple languages. At what point would the below approach impact performance? - as I might do an equivalent on a table with 2-5,000 rows.
Country table structure:
SELECT
`CountryID`,
`Name_English`,
`Name_French`,
`Name_Spanish`
FROM `Country`
WHERE `CountryID` = fCountryID;
City Table Structure:
SELECT
`City`.`CityName` 'City'
,getCountry(1, `City`.`CountryID`) 'Country'
FROM `City`;
Example Function Call:
SELECT
`City`.`CityName` 'City'
,getCountry(1, `City`.`CountryID`) 'Country'
FROM `City`;
Full Function:
delimiter $$
CREATE DEFINER=root#localhost FUNCTION
getCountry(fLanguageID INT, fCountryID SMALLINT)
RETURNS varchar(100) CHARSET utf8 COLLATE utf8_unicode_ci
BEGIN
DECLARE returnCountry VARCHAR(100);
IF (fLanguageID = 1) -- English
THEN
SET returnCountry = (
SELECT `Name_English` FROM `Country`
WHERE `CountryID` = fCountryID
);
ELSEIF (fLanguageID = 2) -- French
THEN
SET returnCountry = (
SELECT `Name_French` FROM `Country`
WHERE `CountryID` = fCountryID
);
ELSEIF (fLanguageID = 3) -- Spanish
THEN
SET returnCountry = (
SELECT `Name_Spanish` FROM `Country`
WHERE `CountryID` = fCountryID
);
END IF;
RETURN returnCountry;
SELECT
`CountryID`,
`Name_English`,
`Name_French`,
`Name_Spanish`
FROM `Country`
WHERE `CountryID` = fCountryID;
SELECT
`City`.`CityName` 'City'
,getCountry(1, `City`.`CountryID`) 'Country'
FROM `City`;
END$$

serialising rows in a table

I have a table which contains header information for transactions. The transactions belong to different projects.
In the header I have columns:
rhguid - uniqueidentifier
rhserial - int
rh_projectID - int
First I insert the row (there's more columns)
Then I calculate the serial number for that project:
update responseheader
set rhSerial = 1 + (select isnull(max(rhSerial), 0)
from responseheader
where (rhstatus = 0) AND (rh_projectID = 1234))
where
(rhGUID = <preassignedGUID>);
However when there are many transactions happening at the same time for a project I am finding duplicate rhserial values.
I'm doing this in classic ASP with SQL Server 2008.
Is there a better way?
From your example, it doesn't look like you're using a transaction. My guess is that the SELECT portion of the statement is running as READ UNCOMMITTED, otherwise you would not see duplicates. There are ways to start transactions with ADO, but I prefer using stored procedures instead.
Try implementing something like this:
CREATE PROC dbo.ResponseHeader_Insert
<more data to insert>,
#ProjectID INT,
#Status SMALLINT
as
insert responseheader (column names here)
select <param values here>, isnull(max(rhSerial), 0) + 1
from responseheader
where (rhstatus = #Status) AND (rh_projectID = #ProjectID))
If this doesn't work for ya, try creating sequence tables (one for each sequence).
create table <tablename> (
SeqID int identity(1,1) primary key,
SeqVal varchar(1)
)
Create a procedure to get the next identity:
create procedure GetNewSeqVal_<tablename>
as
begin
declare #NewSeqValue int
set NOCOUNT ON
insert into <tablename> (SeqVal) values ('a')
set #NewSeqValue = scope_identity()
delete from <tablename> WITH (READPAST)
return #NewSeqValue
end
If there are too many sequence tables that need to be created or you want to create sequences on the fly, try this approach:
Create table AllSequences (
SeqName nvarchar(255) primary key, -- name of the sequence
Seed int not null default(1), -- seed value
Incr int not null default(1), -- incremental
Currval int
)
Go
create procedure usp_CreateNewSeq
#SeqName nvarchar(255),
#seed int = 0,
#incr int = 1
as
begin
declare #currval int
if exists (
select 1 from AllSequences
where SeqName = #SeqName )
begin
print 'Sequence already exists.'
return 1
end
if #seed is null set #seed = 1
if #incr is null set #incr = 1
set #currval = #seed
insert into AllSequences (SeqName, Seed, Incr, CurrVal)
values (#SeqName, #Seed, #Incr, #CurrVal)
end
go
create procedure usp_GetNewSeqVal
#SeqName nvarchar(255)
as
begin
declare #NewSeqVal int
set NOCOUNT ON
update AllSequences
set #NewSeqVal = CurrVal = CurrVal+Incr
where SeqName = #SeqName
if ##rowcount = 0 begin
print 'Sequence does not exist'
return
end
return #NewSeqVal
end
go