prevent duplicate value to submit using stored procedure in sql server - sql-server

I want to prevent the same #coupon_value in sp to submit and return
any message for validation using csharp but I am not able to how to
make changes in stored procedure.
CREATE PROCEDURE [dbo].[USP_REBATE_CAMPAIGN_RULE_DETAIL_VALIDATE]
#rebate_campaign_seq INT,
#coupon_value Varchar(50)='',
#Type varchar(50)='SERIES'
AS
SET NOCOUNT ON
BEGIN
SELECT rcrd.rebate_campaign_rule_detail_seq AS id,Type_value AS NAME,
'SERIES' AS type,
rcrd.amount_per_range AS Amount
FROM rebate_campaign_rule_detail rcrd (nolock)
INNER JOIN rebate_campaign_rule rcr
ON rcr.rebate_campaign_rule_seq = rcrd.rebate_campaign_rule_seq
INNER JOIN rebate_campaign rc
ON rc.rebate_campaign_seq = rcr.rebate_campaign_seq
WHERE rc.rebate_campaign_seq = #rebate_campaign_seq
AND rcrd.active_flag = 'Y' AND rcrd.type = #Type
AND rcrd.type_value=#coupon_value
End

The only way to prevent duplicate in database data is to add a UNIQUE CONSTRAINT. Everything else will fail, especially any solution coded with a procedural program, because of concurrency (imagine for a moment that two users launch the same procedure with the same values at the same time...).
To have a NULLbale UNIQUE constraint, you can add a UNIQUE filtered INDEX like this one :
CREATE INDEX X_UNIQUE_COUPON_RCRD
ON rebate_campaign_rule_detail (type_value)
WHERE type_value IS NOT NULL;

Related

Access Listbox query preventing SQL Server Update Query

I'm developing an Access application and a SQL Server backend simultaneously. I have a Form with a listbox which, when a record is double clicked, opens an unbound form and loads data into it based on the record selected. When changes are made in this second form, a button initiates a pass through query that executes a stored procedure updating the details of the record in the base table in SQL Server.
Here's the thing. As long as Form1 (with the listbox) is open, the stored procedure times out without running. If I close that form, it takes less than a second. It behaves this way when run from Access, when run from management studio, and when run in management studio as a query with hard values (not a sproc with parameters).
The row source for the listbox is a linked table that references a View in SQL Server. The query within the view is a recursive common table expression of two different tables, one of which is the table being edited by the sproc. I've set the view to read only. Is there another setting that I can do to help here?
Here's the stored procedure:
PROCEDURE [dbo].[spSalesPlanUpdate]
#Salesyear numeric(4,0),
#ItemNumber varchar(20),
#Baseline int,
#Speculation int,
#Comments varchar(max)
AS
declare #SY numeric(4,0),
#ItN varchar (20),
#BL int,
#SPL int,
#CmT varchar(max)
set #SY = #Salesyear
set #ItN = #ItemNumber
set #BL = #Baseline
set #SPL = #Speculation
set #CmT = #Comments
BEGIN
SET NOCOUNT ON;
update SalesPlan
set Baseline = #BL
,Speculation = #SPL
,DateModified = getdate()
,Comments = #CmT
where SalesYear = #SY and ItemNumber = #ItN
END
I used both parameters and local variables because at first I was thinking it might be about parameter sniffing.
Here's the view the listbox is queried from:
view [dbo].[vwSalesPlan] as
with cte
as
(
select Item, year(getdate()) as SY
from vwItemsAndLiners il
union all
select ial.Item,
(cte.SY + 1)
From vwItemsAndLiners ial join cte on ial.Item = cte.Item
Where SY < (year(getdate())+ial.YearsFromProp)
)
select sp.ItemNumber, ial.Variety, ial.Size, ial.PerTray, sp.SalesYear, sp.SalesYear - ial.YearsFromProp as PropYear,
sp.SalesYear - ial.YearsFromProduction as ProductionYear,
sp.Baseline, sp.Speculation,
CEILING((CAST(SP.BASELINE AS NUMERIC (12,2)) + CAST(SP.SPECULATION AS numeric(12,2)))/IAL.PerTray)*IAL.PerTray as Total ,
sp.DateModified, ial.Segment ,'Entered' as [Status], sp.Comments
From SalesPlan sp inner join vwItemsAndLiners ial on sp.ItemNumber = ial.Item
Where ial.status = 'Sell'
union
select cte.Item, ial.Variety, ial.Size, ial.PerTray, SY, cte.sy - ial.YearsFromProp as PropYear,
cte.SY - ial.YearsFromProduction as ProductionYear,'', '', 0, null, ial.Segment , 'Not Entered', null
from cte inner join vwItemsAndLiners ial on cte.Item = ial.Item
where cte.Item not in (select ItemNumber from SalesPlan where salesplan.SalesYear = CTE.SY) and ial.Status = 'Sell'
with check option
Table being updated: SalesPlan
View that the listbox is queried from: vwSalesPlan
I realize that there's a lot of stuff here. Really, I'm just hoping this generates some ideas of why a form being open would lock the original table from an update query. Thanks!
I tried:
Indexing the views in SQL Server that provide the rowsource for the listbox, but because they contain CTE's they cannot be indexed.
lstbox.recordset.movefirst then lstbox.recordset.movelast to force access to read the entire list, but whenever the list was filtered or requeried it would throw an error saying the recordset object had changed and the movefirst command was invalid.
So I wrote this sub:
Private Sub readtheData()
Dim i As Integer
i = Me.lstSalesPlan.ListCount
End Sub
Simply forcing it to count the records every time the form was loaded or the query behind the listbox was filtered forced access to release the lock. Hope this helps somebody down the road!

