Replacement of "IN" in stored procedure - sql

I have huge store procedure. I only paste small part of this.
My Query:
--declare variable and Select Statement goes here
WHERE ((v.[RoleID] IN (
SELECT [dbo].[aspnet_UsersInRoles].roleid
FROM [dbo].[aspnet_UsersInRoles]
INNER JOIN [dbo].[aspnet_Users]
ON [dbo].[aspnet_Users].userid = [dbo].[aspnet_UsersInRoles].userid
WHERE
[dbo].[aspnet_Users].username = #UserName
UNION ALL
SELECT [RoleId]
FROM dbo.aspnet_roles
WHERE loweredrolename = 'anonymous user')
OR v.username = #UserName))
Query works perfect but its take long time to execute. There is so many condition inside WHERE condition with IN. I think the subquery inside IN take time. Is there any way to optimize this query and replace IN inside WHERE condition. Or suggest me an alternative way to optimize this type of query. Thanks.

Since the output of inner subquery is always the same. You can take that outside and insert those values into a temptable like below and u could just query to the temp table. So each time it doesnt go in a loop executing the same
`create table #temp(roleid int); `
` insert into #temp as (sSELECT [dbo].[aspnet_UsersInRoles].roleid
FROM [dbo].[aspnet_UsersInRoles]
INNER JOIN [dbo].[aspnet_Users]
ON [dbo].[aspnet_Users].userid = [dbo].[aspnet_UsersInRoles].userid
WHERE
[dbo].[aspnet_Users].username = #UserName
UNION ALL
SELECT [RoleId]
FROM dbo.aspnet_roles
WHERE loweredrolename = 'anonymous user');`
WHERE ((v.[RoleID] IN ( select roleid from #temp);

You could make a temp table with the result of the IN clause and join to it. This would break the problem to two much simpler tasks.

according to Orangecrush recommendation
--declare variable and Select Statement goes here
WHERE EXISTS (
SELECT 1
FROM [dbo].[aspnet_UsersInRoles]
INNER JOIN [dbo].[aspnet_Users]
ON [dbo].[aspnet_Users].userid = [dbo].[aspnet_UsersInRoles].userid
WHERE
[dbo].[aspnet_Users].username = #UserName
AND [dbo].[aspnet_UsersInRoles].roleid = v.[RoleID]
UNION ALL
SELECT 1
FROM dbo.aspnet_roles
WHERE loweredrolename = 'anonymous user'
AND dbo.aspnet_roles.roleid = v.[RoleID])
OR v.username = #UserName)

Related

Rewrite query without using temp table

I have a query that is using a temp table to insert some data then another select from to extract distinct results. That query by it self was fine but now with entity-framework it is causing all kinds of unexpected errors at the wrong time.
Is there any way I can rewrite the query not to use a temp table? When this is converted into a stored procedure and in entity framework the result set is of type int which throws an error:
Could not find an implementation of the query pattern Select not found.
Here is the query
Drop Table IF EXISTS #Temp
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName into #Temp
FROM RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join #Temp t on r.ReceiverID = t.ReceiverID;
No need for anything fancy, you can just replace the reference to #temp with an inner sub-query containing the query that generates #temp e.g.
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join (
select
a.ReceiverID,
a.AntennaID,
a.AntennaName
from RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
) t on r.ReceiverID = t.ReceiverID;
PS: I haven't made any effort to improve the query overall like Gordon has but do consider his suggestions.
First, a full join makes no sense in the first query. You are selecting only columns from the first table, so you need that.
Second, you can use a CTE.
Third, you should be able to get rid of the SELECT DISTINCT by using an EXISTS condition.
I would suggest:
WITH ra AS (
SELECT ra.*
FROM RFIDReceiverAntenna ra
Station s
ON s.ReceiverID = ra.ReceiverID AND
s.AntennaID = ra.AntennaID)
WHERE s.ReceiverID is NULL
)
SELECT r.ReceiverID, r.ReceiverName, r.receiverdescription
FROM RFIDReceiver r
WHERE EXISTS (SELECT 1
FROM ra
WHERE r.ReceiverID = ra.ReceiverID
);
You can use CTE instead of the temp table:
WITH
CTE
AS
(
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName
FROM
RFIDReceiverAntenna a
full join Station b
ON (a.ReceiverID = b.ReceiverID)
and (a.AntennaID = b.AntennaID)
where
(a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
)
select distinct
r.ReceiverID, r.ReceiverName, r.receiverdescription
from
RFIDReceiver r
inner join CTE t on r.ReceiverID = t.ReceiverID
;
This query will return the same results as your original query with the temp table, but its performance may be quite different; not necessarily slower, it can be faster. Just something that you should be aware about.

sql inner table substitution

Suppose I have an sql query like the following (I realize this query could be written better, just bear with me):
SELECT aT.NAME
FROM anothertable aT,
( SELECT ts.slot_id,
tgm.trans_id,
tagm.agent_id
FROM slots ts,
transactions tgm,
agents tagm
WHERE ts.slot_id = (12345, 678910)
and ts.slot_id = tagm.slot_id
AND ts.slot_id = tgm.slot_id) INNER
WHERE INNER.trans_id = aT.trans_id
AND INNER.agent_id = aT.trans_id
Now suppose that I need to break up this query into two parts...in the first I'll execute the inner query, do some processing on the results in code, and then pass back a reduced set to the outer part of the query. The question is, is there an easy way to emulate an inner table in sql?
For instance, if the results of the inner query returned 5 rows but my program deems to only need two of those rows, how can I write sql that will do what I am trying to do below? Is there a way, in sql, to declare a table for in memory in query use?
SELECT
at.Name
FROM
anotherTable aT,
(SLOT_ID, TRANS_ID, AGENT_ID
-------------------------
230743, 3270893, 2307203
078490, 230897, 237021) inner
WHERE
inner.trans_id = at.trans_id
AND INNER.agent_id = aT.trans_id
Just use a subquery:
SELECT at.Name
FROM anotherTable aT JOIN
(select 230743 as SLOT_ID, 3270893 as TRANS_ID, 2307203 as AGENT_ID from dual
select 078490, 230897, 237021 from dual
) i
on i.trans_id = at.trans_id AND i.agent_id = aT.trans_id;
Most systems will let you define a TEMP TABLE or TABLE VARIABLE: https://www.simple-talk.com/sql/t-sql-programming/temporary-tables-in-sql-server/
CREATE TABLE #temp (
SLOT_ID INT,
TRANS_ID INT,
AGENT_ID INT
);
INSERT INTO #temp(SLOT_ID, TRANS_ID, AGENT_ID)
(--inner query goes here)
--do your main query, then:
DROP TABLE #temp
IN MS SQL Server (not sure about other systems), you could possibly use a Common Table Expression (CTE): https://technet.microsoft.com/en-us/library/ms190766%28v=sql.105%29.aspx
WITH inner AS (
--inner query goes here
)
--main select goes here
Personally, since I generally work with MSSQL Server, I use CTE's quite a bit, as they can be created "on the fly", and can be a big help in organizing more complex queries.
The subquery method worked. Since this is Oracle, the syntax turned out to be:
SELECT aT.Name
FROM anotherTable aT,
(select 1907945 as SLOT_ID, 2732985 as TRANS_ID, 40157 as AGENT_ID FROM DUAL
union
select 1907945, 2732985, 40187 FROM DUAL
) inner
WHERE
inner.trans_id = aT.trans_id AND INNER.agent_id = aT.trans_id;

SQL Server 2005 - Using temporary tables in a multi-user environment

I have a problem with this procedure. It uses a couple of temporary tables which it drops and recreates. This is causing loads of crashes, and I can only presume that conflicts are arising.
ALTER PROCEDURE [dbo].[AdditionalVisits_SelectByOrigID]
(
#orig_job_ID int
)
as
if exists (select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'additionalVisitsPodsCnt')
drop table additionalVisitsPodsCnt;
SELECT CONVERT(bit, COUNT(PODS.podID)) AS cnt, AdditionalVisits.this_job_id, AdditionalVisits.orig_job_id
INTO additionalVisitsPodsCnt
FROM PODS RIGHT OUTER JOIN
AdditionalVisits ON PODS.jobID = AdditionalVisits.this_job_id
GROUP BY AdditionalVisits.this_job_id, AdditionalVisits.orig_job_id
HAVING (AdditionalVisits.orig_job_id = #orig_job_ID)
if exists (select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'additionalVisitsPhotosCnt')
drop table additionalVisitsPhotosCnt;
SELECT CONVERT(bit, COUNT(Photos.photoID)) AS cnt, AdditionalVisits.this_job_id, AdditionalVisits.orig_job_id
INTO additionalVisitsPhotosCnt
FROM Photos RIGHT OUTER JOIN
AdditionalVisits ON Photos.jobID = AdditionalVisits.this_job_id
GROUP BY AdditionalVisits.this_job_id, AdditionalVisits.orig_job_id
HAVING (AdditionalVisits.orig_job_id = #orig_job_ID)
SELECT AdditionalVisits.id, AdditionalVisits.this_job_ID, AdditionalVisits.orig_job_ID, AdditionalVisits.reason, AdditionalVisits.date, ThisJob.Job_Reference_No,
ThisJob.Job_POD_Filename, ThisJob.Job_Photo_Filename, ThisJob.Job_Signed_For_Name, ThisJob.Job_Value, ThisJob.Job_Advance_Payment,
ThisJob.Job_Eoj_Payment, ThisJob.Job_Status, ThisJob.Job_Date_Added, ThisJob.Job_Start_Date, additionalVisitsPhotosCnt.cnt AS Job_Photo_Supplied,
additionalVisitsPodsCnt.cnt AS Job_POD_Supplied
FROM AdditionalVisits INNER JOIN
Jobs AS ThisJob ON AdditionalVisits.this_job_ID = ThisJob.Job_ID INNER JOIN
additionalVisitsPodsCnt ON AdditionalVisits.this_job_ID = additionalVisitsPodsCnt.this_job_id INNER JOIN
additionalVisitsPhotosCnt ON AdditionalVisits.this_job_ID = additionalVisitsPhotosCnt.this_job_id
WHERE (AdditionalVisits.orig_job_ID = #orig_job_ID) AND (ThisJob.Job_Status <> 7)
The errors I get are as follow:
Cannot drop the table 'additionalVisitsPodsCnt', because it does not exist or you do not have permission. There is already an object named 'additionalVisitsPodsCnt' in the database.
or
Invalid object name 'additionalVisitsPhotosCnt'.
These errors are intermittent. Sometimes it just works.
Is there a better way of doing this?
Sure, if this table is only used from this one procedure, use a "real" local temporary table (created in tempdb), by prefixing the table name with #:
SELECT CONVERT(bit, COUNT(PODS.podID)) AS cnt,
AdditionalVisits.this_job_id, AdditionalVisits.orig_job_id
INTO #additionalVisitsPodsCnt
FROM PODS ....
This way it is isolated from all other sessions, even if concurrent calls to this procedure are made by others.
Here is the normal pattern I used for a #temp table. (Notice the "#" prefix).
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
CREATE TABLE #TableOne
(
SurrogateKey int ,
NameOf varchar(12)
)
/*
Do something with temp table
*/
IF OBJECT_ID('tempdb..#TableOne') IS NOT NULL
begin
drop table #TableOne
end
Consider using table variables instead.
This StackOverflow topic explains it better than I would... ;)

use stored procedure to select last item value

i am trying to select the last record value from my database by using stored procedure, to do this i set my #UPID parameter as SCORE_IDENTITY(), but there are no output result as all after i execute my stored procedure
CREATE PROCEDURE [dbo].[spAuditLogSelect_NewUser]
#UPID int
AS
BEGIN
SET #UPID = SCOPE_IDENTITY()
SELECT
siUserProfile.UPID
,siUserProfile.ProfileType, siProfileType.RGDName AS ProfileTypeName
,siUserProfile.CBID, siCompany.ComName + ' - ' + siComBranch.ComBranchName AS CBName
,siUserProfile.FullName
,siUserProfile.ShortName
,siUserProfile.SerialCode
,siUserProfile.Serial
,siUserProfile.Gender
from siUserProfile WITH (NOLOCK)
inner join siUserProfileDetail WITH (NOLOCK) on siUserProfile.upid = siUserProfileDetail.UPID
left outer join siReferenceGroupDetail siProfileType WITH (NOLOCK) ON siUserProfile.ProfileType = siProfileType.RGDID
left outer join siComBranch WITH (NOLOCK) on siComBranch.CBID = siUserProfile.CBID
left outer join siCompany WITH (NOLOCK) ON siComBranch.CompanyID = siCompany.CompanyID
where siUserProfile.UPID = #UPID
SCOPE_IDENTITY() is meant to be used right after insert. It won't work in a different session.
To retrieve the latest entry, try top 1:
select top 1 *
...
where siUserProfile.UPID = #UPID
order by
siUserProfile.ID desc
You require to use IDENT_CURRENT(‘tablename’).
Please refer below link which illustrate difference between ##IDENTITY,SCOPE_IDENTITY() and IDENT_CURRENT(‘tablename’).
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
Hope this will help you.
I am assuming that you have some primary key(PK) in your table. Write the procedure, in that procedure fire the query
select * from Your_Table where PK_Column in(select max(PK_Column) from Your_Table)
This way you will be able to fetch the latest record from DB. By opening a cursor, you can play with record in your procedure.

How do I force SQL Server to execute a query in a particular order

I have the following query
DECLARE #userId INT
DECLARE #siteId INT
SET #siteId = -1
SET #userId = 1828
SELECT a.id AS alertId,
a.location_id,
a.alert_type_id,
a.event_id,
a.user_id,
a.site_id,
a.accepted_by
FROM alerts AS a
JOIN alert_types AS ats ON a.alert_type_id = ats.id
JOIN events AS tr ON a.event_id = tr.event_id
WHERE tr.end_Time IS null
AND tr.status_id = 0
AND ats.code = 'E'
AND a.site_id in (SELECT * FROM dbo.udf_get_event_sitelist(#siteId, #userId))
This query takes between 5 and 17 seconds to run, however under many circumstances the function dbo.udf_get_event_sitelist(#siteId, #userId) returns no rows, so the query will not find any data.
How can I force SQL Server to execute the user defined function first. I appreciate that I could rewrite the query into a stored procedure and perform the sub-select first, however I would like to do it in a single SQL statement if possible.
make the "FROM" table the results set of the function and join the other tables to it
DECLARE #userId INT
DECLARE #siteId INT
SET #siteId = -1
SET #userId = 1828
SELECT a.id AS alertId,
a.location_id,
a.alert_type_id,
a.event_id,
a.user_id,
a.site_id,
a.accepted_by
FROM (SELECT * FROM dbo.udf_get_event_sitelist(#siteId, #userId)) dt
JOIN alerts AS a ON dt.site_id=a.site_id
JOIN alert_types AS ats ON a.alert_type_id = ats.id
JOIN events AS tr ON a.event_id = tr.event_id
WHERE tr.end_Time IS null
AND tr.status_id = 0
AND ats.code = 'E'
you could select the results of udf_get_event_sitelist into a table variable and only proceed with the big query if ##rowcount > 0
The problem that you have when using inline functions is that they can be re-evaluated for each row returned in the SELECT. This means, that if the SELECT statement returns 100 rows, then the function can be executed 100 times.
You should really follow Sambo99's advice and extract it to a table variable (or a temp table if you think it needs indexes).
Also, modify you UDF to only return the site_ID as I am guessing you do not neet all (*) columns
SELECT * FROM dbo.udf_get_event_sitelist(#siteId, #userId)
to
SELECT site_id FROM dbo.udf_get_event_sitelist(#siteId, #userId)