Currently I have three stored procedures:
The first will insert a userId and roleId
The second will delete targeted userId and roleId
The third is an update that is supposed to change the bit value of column isShopper
The userId is unique, but can have multiple roles. The roles are "customer", "shopper", and "admin". When I insert the role of "shopper" the column isShopper has a bit value of 1, which is what I want.
However when I delete the role of "shopper" from the user, the bit value remains at 1. I'm curious how I would go about changing the bit value of shopper back to 0 when the shopper role is removed.
Insert procedure:
ALTER proc [dbo].[Asp_Net_User_Role_Insert]
#UserId nvarchar (128),
#RoleId nvarchar (128)
AS
BEGIN
INSERT INTO [dbo].[AspNetUserRoles] ([UserId], [RoleId])
SELECT
#UserId, #RoleId
WHERE
NOT EXISTS(SELECT UserId, RoleId
FROM AspNetUserRoles
WHERE RoleId = #RoleId
AND UserId = #UserId)
EXECUTE dbo.ProfileAccountInfos_Shopper_Update
#UserId, #RoleId
END
Delete procedure:
ALTER proc [dbo].[Asp_Net_User_Role_Delete]
#UserId nvarchar (128),
#RoleId nvarchar (128)
AS
BEGIN
DELETE FROM [dbo].[AspNetUserRoles]
WHERE RoleId = #RoleId
AND UserId = #UserId
EXECUTE dbo.ProfileAccountInfos_Shopper_Update
#UserId, #RoleId
END
Update procedure (tagged on the end of the insert and delete)
ALTER proc dbo.ProfileAccountInfos_Shopper_Update
#IsShopper bit,
#UserId nvarchar (128),
#RoleId nvarchar(128)
AS
BEGIN
IF (#RoleId = '91e67659-0dcb-4171-bc76-78d18b1d1336')
AND #IsShopper = 0
BEGIN
UPDATE [dbo].[ProfileAccountInfos]
SET [DateModified] = getutcdate(),
[IsShopper] = 1
WHERE UserId = #UserId
END
END
In Procedure "ProfileAccountInfos_Shopper_Update" you have parameter #IsShooper but you are not passing value for that parameter when you are executing from "Asp_Net_User_Role_Delete" or "Asp_Net_User_Role_Insert".
If your the RoleId value is going to be the same all the time then you can decide the value of #IsShopper in the Asp_Net_User_Role_Insert and Asp_Net_User_Role_Delete procedures and pass it to the ProfileAccountInfos_Shopper_Update procedure. You don't need #RoleId parameter in ProfileAccountInfos_Shopper_Update Procedure.
So ProfileAccountInfos_Shopper_Update will be simplified as following.
ALTER proc dbo.ProfileAccountInfos_Shopper_Update
,#UserId nvarchar (128)
,#IsShopper BIT
AS
BEGIN
UPDATE [dbo].[ProfileAccountInfos] SET [DateModified] = getutcdate(),[IsShopper] = #IsShopper WHERE UserId = #UserI
END
Procedure Asp_Net_User_Role_Insert will be changed as following.
ALTER proc [dbo].[Asp_Net_User_Role_Insert]
#UserId nvarchar (128),
#RoleId nvarchar (128)
AS
BEGIN
DECLARE #IsShoper BIT
SET #IsShopper = 0
INSERT INTO [dbo].[AspNetUserRoles] ([UserId], [RoleId])
SELECT
#UserId, #RoleId
WHERE
NOT EXISTS(SELECT UserId, RoleId
FROM AspNetUserRoles
WHERE RoleId = #RoleId
AND UserId = #UserId)
IF (#RoleId = '91e67659-0dcb-4171-bc76-78d18b1d1336') -- If role being inserted is Shopper then mark user as a Shopper.
BEGIN
SET #IsShopper = 1
END
EXECUTE dbo.ProfileAccountInfos_Shopper_Update #UserId, #IsShopper
END
Procedure Asp_Net_User_Role_Insert will be changed as following.
ALTER proc [dbo].[Asp_Net_User_Role_Delete]
#UserId nvarchar (128),
#RoleId nvarchar (128)
AS
BEGIN
DECLARE #IsShoper BIT
SET #IsShopper = 1
DELETE FROM [dbo].[AspNetUserRoles] WHERE RoleId = #RoleId AND UserId = #UserId
IF (#RoleId = '91e67659-0dcb-4171-bc76-78d18b1d1336') -- If role being deleted is Shopper then mark user as non-shopper.
BEGIN
SET #IsShopper = 0
END
EXECUTE dbo.ProfileAccountInfos_Shopper_Update #UserId, #IsShopper
END
You can also avoid having ProfileAccountInfos_Shopper_Update stored procedure. You can put the update statement in Asp_Net_User_Role_Insert and Asp_Net_User_Role_Delete
Related
i have a stored procedure in which i have to update the values of Role table and then according to Permission table's Permissionid , i have to Update the third table Roledetail. but what i tried is not working and not updating the table.here is my stored procedure. please help..
i have following table
Role
RoleId int,
RoleName varchar(25),
[Description] varchar(100),
Deleted bit,
CreatedOn datetime,
CreatedBy int,
LastUpdatedOn datetime,
LastUpdatedBy int
Permission
PermissionId int,
PermissionName varchar(25)
RoleDetail
RoleDetailId int,
RoleId int,
PermissionId int
AddedOn datetime,
AddedBy int,
Deleted bit,
DeletedOn datetime,
DeletedBy int
Query
ALTER PROCEDURE usp_UpdateRole
#pRoleId int,
#pRoleName Varchar(25),
#pRoleDescription Varchar(100),
#pAttachedPermission varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Permissions AS VARCHAR(50)
SET #Permissions = #pAttachedPermission
DECLARE #Query AS NVARCHAR (max)
UPDATE [Role]
SET
[Role].[RoleName] = '#pRoleName',
[Role].[Description] = '#pRoleDescription',
[Role].[Deleted] = 0,
[Role].[CreatedOn]= GetDate(),
[Role].[CreatedBy] = 1,
[Role].[LastUpdatedOn] = NULL,
[Role].[LastUpdatedBy]=NULL
where [Role].RoleId = #pRoleId
DECLARE #RoleId AS INT
SET #RoleId = Scope_Identity()
SET #Query = 'SELECT '+
CAST(#RoleId as Varchar(10))+' AS RoleId,
PermissionId AS PermissionId,
GETDATE() AS AddedOn,
1 As AddeBy,
0 AS Deleted,
NULL As DeletedOn,
NULL AS DeletedBy
FROM
[Permission]
WHERE
PermissionId in ('+#Permissions+')'
SET #Query = 'INSERT INTO [RoleDetail] '+ #Query
exec sp_ExecuteSQL #Query
END
You are trying to use scope_identity with update statement.
Your RoleId will be last ID inserted in your scope, not ID of your updated record.
So basically you need to change the way how you get your role id.
But in general, i would rewrite your proc as follows
UPDATE [Role]
SET
....
where [Role].RoleId = #pRoleId
INSERT INTO [RoleDetail] (RoleId, PermissionId, AddedOn, AddedBy, Deleted )
SELECT #pRoleId, PermissionId, getdate(), 1, 0
FROM [Permission]
WHERE
PermissionId in (select id from fn_parseIntList(#Permissions))
Where parseIntList is function which converts string list into table data of int. There are a lot of examples how to implement this function
e.g.
Converting String List into Int List in SQL
UPD:
After getting table structure - you need to insert only 2 columns, assuming RoleDetailId is identity
INSERT INTO [RoleDetail] (RoleId, PermissionId)
SELECT #pRoleId, PermissionId
FROM [Permission]
WHERE
PermissionId in (select id from fn_parseIntList(#Permissions))
with some changes suggested by # fly_ua
its working as charm .
ALTER PROCEDURE usp_UpdateRole
#pRoleId int,
#pRoleName Varchar(25),
#pRoleDescription Varchar(100),
#pAttachedPermission varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Permissions AS VARCHAR(50)
SET #Permissions = #pAttachedPermission
DECLARE #Query AS NVARCHAR (max)
UPDATE [Role]
SET
[Role].[RoleName] = #pRoleName,
[Role].[Description] = #pRoleDescription,
[Role].[Deleted] = 0,
[Role].[CreatedOn]= GetDate(),
[Role].[CreatedBy] = 1,
[Role].[LastUpdatedOn] = NULL,
[Role].[LastUpdatedBy]=NULL
where [Role].RoleId = #pRoleId
SET #Query = 'SELECT '+
CAST(#pRoleId as Varchar(10))+' AS RoleId,
PermissionId AS PermissionId,
GETDATE() AS AddedOn,
1 As AddeBy,
0 AS Deleted,
NULL As DeletedOn,
NULL AS DeletedBy
FROM
[Permission]
WHERE
PermissionId in ('+#Permissions+')'
SET #Query = 'INSERT INTO [RoleDetail] '+ #Query
exec sp_ExecuteSQL #Query
END
I have a stored procedure that I am passing a string of documentIDs to and its supposed to delete the documentID where its roleID = #roleID, but instead it is deleting all the records based on the roleID and all I want to do is delete the documentIDs from the table based on the roleID
My sp is
ALTER PROCEDURE sp_RemoveDocumentIDsFromRole
(
#roleID int,
#group_name varchar(50),
#DocumentIDString varchar(500),
#group_display_name varchar(50)
)
as
UPDATE [master_groups]
set group_name = #group_name, #group_display_name = #group_display_name
where roleID = #roleID
-- Creating Variables
DECLARE #numberLength int
DECLARE #numberCount int
DECLARE #TheDocumentIDs VarChar(500)
DECLARE #sTemp VarChar(100) -- to hold single characters
-- Creating a temp table
DECLARE #T TABLE
(
TheDocumentIDs VarChar(500)
)
--Initializing Variables for counting
SET #numberLength = LEN (#DocumentIDString)
SET #numberCount = 1
SET #TheDocumentIDs = ''
--Start looping through the keyword ids
WHILE (#numberCount <= #numberLength)
BEGIN
SET #sTemp = SUBSTRING (#DocumentIDString, #numberCount, 1)
IF (#sTemp = ',')
BEGIN
INSERT #T(TheDocumentIDs) VALUES (#TheDocumentIDs)
SET #TheDocumentIDs = ''
END
IF (#sTemp <> ',')
BEGIN
SET #TheDocumentIDs = #TheDocumentIDs + #sTemp
END
SET #numberCount = #numberCount + 1
END
declare #rLevel int
set #rLevel = 0
delete from [master_group_document_relations] where exists(select documentID = #TheDocumentIDs from #T) and roleID = #roleID
UPDATE: SAMPLE DATA
Not sure what you get in #TheDocumentIDs, but I suppose it should work for you.
First of all as #Chris mentioned, you should check put condition to where clause and as documentIds is a list of id's it should used with IN condition and not equality, that's why you need or use sp_executesql or fill id's to a temp table.
EXECUTE sp_executesql
N'delete from [master_group_document_relations]
where documentID IN (#TheDocumentIDs)
and roleID = #roleID',
N'#TheDocumentIDs varchar(500), #roleID int',
#DocumentIDString,
#roleID;
Or try this
delete from [master_group_document_relations]
where documentID IN (SELECT TheDocumentIDs FROM #T)
and roleID = #roleID
If you want to delete records of master_group_document_relations where its documentID is in TheDocumentIDs of #t and roleID is equal to #roleID then try this :
DELETE T1
FROM [master_group_document_relations] AS T1
WHERE EXISTS(SELECT 1
FROM #T AS T2
WHERE T1.documentID = T2.TheDocumentIDs)
AND roleID = #roleID
I'm having a bit of difficulty with this one in that I'm not sure how to do this in SQL Server.
Basically, I want to insert a new row into the database, get the PK value that was just inserted (same query) and output it back to whatever called the stored procedure:
CREATE PROCEDURE Users_Insert
-- Add the parameters for the stored procedure here
#userid int output,
#name varchar(50),
#surname varchar(50),
#email varchar(200),
#password varchar(50),
#location varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
insert into Users(FirstName, LastName, Email, Password, Location)
values(#name, #surname, #email, #password, #location);
GO
#userid = ##IDENTITY;
END
I've done this in MySQL as follows:
CREATE PROCEDURE Users_Insert(#userid int output, #name varchar(50), #surname varchar(50), #email varchar(200), #password varchar(50), #location varchar(50)
BEGIN
insert into Users(FirstName, LastName, Email, Password, Location)
values(#name, #surname, #email, #password, #location);
set #userid = last_insert_id();
END
SQL Server gives me an error:
Msg 102, Level 15, State 1, Procedure Users_Insert, Line 18
Incorrect syntax near '#userid'.
Frankly, I'm not sure I declared the output parameter correctly, can anyone offer suggestions?
You need to assign the value to #userid ! Also, I would recommend using SCOPE_IDENTITY() and not ##IDENTITY :
CREATE PROCEDURE Users_Insert
-- Add the parameters for the stored procedure here
#userid int output,
#name varchar(50),
#surname varchar(50),
#email varchar(200),
#password varchar(50),
#location varchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
insert into Users(FirstName, LastName, Email, Password, Location)
values(#name, #surname, #email, #password, #location);
-- make an actual **assignment** here...
SELECT #userid = SCOPE_IDENTITY();
END
See this blog post for an explanation as to WHY you should use SCOPE_IDENTITY over ##IDENTITY
I have a table "tbluser" with 2 fields:
userid = integer (autoincrement)
user = nvarchar(100)
I have a multithreaded/multi server application that uses this table.
I want to accomplish the following:
Guarantee that field user is unique in my table
Guarantee that combination userid/user is unique in each server's memory
I have the following stored procedure:
CREATE PROCEDURE uniqueuser #user nvarchar(100) AS
BEGIN
BEGIN TRAN
DECLARE #userID int
SET nocount ON
SET #userID = (SELECT #userID FROM tbluser WITH (TABLOCKX) WHERE [user] = #user)
IF #userID <> ''
BEGIN
SELECT userID = #userID
END
ELSE
BEGIN
INSERT INTO tbluser([user]) VALUES (#user)
SELECT userID = SCOPE_IDENTITY()
END
COMMIT TRAN
END
Basically the application calls the stored procedure and provides a username as parameter.
The stored procedure either gets the userid or insert the user if it is a new user.
Am I correct to assume that the table is locked (only one server can insert/query)?
I'm sure the following advice might help someone in the future.
Instead of (TABLOCKX), try (TABLOCKX, HOLDLOCK)
or even better, if this is the only procedure in which writing to tbluser takes place, you can cut down on a whole bunch of code entirely using only TABLOCKX and no transaction via
CREATE PROCEDURE uniqueuser #user nvarchar(100)
AS
--BEGIN TRAN NOT NECESSARY!
INSERT tbluser WITH (TABLOCKX) ([user])
SELECT #user
WHERE NOT EXISTS(SELECT 1 FROM tbluser WHERE [user]=#user)
--COMMIT TRAN NOT NECESSARY!
SELECT userID FROM tbluser WHERE [user]=#user
This way, the insert statement (which automatically creates a transaction for the duration of the INSERT) is the only time you need the table lock. (Yes, I stress-tested this on two windows both with and without TABLOCKX to see how it faired before posting my message.)
If you want to guarantee that the user is unique, the only way is to a unique constraint
ALTER TABLE tbluser WITH CHECK
ADD CONSTRAINT UQ_tbluser_user UQNIUE (user);
Do not "roll your own" unique checks: it will fail.
The cached data in the server's memory conforms to the same constraint
I'd do this. Look for user first, if not found insert, handle unique error just in case. And I'd use an OUTPUT parameter
CREATE PROCEDURE uniqueuser
#user nvarchar(100)
-- ,#userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE #userID int;
BEGIN TRY
SELECT #userID FROM tbluser WHERE [user] = #user;
IF #userID IS NULL
BEGIN
INSERT INTO tbluser([user]) VALUES (#user);
SELECT userID = SCOPE_IDENTITY() ;
END
END TRY
BEGIN CATCH
-- test for a concurrent call that just inserted before we did
IF ERROR_NUMBER() = 2627
SELECT #userID FROM tbluser WHERE [user] = #user;
ELSE
-- do some error handling
END CATCH
-- I prefer output parameter for this SELECT #userID AS UserID
GO
Edit: why TABLOCKX fails...
You only lock the table for the duration of the SELECT.
A 2nd process running concurrently will starting reading the table after the lock is released by process 1
Both processes can have #userID IS NULL because process 1 has not yet executed the INSERT
Process 2 gets an error when it INSERTS
This happens because TABLOCKX modifies lock isolation and granularity, not duration.
Edit 2: for SQL Server 2000
CREATE PROCEDURE uniqueuser
#user nvarchar(100)
-- ,#userid int = NULL OUTPUT
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE #userID int;
SELECT #userID FROM tbluser WHERE [user] = #user;
IF #userID IS NULL
BEGIN
INSERT INTO tbluser([user]) VALUES (#user);
IF ##ERROR = 2627
SELECT #userID FROM tbluser WHERE [user] = #user;
ELSE
RAISERROR ('the fan needs cleaning', 16, 1);
SELECT userID = SCOPE_IDENTITY();
END
GO
Can I return UNIQUEIDENTIFIER from a stored procedure using the RETURN statement or is it only by using the OUTPUT statement?
i.e to return the PersonID UNIQUEIDENTIFIER:
CREATE PROCEDURE CreatePerson
#Name NVARCHAR(255),
#Desc TEXT
AS
DECLARE #Count INT
DECLARE #JobFileGUID UNIQUEIDENTIFIER
-- Check if job exists?
SET #Count = (SELECT COUNT(Name) AS Name FROM Person WHERE Name=#Name)
IF #Count < 1
BEGIN
SET #PersonGUID = NEWID();
INSERT INTO Person
(PersonID, Name, [Desc])
VALUES (#PersonGUID, #Name, #Desc)
END
SELECT #PersonGUID = Person.PersonID
FROM Person
WHERE Name = #Name
RETURN #PersonGUID
GO
Thanks
In stored procedure - only using the OUTPUT statement. In function - return.
Use:
CREATE PROCEDURE CreatePerson
#Name NVARCHAR(255),
#Desc TEXT,
#PersonGUID UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
SET #PersonGUID = ...
END
How to call:
DECLARE
#name NVARCHAR(255),
#desc TEXT,
#personGUID UNIQUEIDENTIFIER
SET #name = 'Bob'
SET #desc = 'One handsome man.'
EXEC [Database].[schema].CreatePerson #name, #desc, #personGUID OUTPUT
From the documentation you can actually see that a return in a stored procedure is actually used as a response code, hence you get the exception when trying to return a uniqueidentifier.
https://learn.microsoft.com/en-us/sql/relational-databases/stored-procedures/return-data-from-a-stored-procedure?view=sql-server-ver16#return-data-using-a-return-code
How I solved it, is by just performing a SELECT after the insert of the generated unique identifier.
DECLARE #ReportId UNIQUEIDENTIFIER;
SET #ReportId = NEWID();
INSERT INTO [dbo].[Report]
([ReportId]
,[ReportName])
VALUES
(#ReportId
,#ReportName)
SELECT #ReportId as ReportIdInternal
You'll have to see how to perform that with multiple selects though.
CREATE TABLE [dbo].[tbl_Clients]( [ClientID] [uniqueidentifier] NULL, [ClientName] varchar NULL, [ClientEnabled] [bit] NULL ) ON [PRIMARY]
GO
CREATE PROCEDURE [dbo].[sp_ClientCreate] #in_ClientName varchar(250) = "New Client 123", #in_ClientEnabled bit, #out_ClientId uniqueidentifier OUTPUT AS
SET #out_ClientId = NEWID();
INSERT INTO tbl_Clients(ClientId, ClientName, ClientEnabled) VALUES( #out_ClientId, #in_ClientName, #in_ClientEnabled)
DECLARE #return_value int, #out_ClientId uniqueidentifier
EXEC #return_value = [dbo].[sp_ClientCreate] #in_ClientName = N'111', #in_ClientEnabled = 1, #out_ClientId = #out_ClientId OUTPUT
SELECT #out_ClientId as N'#out_ClientId'
SELECT 'Return Value' = #return_value
GO
Result:-59A6D7FE-8C9A-4ED3-8FC6-31A989CCC8DB