SQL Trigger Works in Play but not Production

I created an SQL trigger in my Play database and it worked great. When I moved it over to Production, it suddenly won't work. We want the trigger to kick off whenever someone edits one of two custom fields in our database. The company who created the software already set up a trigger that kicks of any time a change is made to the database object (it just didn't track the changes made to custom fields). If I let my new trigger create a new record, I wound up with two audit records, so I changed my trigger to update the audit record the software company's trigger created. Could anyone tell me what I have done wrong? Here is my trigger:
USE [TmsEPrd]
GO
/****** Object: Trigger [dbo].[tr_Biograph_Udef_Audit_tracking] Script Date: 11/23/2020 10:22:57 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_Biograph_Udef_Audit_tracking] ON [dbo].[BIOGRAPH_MASTER] FOR UPDATE AS
BEGIN
IF EXISTS (SELECT 1 FROM deleted d
JOIN inserted i ON d.ID_NUM = i.ID_NUM
JOIN (SELECT ID_NUM, binary_checksum(UDEF_10A_1, UDEF_2A_4) AS inserted_checksum
FROM inserted) a ON i.ID_NUM = a.ID_NUM
JOIN (SELECT ID_NUM, binary_checksum(UDEF_10A_1, UDEF_2A_4) AS deleted_checksum
FROM deleted) b ON d.ID_NUM = b.ID_NUM
WHERE a.inserted_checksum <> b.deleted_checksum)
BEGIN
Update BIOGRAPH_HISTORY
set archive_job_name = 'UDEF_Change',
udef_2a_4 = i.udef_2a_4,
udef_2a_4_CHG = i.udef_2a_4_chg,
udef_10a_1 = i.udef_10a_1,
udef_10a_1_chg = i.udef_10a_1_chg
from
(select i.ID_NUM, SYSDATETIME()as job_time_a,
i.UDEF_10A_1, case when i.UDEF_10A_1 = d.UDEF_10A_1 then 0 when i.UDEF_10A_1 is null and d.UDEF_10A_1 is null then 0 else 1 end as UDEF_10A_1_CHG,
i.UDEF_2A_4, case when i.UDEF_2A_4 = d.UDEF_2A_4 then 0 when i.UDEF_2A_4 is null and d.UDEF_2A_4 is null then 0 else 1 end as UDEF_2A_4_CHG,
d.USER_NAME,d.JOB_NAME,d.JOB_TIME
FROM deleted d JOIN inserted i ON d.ID_NUM = i.ID_NUM) i
join BIOGRAPH_HISTORY b on i.ID_NUM = b.ID_NUM
where DATEDIFF(Minute, i.job_time_a, b.ARCHIVE_JOB_TIM) = 0
and b.ARCHIVE_JOB_NAME not like 'UDEF_Change%'
END;
END;
Try specifying #order = 'LAST' for your trigger. It might be that your trigger is executing first and not finding a record to update. In your test system, the trigger execution order might be reversed.
The order that triggers are created might affect trigger execution order, but this is not something to rely upon. When you think about it, this can be a headache. A test system that looks just like production can behave differently.
This is similar to relying upon a "natural" record order of a clustered index and not using a ORDER BY clause. A different execution plan can use a different index or go parallel resulting in a different or no order.

Optionally saving selectively data in one-go only when you want it

I have a table where data does not initially exist until an action is taken that stores all settings made by client in one-go. To illustrate this simply, a button click that stores all column values off a (HTML) table into a database table (let's call this dbo.Settings).
So instead of inserting into this dbo.Settings all the default values prior to user making any changes to their individual settings (ever), I kind of created the pseudo data for them that will be returned whenever requested, kind of like SELECT-ing the default values:
SELECT
CanView = ISNULL(CanView, 1),
CanRead = ISNULL(CanRead, 1),
CanWrite = ISNULL(CanWrite, 0)
FROM
dbo.Settings AS s
WHERE
UserId = #id
Rather than doing:
IF NOT EXISTS(SELECT * FROM dbo.Settings WHERE UserId = #id)
BEGIN
INSERT INTO dbo.Settings (UserId, CanView, CanRead, CanWrite)
VALUES (#id, 1, 1, 0)
END
The problem with this is whenever I need to add a new setting column in the future, I now have to note one more procedure to modify/add the default value for this column as well -- which I don't like. Using TRIGGER would be an option but I wonder what the best practice in managing data like this would be. Or would you do something like this:
CREATE PROC Settings_CreateOrModify
#userId INT,
#canView BIT = NULL,
#canRead BIT = NULL,
#canWrite BIT = NULL
AS
BEGIN
IF EXISTS(SELECT * FROM dbo.Settings WHERE UserId = #userId) BEGIN
UPDATE s
SET
CanView = #canView,
CanRead = #canRead,
CanWrite = #canWrite
FROM
dbo.Settings AS s
WHERE
s.UserId = #userId AND
(#canView IS NULL OR #canView <> s.CanView) AND
(#canRead IS NULL OR #canRead <> s.CanRead) AND
(#canWrite IS NULL OR #canWrite <> s.CanWrite)
END
ELSE BEGIN
INSERT INTO
dbo.Settings(UserId, CanView, CanRead, CanWrite)
SELECT
#userId, #canView, #canRead, #canWrite
END
END
How would you handle data structure like this? Any recommendation or correction would be greatly appreciated. Thanks in advance!
Your SP is a good way to go, and doing it like this is commonly called an "UPSERT".
It also looks to me as if the block:
(#canView IS NULL OR #canView <> s.CanView) AND
(#canRead IS NULL OR #canRead <> s.CanRead) AND
(#canWrite IS NULL OR #canWrite <> s.CanWrite)
is problematic since it causes the UPDATE to run only if ALL parameters changed their value. I don't think that's what you wanted to say. Just SET the three values regardless of what's already there.
You still end up with three places to change when you add a new setting: The Table, the Upsert and the Defaults.
One very different approach is this:
Apply the defaults to the columns in the table definition.
Whenever you need the values for a new user, do: INSERT INTO dbo.Settings(UserId) The defaults will fill the rest of the columns.
Now you can retrieve the values for ALL users (new or not) in the same way from the table.
Since you already inserted the user in step 2, you know the userid is there already and you can always save the changes via a simple update.
This eliminates the SP and the need of providing the defaults in one extra place.

SQL Server won't create trigger on table I can query

The command:
select * from dbo.hmg_cahplots
returns 9250 rows. However, when I try to create a trigger, it fails with:
Msg 8197, Level 16, State 6, Procedure LotUpdateTrigger_tdp, Line 1
The object 'dbo.hmg_cahplots' does not exist or is invalid for this
operation.
Trigger code is:
CREATE TRIGGER dbo.LotUpdateTrigger_tdp ON dbo.hmg_cahplots FOR UPDATE, INSERT
AS
BEGIN
update lot
set lot.hmg_planmodelname = model.hmg_modelname, lot.hmg_thermslotincentive = model.hmg_thermsincentive,
lot.hmg_thermslotincentive_base = model.hmg_thermsincentive_base, lot.hmg_kwlotincentive = model.hmg_kwincentive
from hmg_cahplots as lot inner join i
on lot.hmg_cahplotsid = i.hmg_cahplotsid
inner join hmg_pgecahp as proj
on proj.hmg_pgecahpid = lot.hmg_pgecahplots
left outer join hmg_pgecahpmodels as model
on model.hmg_pgecahpmodelsid = lot.hmg_cahpplanstolotsid
and model.hmg_pgecahpplansid = lot.hmg_pgecahplots
END
I doubt this is very hard to solve. I assume I need to specify a namespace or something. However, I'm new to SQL Server and I don't have any idea how to start on this.
Thanks -- Tim
Are you sure you are located in correct database, not master?
Are you sure your permissions are fine?
Are you sure this is a table, not a view?
If you are sure that this table exists and you are trying to create trigger in the same database, then remove coma just before from and after lot.hmg_kwlotincentive = model.hmg_kwincentive,.
Two problems:
Triggers are mostly Schema-locked.
You are using more than one.
use the same schema and ADD:
-- AT start add the code
USE [DATABASE] --switch with database name
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
--At the END add
END
GO

Stored Procedure with two input params and multiple matches and create view with in

I am not much strong in SQL, so looking for some help.
First I am looking for suggestion for the best way to implement this logic in SQL and then some sample code to implement.
My portal is going to connect Students and Training Providers.
Students: Select what courses (multiple) they want, type of delivery (online, class room), Industry(domain) to which the course to be targeted more, Location Preference.
Training Providers: Select what courses offering (so one record for each course), offering locations, type of delivery for each course, industries (multiple) it is targeting.
When student login:
I would like to create SP which in turn create view to store the matched records of the Training Providers data which matches that student needs of that StudentID, CourseID passed to SP
I have created the following sp ( but not included create view part as I am not sure how to do this)
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_TPsMatched2StuCourse]
-- Add the parameters for the stored procedure here
#StuID int,
#CourseID int
AS
BEGIN
Select TP.MemID,TP.PastExp,SN.DeliveryType,SN.LocPref,SN.Industry,SC.CourseID from
tbl_TrainingProvider as TP , tbl_StuCourses as SC, tbl_StuNeeds SN
where SN.CourseID = #CourseID and SN.StuID = #StuID and
SN.DeliveryType in (TP.DeliveryMode) and
SN.LocPref IN (TP.LocOffering) and
SN.Industry IN (TP.Industries)
END
--- exec sp_ELsMatched2EntProp 1, 1
Why I need to put the data is as follows:
Assume the data is stored in that dynamic view and that would be bind to datagrid. Student then select interested TPs. Then only contact details would be shared to each other and this cannot be reveresed. So I would put this interested data in another table later. Every time data changes, hence the matches. Student can change some of his/her needs or new TPs join etc so view to be temparory.
when I executed this using above command, I am not getting data though it matches few records. What is wrong I am doing.
Any help would be greatly appreciated.
You are not getting expected results because you filter out too many records in WHERE( I'm talking about this part : SN.DeliveryType in (TP.DeliveryMode) and
SN.LocPref IN (TP.LocOffering) and SN.Industry IN (TP.Industries)). I'd recommend to use JOIN ... ON instead of specifying all tables in FROM and join condition in WHERE. I'm not sure what you want exactly, but I believe you are looking for
FROM tbl_StuNeeds SN
LEFT JOIN tbl_TrainingProvider as TP ON (TP.DeliveryMode = SN.DeliveryType AND
SN.LocPref = TP.LocOffering AND TP.Industries = SN.Industry)
WHERE SN.CourseID = #CourseID and SN.StuID = #StuID
Also, there is no join conditions in your code for tbl_StuCourses as SC which results in cross-join.
Finally, why do you need a stored procedure at all? From what I see in your example, a table-valued function will work better:
CREATE FUNCTION [dbo].getTPsMatched2StuCourse(#StuID INT,#CourseID INT)
RETURNS TABLE AS
RETURN
Select .... ;

Resources