Stored Procedure changing where clause statements - c#

I have a question regarding sql statements. Is it possible to make a query this way? I need to filter a where clause that depends what is the value of #user_id.
ALTER PROCEDURE [dbo].[getUserAddress]
#user_id int = null
AS
SELECT user_id, state_code, address from UserAddress
IF(#user_id > 10)
BEGIN
WHERE state_code = 'CA'
END
ELSE
WHERE address = 'HILLS'
END
I tried to do this way but It seems not a standard way.
IF(#user_id > 10)
BEGIN
SELECT user_id, state_code, address from UserAddress WHERE state_code = 'CA'
END
ELSE
SELECT user_id, state_code, address from UserAddress WHERE address = 'HILLS'
END
Please advise what is the best approach for this concept. Please help.
Thanks

You are missing a begin after your else. Try this:
IF(#user_id > 10)
BEGIN
SELECT user_id, state_code, address from UserAddress WHERE state_code = 'CA'
END
ELSE
BEGIN
SELECT user_id, state_code, address from UserAddress WHERE address = 'HILLS'
END

You can simplify:
select
user_id,
state_code,
address
from
UserAddress
where
(#user_id > 10 and state_code = 'CA') or
(#user_id IS NULL OR (#user_id <= 10 and address = 'HILLS'))
If you will add more filter criteria in the future you should be aware of parameter sniffing.
As an aside, creating an index on (user_id, state_code, address) will be a covering index for this query.

You can also use a dynamic sql query in the stored procedure.
Query
declare #sql as varchar(max);
select #sql = 'select * from [UserAddress] '
+ stuff((select case when #user_d > 10
then 'where state_code= ''CA'''
else 'where address = ''Hills''' end
for xml path('')), 1,0,'')
exec(#sql);

rather then this use can use case in your where statement:
SELECT user_id, state_code, address from UserAddress WHERE
(case when #user_id > 10 then state_code else address end)
= (case when #user_id > 10 then 'CA' else 'HILLS' end)

Try this
SELECT user_id, state_code, address from UserAddress
WHERE
((WHERE state_code = 'CA') AND (#user_id > 10))
OR
((WHERE address = 'HILLS') AND (#user_id <= 10))

You can simply...
WHERE
(#user_id > 10 AND state_code = 'CA')
OR ((#user_id <= 10 OR #user_id IS NULL) AND address = 'HILLS')
However, this approach may not produce the best query plan. If that turns out to be a problem, either use separate queries (as you already tried, but fix the syntax error as suggested by AsheraH), or dynamic SQL.
BTW, you can write separate queries more succinctly like this:
IF #user_id > 10
SELECT user_id, state_code, address from UserAddress WHERE state_code = 'CA'
ELSE
SELECT user_id, state_code, address from UserAddress WHERE address = 'HILLS'

You can try this:
ALTER PROCEDURE [dbo].[getUserAddress]
#user_id int = null
AS
BEGIN
Declare #sql nvarchar(5000)
set #sql='SELECT user_id, state_code, address from UserAddress'
IF(#user_id > 10)
BEGIN
set #sql=#sql+' WHERE state_code = ''CA'''
END
ELSE
set #sql=#sql+' WHERE address = ''HILLS'''
END
Exec(#sql)
END

Related

Table valued parameter has wrong data

I have ASP .NET Web Form application with SignalR handling online notifications. Somewhere down the road of simplifying the code, I ran into an issue when stored procedure accepting whole table of connected clients as one of the input parameters for some reason inserts into result table less rows than when I just put a breakline into c# code before the query runs and launch it SQL-side myself.
It's really complicated code now spanning over multiple c# methods and sql queries, so I just want to know, why could this be an issue. I'm providing with small piece of code, that will hopefuly give you guys some ideas, cause I fresh ran out of mine, I feel like I tried everything by now to no avail.
Here is the SQL function dbo.UpdateAffectedClients I'm using in procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
RETURNS
#Output TABLE
(
connectionId varchar(512),
clientId int,
numberOfNotifications int,
isPatient bit
)
AS
BEGIN
--ADDING AFFECTED CLIENTS TO OUTPUT
--med/Instrument/Order
IF(#typeId = 1)
BEGIN
--Adding doctors and patients
INSERT INTO #Output (connectionId, clientId, numberOfNotifications, isPatient)
SELECT cc.connectionId, cc.clientId, COALESCE(COUNT(n.id), 0), cc.isPatient
FROM MedOrders mo
LEFT JOIN User2ExamRoom uer on mo.examRoomId = uer.examRoomId
JOIN #ConnectedClients cc on (uer.userId = cc.clientId AND cc.isPatient = 0) OR (mo.patientId = cc.clientId AND cc.isPatient = 1)
LEFT JOIN Notifications n on n.clientId = cc.clientId AND n.isPatient = cc.isPatient
WHERE mo.id = #tableId
GROUP BY cc.isPatient, cc.clientId, cc.connectionId
END
RETURN
END
and here is the procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[HandleNotificationsDBUpdate]
#ConnectedClients as SignalRConnectedClients READONLY,
#tableId int,
#typeId int
AS
BEGIN
SET NOCOUNT ON;
--QUERY
DECLARE
#affectedClients SignalRConnectedClients,
#orderId int = -1,
#meetingId int = -1,
#messageId int = -1,
#processed bit,
#cancelledByPatient bit,
#examRoomId int,
#affectedPatientId int,
#errorMessage varchar(MAX) = '',
#written bit,
#tracing varchar(2048) = ''
--Whatever happened, the row got deleted from the table in the process and info is no longer available.
IF NOT EXISTS(SELECT TOP 1* FROM MedOrders WHERE id = #tableId)
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Row no longer exists in the original table. '
--Insert to affected clients - prior to deleting from notifications, I'd lose the reference afterwards
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT cc.connectionId, cc.clientId, COUNT(n.id), cc.isPatient
FROM #ConnectedClients cc
JOIN Notifications n on cc.clientId = n.clientId AND cc.isPatient = n.isPatient
WHERE n.notificationTypeId = #typeId AND n.tableId = #tableId
GROUP BY cc.isPatient, cc.clientId, cc.connectionId
--Then delete notifications linked to it
DELETE FROM Notifications WHERE tableId = #tableId AND notificationTypeId = #typeId
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, "Whatever happened, the row got deleted from the table in the process and info is no longer available" section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--row is still there
ELSE
BEGIN
SET #tracing = #tracing + 'Row still exists in the original table. '
--MED ORDER
IF (#typeId = 1)
BEGIN
SET #tracing = #tracing + 'Its a medOrder, '
SET #orderId = #tableId
SET #examRoomId = (SELECT examRoomId FROM MedOrders WHERE id = #orderId)
SET #processed = (SELECT CASE WHEN processed IS NULL THEN 0 ELSE 1 END FROM MedOrders WHERE id = #orderId)
SET #cancelledByPatient = (SELECT CASE WHEN cancelledByPatient IS NULL THEN 0 ELSE 1 END FROM MedOrders WHERE id = #orderId)
--not processed
IF (#processed = 0)
--PATIENT POSTED OR UPDATED ORDER
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'not processed. '
--it's not already there (meaning patient didn't just update something)
IF NOT EXISTS (SELECT TOP 1 id FROM Notifications WHERE notificationTypeId = #typeId AND tableId = #tableId)
BEGIN
SET #tracing = #tracing + 'Patient is creating an order. '
--Insert row to notifications for each doctor (user) having this examRoom assigned
INSERT INTO Notifications (tableId, notificationTypeId, clientId, isPatient)
SELECT #tableId, #typeId, uer.userId, 0
FROM User2ExamRoom uer
WHERE uer.examRoomId = #examRoomId
--FUNCTION - insert into affectedClients table affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, PATIENT POSTED OR UPDATED ORDER section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--processed
ELSE
BEGIN
SET #tracing = #tracing + 'processed. '
--deleting from notifications cause its been processed
DELETE FROM Notifications
WHERE tableId = #tableId AND notificationTypeId = #typeId
--cancelled by patient
IF (#cancelledByPatient = 1)
--PATIENT REMOVED IT BEFORE APPROVAL
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Patient cancelled the order. '
--FUNCTION - insert into affectedCLients table affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, PATIENT REMOVED IT BEFORE APPROVAL section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--not cancelled by patient
ELSE
BEGIN
--DOCTOR PROCESSED ORDER
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Doctor processed the order. '
SET #affectedPatientId = (SELECT patientId FROM MedOrders WHERE id = #orderId)
--Insert row to notifications table for the patient that placed this order
INSERT INTO Notifications (tableId, notificationTypeId, clientId, isPatient)
VALUES (#tableId, #typeId, #affectedPatientId, 1)
--FUNCTION - Add affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, DOCTOR PROCESSED ORDER section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
END
END
END
END
--Final selects
SET #written = CASE WHEN LEN(#errorMessage) = 0 THEN 1 ELSE 0 END
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM #affectedClients
SELECT #written as written, #errorMessage as errorMessage, #tracing as tracing
END
Patient posted order section is just fine, Doctor processed order doesn't return any patients, it only returns doctors in affectedClients result. As if it somehow failed JOIN or something (I used to run it in two insert statements, but managed to turn it into single one to see if that could be the issue).
C# method:
/// <summary>
/// Handles any changes in notifications SQL side provided typeId (see NotificationTypes Table in SQL or comment in header of this function) and id of the updated row in respective table (tableId - could stand for orderId, messageId, taskId, etc.)
/// </summary>
/// <param name="typeId"></param>
/// <param name="tableId"></param>
public static Obj_NotificationDBUpdateResult HandleNotificationsDBUpdate(int typeId, int tableId)
{
//typeIds:
//1: medOrder
//2: instrumentOrder
//3: meeting
//4: message
//5: task
Obj_NotificationDBUpdateResult output = new Obj_NotificationDBUpdateResult();
List<Obj_SignalRClientsListEntry> ConnectedClients = NotificationsHub.ConnectedClients;
//preparing ConnectedClients as parametr to pass into stored procedure so I can easily link connectionId with clientId
DataTable ConnectedClientsDt = new DataTable();
ConnectedClientsDt.Columns.Add("connectionId", typeof(string));
ConnectedClientsDt.Columns.Add("clientId", typeof(Int32));
ConnectedClientsDt.Columns.Add("numberOfNotifications", typeof(Int32));
ConnectedClientsDt.Columns.Add("isPatient", typeof(bool));
foreach(Obj_SignalRClientsListEntry item in ConnectedClients)
{
DataRow dr = ConnectedClientsDt.NewRow();
dr["connectionId"] = item.connectionId;
dr["clientId"] = item.clientId;
dr["numberOfNotifications"] = item.numberOfNotifications;
dr["isPatient"] = item.isPatient;
ConnectedClientsDt.Rows.Add(dr);
}
//Query
SqlConnection conn = new SqlConnection(Data.connStr);
try
{
conn.Open();
using (SqlCommand comm = new SqlCommand("HandleNotificationsDBUpdate", conn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ConnectedClients", ConnectedClientsDt).SqlDbType = SqlDbType.Structured;
comm.Parameters.AddWithValue("#typeId", typeId);
comm.Parameters.AddWithValue("#tableId", tableId);
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
output.affectedClients.Add(new Obj_SignalRClientsListEntry()
{
connectionId = reader["connectionId"].ToString(),
clientId = Convert.ToInt32(reader["clientId"]),
isPatient = Convert.ToBoolean(reader["isPatient"]),
numberOfNotifications = Convert.ToInt32(reader["numberOfNotifications"]),
});
}
reader.NextResult();
while (reader.Read())
{
output.written = Convert.ToBoolean(reader["written"]);
output.errorMessage = reader["errorMessage"].ToString();
output.tracing = reader["tracing"].ToString();
}
}
}
}
catch (Exception ex)
{
throw (ex);
}
finally
{
conn.Close();
}
//joining the hub, providing context
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>();
//looping through each client whom we should notify
foreach (Obj_SignalRClientsListEntry item in output.affectedClients)
{
//calling clients refresh function providng this number as an input
context.Clients.Client(item.connectionId).clientRefreshNotificationsCount(item.numberOfNotifications);
}
return output;
}
When I check (right before hubContext line) the output.affectedClients, it only shows doctors. But when I put a breakline before the SQLConnection line and run the body of the procedure provided same input as came to the c# method (see debug section at the beginning in the procedure), it returns doctors and patients just fine.
I tried switching some inserts in the procedure, putting stuff in the function directly into procedure, omitting function completely, tried to comment everything but the part with inserting into affectedClients, fiddle with ansi_nulls and those up there, tried to put them before query when I run it solo, tried to remove them from the procedure before altering, nothing.
I even put the tracing output to see exactly where it went in the procedure and it all seems legit, I have no idea why would it return just doctors.
Other than that - it's pretty neatly commented, so it should give you some idea of what's going on behind the curtain, but If you need to ask, ask, I'll try my best to answer. Thanks in advance.
UPDATE
So I narrowed the problem down to this
SELECT cc.connectionId, cc.clientId, 100, cc.isPatient
FROM #ConnectedClients cc
JOIN MedOrders mo on (mo.patientId = cc.clientId AND cc.isPatient = 1)
WHERE mo.id = #tableId
that cc.isPatient = 1 just doesn't work. For some unknown reason it doesn't recognize that 1 in #ConnectedClients (bit) as bit value. Any ideas?
Ok, so I figured it out... apparently - order of columns in the SQL User defined Table Type matters. I had last two columns switched in SQL table type and when the DataTable object arrived to the procedure, it disregarded column names from C# and just took those columns from left to right and started to fill the SQL table in the same order, therefore inserting numberOfNotifications to isPatient and vice versa. Now since you cannot have value bigger than 1 in a bit column, it treated it as 0 and the query kept failing JOINs and other stuff...
I would have never guessed this to be an issue. Why do I even bother naming columns in DataTable then?

How to return filtered rows using SQL Cursors

I have been looking at examples of cursors in stored procedures and all of them seem to be either printing to the console or manipulating data in the main loop.
My situation is as follows:
Create a stored procedure that returns filtered rows from a single table.
Create a cursor for a simple SELECT statement without a WHERE clause.
Iterate through the fetch cycle.
Return only rows that meet certain criteria.
Pseudocode (C#, EntityFrameworkCore):
var name = "xyz";
var email = "";
var users = new List<User>();
foreach (var user in context.Users.AsQueryable())
{
var added = false;
if (!added)
{
if ((!string.IsNullOrWhitespace(name)) && (user.Name.Contains(name)))
{
users.Add(user);
}
}
if (!added)
{
if ((!string.IsNullOrWhitespace(email)) && (user.Name.Contains(email)))
{
users.Add(user);
}
}
}
These conditions can be complex enough to not be able to fit in a WHERE clause. Hence looking at cursors.
Just not sure how to think about this. A cursor is an iterator but how do you accumulate filtered rows to return?
Any advice would be appreciated.
Here is the stored procedure I would like to use cursors in:
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #UseName BIT
DECLARE #UseEmail BIT
IF ((#Name IS NULL) OR (LEN(#Name) = 0)) SET #UseName = 0 ELSE SET #UseName = 1
IF ((#Email IS NULL) OR (LEN(#Email) = 0)) SET #UseEmail = 0 ELSE SET #UseEmail = 1
IF (#Condition = 0)
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) OR ([Name] LIKE '%' + #Name + '%'))
AND
((#UseEmail = 1) OR ([Email] LIKE '%' + #Email + '%'))
ELSE
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) OR ([Name] LIKE '%' + #Name + '%'))
OR
((#UseEmail = 1) OR ([Email] LIKE '%' + #Email + '%'))
RETURN (##ROWCOUNT)
END
This type of query is called a Kitchen Sink query.
This can have issues due to parameter sniffing. So the best way to do this is not to use a cursor, but to dynamically build the conditions, and execute it using sp_executesql.
Note how the actual values are not concatenated in, but are also passed through as parameters to the dynamic side.
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql nvarchar(max) = N'
SELECT [Id], [Name], [Email]
FROM [User]
WHERE 1=1 AND (
';
DECLARE #conditions nvarchar(max);
IF NULLIF(#Name, '') IS NOT NULL
SET #conditions = '([Name] LIKE ''%'' + #Name + ''%'')' + '
';
IF NULLIF(#Email, '') IS NOT NULL
SET #conditions = CONCAT(
#conditions + CASE WHEN #Condition = 0 THEN 'AND ' ELSE 'OR ' END + '
',
'([Email] LIKE ''%'' + #Email + ''%'')
');
SET #sql += #conditions + '
);
RETURN (##ROWCOUNT);
';
PRINT #sql; --for testing
DECLARE #rc int;
EXEC #rc = sp_executesql
#sql,
N'#Name NVARCHAR(100), #Email NVARCHAR(100)',
#Name = #Name,
#Email = #Email;
RETURN #rc;
END
I agree with the others that a cursor sounds like a really bad fit for your use case. But to answer your question, you would first create a temp table, then the cursor could insert any matching rows to the temp table as it looks through the data. At the end, select all rows from the temp table.

Retrieve multiple data from stored procedure using Linq-to-SQL

I am using Silverlight and Linq-to-SQL to communicate with the database.
I have a stored procedure which receives 2 parameters (PFOID and Quantity) and Userid and returns a product name.
If we send multiple values like multiple pfoid's and quantity's it will return multiple product names shown as below
The stored procedure looks like this..
ALTER PROCEDURE [PFO].[PFOValidateUpdateData]
#PfoIDs xml, -- list of PFO ID's
#UserID uniqueidentifier --The Identity of the User making the call.
AS
BEGIN
-- SET DEFAULT BEHAVIOR
SET NOCOUNT ON -- Performance: stops rows affected messages
SET DEADLOCK_PRIORITY LOW -- This SP to be the Deadlock victim
-- Initialise Lock-Timeout and Deadlock vars for Insert
DECLARE #iLockTimeoutRetries as int
DECLARE #iDeadLockRetries as int
DECLARE #dtLockTimeoutSleepInterval as datetime
DECLARE #dtDeadlockSleepInterval as datetime
DECLARE #iErrorNumber as int
SET #iLockTimeoutRetries = 0
SET #iDeadLockRetries = 0
SET #dtLockTimeoutSleepInterval = sCommon.fnLockTimeoutSleepInterval()
SET #dtDeadlockSleepInterval= sCommon.fnDeadlockSleepInterval()
SET #iErrorNumber = 0
-- procedure specific
DECLARE #idoc as int
DECLARE #IsBrightstarUser as bit
RETRY:
BEGIN TRY
--Create Temp table to store stores!
CREATE TABLE [#PFOList]
(
[PFOId] nvarchar(50),
[Quantity] INT
)
--Create Temp table to store User stores!
CREATE TABLE [#UserStoreList]
(
[StoreID_XRef] nvarchar(50)
)
print CONVERT(varchar(1000), #PfoIDs)
--Create Document
EXEC sp_xml_preparedocument #idoc OUTPUT, #PfoIDs
-- Append to new list of Store records
INSERT INTO [#PFOList] (
[PFOId],
[Quantity]
)
SELECT [PFOID],[Quantity]
FROM OPENXML (#idoc, 'ArrayOfString/string',2)
WITH( [PFOID] nvarchar(50),[Quantity] [INT]) Stores
--WHERE [PFOId] Is Not NULL
-- Clean UP
exec sp_xml_removedocument #iDoc
-- are we dealing with a brightstar user?
SET #IsBrightstarUser = CASE WHEN exists
(SELECT *
FROM dbo.aspnet_UsersInRoles AS uir inner join
dbo.aspnet_Roles AS roles ON uir.RoleId = roles.roleid
WHERE roles.rolename = 'Brightstar Employee' and uir.userid = #userid)
THEN 1 ELSE 0 END
--Get User Storelist
INSERT INTO [#UserStoreList] (
[StoreID_XRef]
)
SELECT s.StoreId_XRef
FROM PFO.UserStoreLink us(nolock)
INNER JOIN PFO.Store s(nolock)
ON us.StoreId=s.StoreId
where UserId=#UserID
--Select * from [#PFOList]
--SELECT #IsBrightstarUser AS ISBrightstaruser
--SELECT * from [#UserStoreList]
--If BrightstarCustomer Update all the Quantities.
IF #IsBrightstarUser=1
BEGIN
UPDATE
PFO.PFO
SET
IsBrightstarReviewComplete = 1
,[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
,[BrightstarReviewedQty]=pfol.[Quantity]
FROM
PFO.PFO as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN [#PFOList] as pfol on pfo.PFOId = pfol.PFOId
WHERE #IsBrightstarUser = 1
END
ELSE BEGIN
--Update Non Contrained Orders
UPDATE
PFO.PFO
SET
[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
FROM
PFO.PFO (nolock) as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN [#PFOList] as pfol on pfo.PFOId = pfol.PFOId
WHERE pfo.IsBrightstarReviewComplete=1 AND IsConstraint=0
--SELECT * from PFO.PFO (nolock) where PFOId='04676723-2afb-49ff-9fa1-0131cabb407c'
--Update Contrained Orders
--Get Existing quantities for the User
CREATE TABLE #ExistingProductQuantity
(
[PfoID] nvarchar(100)
,[Product] nvarchar(255)
,[PlannedQty] INT
,[BrightstarReviewedQty] INT
)
CREATE TABLE #CustProductQuantity
(
[Product] nvarchar(255)
,[IsUpdatable] BIT
)
INSERT INTO #ExistingProductQuantity
( [PfoID],[Product],[PlannedQty],[BrightstarReviewedQty])
SELECT PFOId,InventoryId,PlannedQty,BrightstarReviewedQty
FROM PFO.PFO as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
WHERE pfo.IsBrightstarReviewComplete=1 AND IsConstraint=1
UPDATE
#ExistingProductQuantity
SET [PlannedQty]=pfol.[Quantity]
FROM #ExistingProductQuantity eoq
INNER JOIN [#PFOList] as pfol on eoq.PFOId = pfol.PFOId
INSERT INTO #CustProductQuantity
( [Product],[IsUpdatable] )
SELECT
[Product],
CASE WHEN SUM(PlannedQty)<=SUM(BrightstarReviewedQty) THEN 1 ELSE 0 END
FROM #ExistingProductQuantity
GROUP BY [Product]
--SELECT * from #ExistingProductQuantity
--SELECT * from #CustProductQuantity
--Update the products that can be updatable
UPDATE
PFO.PFO
SET
[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
FROM
PFO.PFO as pfo
INNER JOIN #UserStoreList as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN #PFOList as pfol on pfo.PFOId = pfol.PFOId
INNER JOIN #CustProductQuantity as pr on pr.Product=pfo.InventoryId
WHERE pfo.IsBrightstarReviewComplete=1 AND pr.IsUpdatable=1 AND IsConstraint=1
--Return the products that are not updatabele
select [Product]
FROM #CustProductQuantity
where [IsUpdatable]=0
END
END TRY
BEGIN CATCH
-- Get the ErrorNumber
Set #iErrorNumber = ERROR_NUMBER()
--Handle Deadlock situation (Deletes, Inserts & Updates)
IF #iErrorNumber = 1205
BEGIN
-- If we have not made enough attempts to break the lock
IF #iDeadLockRetries < sCommon.fnMaxDeadlockRetries()
BEGIN
-- Increment the Attempt count
SET #iDeadLockRetries = #iDeadLockRetries + 1
-- Pause to allow the deadlock contention to clear
WAITFOR DELAY #dtDeadlockSleepInterval
GOTO RETRY
END
END
-- Handle Lock Timeout situation (Deletes, Inserts & Updates)
IF #iErrorNumber = 1222
BEGIN
-- If we have not made enough attempts to break the Deadlock
IF #iLockTimeoutRetries < sCommon.fnMaxLockTimeoutRetries()
BEGIN
-- Increment the Attempt count
SET #iLockTimeoutRetries = #iLockTimeoutRetries + 1
-- Pause to allow the lock contention to clear
WAITFOR DELAY #dtLockTimeoutSleepInterval
GOTO RETRY
END
END
exec Common.RethrowError
END CATCH
END
The result is as follows..
Product
6435LVWK-360-CD819E3
NSCHI535C1097I360-4C
NSCHU485C1819I360-0C
Return Value
0
My Linq-to-SQL connection is like this
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="PFO.PFOValidateUpdateData")]
public int PFOValidateUpdateData([global::System.Data.Linq.Mapping.ParameterAttribute(Name = "PfoIDs", DbType = "Xml")] System.Xml.Linq.XElement pfoIDs, [global::System.Data.Linq.Mapping.ParameterAttribute(Name = "UserID", DbType = "UniqueIdentifier")] System.Nullable<System.Guid> userID)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), pfoIDs, userID);
return ((int)(result.ReturnValue));
}
I am trying to retrieve all the data from the stored procedure but the when I debugging it the return value is "o"..
I would be grateful to you if you could help me retrieve all the data returned by the stored procedure... thank you very much...
If your stored procedure returns a collection of nvarchar's, then the signature of your Linq2Sql method is not correct. It should not return an int, but an ISingleResult.
So the correct signature will be:
public ISingleResult<string> PFOValidateUpdateData(...)
{
IExecuteResult result = this....;
return (ISingleResult<string>)result.ReturnValue;
}
var products = PFOValidateUpdateData(...).ToList();
If you want to return the results from multiple SELECT's in your stored procedure, you'll have to use IMultipleResults.
Well I know this is not the right way...for time being,its working for me...
I created an other table with two columns one ProductId and ID, I am inserting the values returned by the stored procedure,
in the designer.cs I am returning the table,
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="PFO.PFOValidateUpdateData")]
public ISingleResult<PFOValidData> PFOValidateUpdateData([global::System.Data.Linq.Mapping.ParameterAttribute(Name = "PfoIDs", DbType = "Xml")] System.Xml.Linq.XElement pfoIDs, [global::System.Data.Linq.Mapping.ParameterAttribute(Name = "UserID", DbType = "UniqueIdentifier")] System.Nullable<System.Guid> userID)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), pfoIDs, userID);
return ((ISingleResult<PFOValidData>)(result.ReturnValue));
}
And in the Domainservice
List<string> PFOValidateUpdateData(string pfoIds, Guid userID)
{
List<string> productIdList = new List<string>();
// Acquire the int
result = this.DataContext.PFOValidateUpdateData(element, userID);
foreach (var item in result)
{
productIdList.Add(item.ProductID);
}
return productIdList;
To get the multiple values returned by the stored procedure....
Please let me know if there is a better way to solve this... thank you

Check if value exists in multiple tables

I have such tables in my database like Customer, Member, Instructor, Employee etc. Each of these users has his email adrress. I need to check if there is already a user with given email. I was thinking about:
Check each table, something like this:
public bool IsEmailAddressExists(string email)
{
if (!Context.Customers.Any(c => string.Equals(c.Email, email, StringComparison.InvariantCultureIgnoreCase)))
if (!Context.Members.Any(m => string.Equals(m.Email, email, StringComparison.InvariantCultureIgnoreCase)))
...
}
Select all emails and check:
public bool IsEmailAddressExists(string email)
{
var emails = Context.Customers.Select(c => c.Email).Union(Context.Members.Select(m => m.Email))...; //other unions
return emails.Any(e => string.Equals(e, email, StringComparison.InvariantCultureIgnoreCase));
}
There are more tables and many users, so I would like to know what would be the most efficient way to implement such kind of checking.
Thank you.
It sounds like you have a couple of options.
Create a view. You could create a view in the database that shows, say, email addresses only. Assuming you're using MSSQL Server, something like:
CREATE VIEW EmailView AS
SELECT Email from Customers
UNION ALL
SELECT Email from Instructors
....
... then using an entity bound to that view so you can check the list of emails to see if that email exists already. Check out the documentation for more information.
Normalize your database. Do each of these tables share common information beyond email, say, first name and/or last name? It might be worth your time to reorganize your data model to put that information in a "Persons" table, then foreign key your other tables to it. (This will also help if your users are two different things, say, a customer and an instructor.)
In pure SQL this would be your most efficient because it stops searching as soon as it hits a match:
... As a stored procedure:
CREATE PROCEDURE EmailExists
#email varchar(254) = NULL
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #emailExists bit
SET #emailExists = 0
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Customer WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Member WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Instructor WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Employee WHERE email = #email)
END
END
END
SELECT #emailExists
END
... As a scalar-valued function:
CREATE FUNCTION EmailExists
(
#email varchar(254)
)
RETURNS bit
AS
BEGIN
DECLARE #emailExists bit
SET #emailExists = 0
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Customer WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Member WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Instructor WHERE email = #email)
IF #emailExists = 0
BEGIN
SELECT #emailExists = 1 WHERE EXISTS(SELECT 1 FROM Employee WHERE email = #email)
END
END
END
-- Return the result of the function
RETURN #emailExists
END
In C# with Linq, you can use the Any extension and the || operator. Since Any usually gets translated to EXISTS in SQL and evalutation of the || operator in C# is lazy, evaluation will stop as soon as the first ocurrence of an email is reached.
bool emailExists = customerEmails.Any(e => string.Equals(e, email, StringComparison.InvariantCultureIgnoreCase))
|| memberEmails.Any(e => string.Equals(e, email, StringComparison.InvariantCultureIgnoreCase))
|| instructorEmails.Any(e => string.Equals(e, email, StringComparison.InvariantCultureIgnoreCase))
|| employeeEmails.Any(e => string.Equals(e, email, StringComparison.InvariantCultureIgnoreCase));

Error in MySql Query Syntax

I am trying to create a MySql stored procedure through C#. I have created some other as well but when I try to execute the Query via MySqlCommand.ExecuteNonQuery its throws a exception that you query syntax is not compatible. Here is the exception message:
You have an error in your SQL syntax;
check the manual that corresponds to
your MySQL server version for the
right syntax to use near 'SET
VariableRecordExists = (SELECT
COUNT(*) FROM SmartCache_Sync WHERE
MachineNa' at line 10
And here I am trying to build the query string:
string sql = #"CREATE PROCEDURE SmartCache_UpdateSync
(
VariableMachineName varchar(50)
)
BEGIN
DECLARE VariableRecordExists int;
DECLARE VariableSetDate datetime;
START TRANSACTION;
SET VariableSetDate= Now()
SET VariableRecordExists = (SELECT COUNT(*) FROM SmartCache_Sync WHERE MachineName = VariableMachineName)
IF VariableRecordExists = 1
THEN
UPDATE SmartCache_Sync
SET LastUpdate = VariableSetDate
WHERE MachineName= VariableMachineName;
ELSE
INSERT INTO SmartCache_Sync
(MachineName,LastUpdate)
VALUES (VariableMachineName,VariableSetDate);
END IF;
COMMIT;
SELECT VariableSetDate;
END";
I don't know where I am making a mistake. Probably I am missing a semi colon ; somewhere or what. I would be obliged if anyone could help me.
I don't know about making queryies in c#, but normally the ; is the end of your query: so if you don't change the delimiter, you are ending your command early. normally you would do something like this:
delimiter //
CREATE PROCEDURE simpleproc (OUT param1 INT)
BEGIN
SELECT COUNT(*) INTO param1 FROM t;
END//
delimiter ;
from:
http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html
Slightly off-topic - but...
In general, prefer "if exists (select...)" to "select count(*)..." when all you want to do is check if any rows exist. It is far cheaper than actually counting all the rows.
And secondly, it looks as though you are trying to do an "upsert", which in MySQL would be
INSERT INTO SmartCache_Sync(MachineName,LastUpdate)
VALUES (VariableMachineName,VariableSetDate)
ON DUPLICATE KEY UPDATE LastUpdate = VariableSetDate
and then you don't need the explicit transaction either.
This of course assumes that MachineName is a primary key, which I'm guessing it is.
My guess was right I was missing a ";". And honestly speaking it took me 2 hours to that out.
**
SET VariableSetDate= Now();
SET VariableRecordExists = (SELECT COUNT(*) FROM SmartCache_Sync
WHERE MachineName =
VariableMachineName);
**
Both the statements didn't have ";" at the end
CREATE PROCEDURE SmartCache_UpdateSync
(
VariableMachineName varchar(50)
)
BEGIN
DECLARE VariableRecordExists int;
DECLARE VariableSetDate datetime;
START TRANSACTION;
SET VariableSetDate= Now()
(SELECT VariableRecordExists = COUNT(*) FROM SmartCache_Sync WHERE MachineName = VariableMachineName)
IF VariableRecordExists = 1
THEN
UPDATE SmartCache_Sync
SET LastUpdate = VariableSetDate
WHERE MachineName= VariableMachineName;
ELSE
INSERT INTO SmartCache_Sync
(MachineName,LastUpdate)
VALUES (VariableMachineName,VariableSetDate);
END IF;
COMMIT;
SELECT VariableSetDate;
END";

Categories