In stored procedure not getting expected result - sql

I have 3 tables Surveys, RightsonSurveys and Users.
I want the result as shown in the screenshot below where all survey list should be shown but only those surveys should be checked where user has IsViewer or IsAuthor is true:
But instead, I'm getting this result:
In the RightsOnSurveys table I am providing permissions to access particular surveys. For this I have created a view like this:
ALTER VIEW [dbo].[vRightsOnSurveys]
AS
SELECT
ID SurveyID, 0 ID, -1 UserID,
CAST(1 AS bit) IsViewer, CAST(1 AS bit) IsAuthor, Name
FROM
Surveys
UNION
SELECT
SurveyID, ID, UserID, IsViewer, IsAuthor, NULL AS Name
FROM
RightsOnSurveys
GO
SELECT * FROM [dbo].[vRightsOnSurveys]
Here is output of this view:
and selecting from this view in the stored procedure for front end where SurveyID and UserID could be null or have data
ALTER PROCEDURE [dbo].[GetUserRightsOnSurveys]
(#SurveyID int = NULL,
#UserID int = NULL)
AS
BEGIN
SELECT
ID, UserID, SurveyID, ros.IsViewer, IsAuthor, ros.Name
FROM
vRightsOnSurveys ros
WHERE
(ros.SurveyID = #SurveyID OR #SurveyID IS NULL)
AND (ros.UserID = #UserID OR #UserID IS NULL)
END
With this procedure, I am not getting results as per my requirements.
Can somebody please help me? Thanks in advance..

You need left join in the view as follows:
alter view [dbo].[vRightsOnSurveys]
as
select s.ID SurveyID,
coalesce(r.id,0) ID,
coalesce(r.userid,-1) UserID,
coalesce(r.isviewer,cast(1 as bit)) IsViewer,
coaleace(r.isauthor,cast(1 as bit)) IsAuthor,
s.Name
from Surveys s
left Join rightonsurvey r on r.surveyid = s.id;

I have to change my whole query and got my results as per my requirements here is the query..
select usr.ID as UserID,usr.Login,usr.Name as UserName, sur.ID as SurveyID,sur.Name
,ISNULL((select r.ID from RightsOnSurveys r where r.UserID = usr.ID and r.SurveyID
= sur.ID),0) as ID
,ISNULL((select r.IsViewer from RightsOnSurveys r where r.UserID = usr.ID and
r.SurveyID = sur.ID),cast(0 as bit)) as IsViewer
,ISNULL((select r.IsAuthor from RightsOnSurveys r where r.UserID = usr.ID and
r.SurveyID = sur.ID),cast(0 as bit)) as IsAuthor
from Users usr,Surveys sur

Related

SQL Query - long running / taking up CPU resource

Hello I have the below SQL query that is taking on average 40 minutes to run, one of the tables that it references has over 7 million records in it.
I have ran this through the database tuning advisor and applied all recommendations, also I have assesed it within the activity monitor in sql and no further indexes etc have been recommended.
Any suggestions would be great, thanks in advance
WITH CTE AS
(
SELECT r.Id AS ResultId,
r.JobId,
r.CandidateId,
r.Email,
CAST(0 AS BIT) AS EmailSent,
NULL AS EmailSentDate,
'PICKUP' AS EmailStatus,
GETDATE() AS CreateDate,
C.Id AS UserId,
C.Email AS UserEmail,
NULL AS Subject
FROM Result R
INNER JOIN Job J ON R.JobId = J.Id
INNER JOIN User C ON J.UserId = C.Id
WHERE
ISNULL(J.Approved, CAST(0 AS BIT)) = CAST(1 AS BIT)
AND ISNULL(J.Closed, CAST(0 AS BIT)) = CAST(0 AS BIT)
AND ISNULL(R.Email,'') <> '' -- has an email address
AND ISNULL(R.EmailSent, CAST(0 AS BIT)) = CAST(0 AS BIT) -- email has not been sent
AND R.EmailSentDate IS NULL -- email has not been sent
AND ISNULL(R.EmailStatus,'') = '' -- email has not been sent
AND ISNULL(R.IsEmailSubscribe, 'True') <> 'False' -- not unsubscribed
-- not already been emailed for this job
AND NOT EXISTS (
SELECT SMTP.Email
FROM SMTP_Production SMTP
WHERE SMTP.JobId = R.JobId AND SMTP.CandidateId = R.CandidateId
)
-- not unsubscribed
AND NOT EXISTS (
SELECT u.Id FROM Unsubscribe u
WHERE ISNULL(u.EmailAddress, '') = ISNULL(R.Email, '')
)
AND NOT EXISTS (
SELECT SMTP.Id FROM SMTP_Production SMTP
WHERE SMTP.EmailStatus = 'PICKUP' AND SMTP.CandidateId = R.CandidateId
)
AND C.Id NOT IN (
-- list of ids
)
AND J.Id NOT IN (
-- list of ids
)
AND J.ClientId NOT IN
(
-- list of ids
)
)
INSERT INTO smtp_production (ResultId, JobId, CandidateId, Email, EmailSent, EmailSentDate, EmailStatus, CreateDate, ConsultantId, ConsultantEmail, Subject)
OUTPUT INSERTED.ResultId,GETDATE() INTO ResultstoUpdate
SELECT
CTE.ResultId,
CTE.JobId,
CTE.CandidateId,
CTE.Email,
CTE.EmailSent,
CTE.EmailSentDate,
CTE.EmailStatus,
CTE.CreateDate,
CTE.UserId,
CTE.UserEmail,
NULL
FROM CTE
INNER JOIN
(
SELECT *, row_number() over(partition by CTE.Email, CTE.CandidateId order by CTE.EmailSentDate desc) as rn
FROM CTE
) DCTE ON CTE.ResultId = DCTE.ResultId AND DCTE.rn = 1
Please see my updated query below:
WITH CTE AS
(
SELECT R.Id AS ResultId,
r.JobId,
r.CandidateId,
R.Email,
CAST(0 AS BIT) AS EmailSent,
NULL AS EmailSentDate,
'PICKUP' AS EmailStatus,
GETDATE() AS CreateDate,
C.Id AS UserId,
C.Email AS UserEmail,
NULL AS Subject
FROM RESULTS R
INNER JOIN JOB J ON R.JobId = J.Id
INNER JOIN Consultant C ON J.UserId = C.Id
WHERE
J.DCApproved = 1
AND (J.Closed = 0 OR J.Closed IS NULL)
AND (R.Email <> '' OR R.Email IS NOT NULL)
AND (R.EmailSent = 0 OR R.EmailSent IS NULL)
AND R.EmailSentDate IS NULL -- email has not been sent
AND (R.EmailStatus = '' OR R.EmailStatus IS NULL)
AND (R.IsEmailSubscribe = 'True' OR R.IsEmailSubscribe IS NULL)
-- not already been emailed for this job
AND NOT EXISTS (
SELECT SMTP.Email
FROM SMTP_Production SMTP
WHERE SMTP.JobId = R.JobId AND SMTP.CandidateId = R.CandidateId
)
-- not unsubscribed
AND NOT EXISTS (
SELECT u.Id FROM Unsubscribe u
WHERE (u.EmailAddress = R.Email OR (u.EmailAddress IS NULL AND R.Email IS NULL))
)
AND NOT EXISTS (
SELECT SMTP.Id FROM SMTP_Production SMTP
WHERE SMTP.EmailStatus = 'PICKUP' AND SMTP.CandidateId = R.CandidateId
)
AND C.Id NOT IN (
-- LIST OF IDS
)
AND J.Id NOT IN (
-- LIST OF IDS
)
AND J.ClientId NOT IN
(
-- LIST OF IDS
)
)
INSERT INTO smtp_production (ResultId, JobId, CandidateId, Email, EmailSent, EmailSentDate, EmailStatus, CreateDate, UserId, UserEmail, Subject)
OUTPUT INSERTED.ResultId,GETDATE() INTO ResultstoUpdate
SELECT
CTE.ResultId,
CTE.JobId,
CTE.CandidateId,
CTE.Email,
CTE.EmailSent,
CTE.EmailSentDate,
CTE.EmailStatus,
CTE.CreateDate,
CTE.UserId,
CTE.UserEmail,
NULL
FROM CTE
INNER JOIN
(
SELECT *, row_number() over(partition by CTE.Email, CTE.CandidateId order by CTE.EmailSentDate desc) as rn
FROM CTE
) DCTE ON CTE.ResultId = DCTE.ResultId AND DCTE.rn = 1
GO
Using ISNULL in your WHERE and JOIN clauses is probably the main cause here. Using functions against columns in your query causes the query to become non-SARGable (meaning that it can't use any of the indexes on your table(s) and so it has the scan the whole thing). Note; using functions against variables, in there WHERE is normally fine. For example WHERE SomeColumn = DATEADD(DAY, #n, #SomeDate). Things like WHERE SomeColumn = ISNULL(#Variable,0) have the smell of a "catch-all query", so can be performance hitters; depending on your set up. This isn't the discussion at hand though.
For clauses like ISNULL(J.Closed, CAST(0 AS BIT)) = CAST(0 AS BIT) this is therefore a big headache for the query optimiser and your query is riddled with them. You'll need to replace these with clauses like:
WHERE (J.Closed = 0 OR J.Closed IS NULL)
Although it makes no difference, there's no need to CAST the 0 there either. SQL Server can see you're making a comparison to a bit and will therefore interpret the 0 as one as well.
You also have a EXISTS with the WHERE clause ISNULL(u.EmailAddress, '') = ISNULL(R.Email, ''). This will need to become:
WHERE (u.EmailAddress = R.Email
OR (u.EmailAddress IS NULL AND R.Email IS NULL))
You'll need to change all of your ISNULL usage in your WHERE clauses (the CTE and the subqueries) and you should see a decent performance increase.
Generally, 7 million records are a joke for modern databases. If you alk problems, you are supposed to talk problems on billions of rows, not 7 millions.
Which indicates problems with the query. High CPU is generally a sign of non matching fields (compare string in one table to number in another ) or... functions called too often. Long running normally is a sign of either missing indices or.... non sargeability. Which you really do a lot to force.
Non-Sargeability means taht indices CAN NOT be used. Example of this is all this:
ISNULL(J.Approved, CAST(0 AS BIT)) = CAST(1 AS BIT)
The ISNULL(field, value) means that an index on field is not usable - baically "goodby index, hello table scan". It also means - well....
(J.Approoved = 1 or J.Approoved IS NULL)
has the same meaning, but it sargeable. Pretty much EVERY of your conditions is written in a non sargeable way - welcome to db hell. Start rewriting.
You may want to read up more on sargeability at https://www.techopedia.com/definition/28838/sargeable
Also make sure you ahve indices on all relevant foreign keys (and the referenced primary keys) - otherwise, again, welcome table scans.

Conversion failed while using ids "where ID in (...)", nvarchar to int

I have a query in MSSQL 2008 like:
IF OBJECT_Id('tempdb..#AccessibleFacilities') IS NOT NULL DROP TABLE #AccessibleFacilities
SELECT u.Userid
, AccesibleFacilityIds = dbo.GetCommaDelimitedString(upf.Facility_Id)
INTO #AccessibleFacilities
FROM Users u
INNER join UserProfileFacilities upf on upf.UserProfile_Id = up.Id
WHERE LOWER(u.Userid) = LOWER(#userId)
GROUP BY u.Userid
This query returns AccessibleFacilityIds like ",1,2,3,4,5,6,". Please note that I am not able to modify GetCommaDelimitedString function.
What I actually need to do is that using those facility ids to reach provs like below:
INSERT INTO #AccessibleProvs
SELECT Userid = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
FROM Provs p
inner join ProvFacs pf on p.Id = pf.Provider_Id
WHERE pf.Facility_Id in
(select a.AccesibleFacilityIds from #AccessibleFacilities a)
However, it gives me an error like:
Conversion failed when converting the nvarchar value ',1,2,3,4,5,6,'
to data type int.
I tried removing the comma signs at the start and end like below to fix it, but it did not help:
...
where pf.Facility_Id in (
select SUBSTRING(a.AccesibleFacilityIds,2,LEN(a.AccesibleFacilityIds)-2)
from #AccessibleFacilities a
)
Any advice would be appreciated. Thanks.
Instead of converting Facility_Id into a comma delimited string, why not keep it as a usable column in your temp table?
if object_Id('tempdb..#AccessibleFacilities') is not null drop table #AccessibleFacilities;
select
u.UserId
, upf.Facility_Id
into #AccessibleFacilities
from Users u
inner join UserProfileFacilities upf
on upf.UserProfile_Id = up.Id
Then use it as you did with in() or with exists():
insert into #AccessibleProvs
select
UserId = #userId
, AccessibleProvIds = dbo.GetCommaDelimitedString(distinct p.Id)
from Provs p
inner join ProvFacs pf
on p.Id = pf.Provider_Id
where exists (
select 1
from #AccessibleFacilities a
where a.Facility_Id = pf.Facility_Id
--and a.UserId = #UserId -- Do you need to check Facility_Id by User?
)
If you have the value for #UserId in the beginning, you could limit your temp table usage to just the user you need. Hopefully this code is not meant for use in some sort of cursor or other loop.

How to write a query to check if a child has any children in a table

I have two tables named User and ParentUser that there is an one to many relation between them and the many side is the ParentUser table.
I wanna write a query to pass a parentId and fetch all of its children and a column name HasChildren to see whether every child has any children or not.
The picture below shows some sample data and needed result:
Tries:
1-By Prdp
SELECT u.*,
CASE
WHEN p.ParentId IS NULL THEN 1
ELSE 0
END as HasChildren
FROM [User] u
LEFT JOIN (select distinct ParentId from ParentUser) p
ON u.UserId = p.ParentId
All good here but I cant pass a parentId to it.
2- By Juozas
DECLARE #ParentId INT = 2441;
SELECT DISTINCT
[UserId] = [u].[userid]
,[HasChildren] = CAST(ISNULL([pu].[userid], 0) AS BIT)
,[ChildrenId] = [pu].[userid]
FROM
[user] AS [u]
OUTER APPLY
(
SELECT [userid], [parentid] FROM [ParentUser] WHERE [parentid] = [u].[userid]
) AS [pu]
WHERE
[pu].[parentid] = #ParentId;
All good again but the field HasChildren's value is always 1.
SELECT UserId, HasChildren = CASE WHEN EXISTS(SELECT 1 FROM ParentUser pu2
WHERE pu2.ParentId = pu.UserId)
THEN 1 ELSE 0 END
FROM ParentUser pu
WHERE ParentId = #ParentId
ORDER BY UserId

SQL procedure select

I would like to write a procedure to database which will return select all data from database Tournaments plus bool parameter. If user is registered, it will return true.
Call:
exec TournamentsWithLoggedUser #user = 'asd123'
Procedure:
CREATE PROCEDURE [dbo].[TournamentsWithLoggedUser]
#user nvarchar(128)
AS
SELECT
t.Id, t.Info, BIT(r.Id)
FROM
Tournaments AS t
LEFT JOIN
Registrations AS r ON t.Id = r.TournamentId
WHERE
r.UserId IS NULL OR r.UserId = #user
RETURN
it mean something like
1, 'some info', true //1
2, 'some info2', false //2
Why not just use a case statement?
CASE WHEN r.Id IS NULL THEN 0 ELSE 1 END
Change the 0 and 1 to whatever you want for false and true.
SELECT t.Id, t.Info,
-- this works in SQL Server
CAST ((CASE WHEN r.UserId IS NOT NULL THEN 1 ELSE 0 END) AS BIT) AS IsRegistered
FROM Tournaments as t
LEFT JOIN Registrations as r ON t.Id = r.TournamentId
where (r.UserId = '' OR r.UserId = #user)
-- i think this one is help for you...
You are looking for this query
SELECT t.id,
t.info,
Cast (CASE
WHEN r.userid IS NOT NULL THEN 1
ELSE 0
END AS BIT) AS IsRegistered
FROM tournaments AS t
LEFT JOIN registrations AS r
ON t.id = r.tournamentid
AND r.userid = #user
You should clarify what SQL language you are actually using, but an answer can be provided anyway:
CREATE PROCEDURE [dbo].[TournamentsWithLoggedUser]
#user nvarchar(128)
AS
BEGIN
SELECT t.Id, t.Info,
-- this works in SQL Server
CAST ((CASE WHEN r.UserId IS NOT NULL THEN 1 ELSE 0 END) AS BIT) AS IsRegistered
FROM Tournaments as t
LEFT JOIN Registrations as r ON t.Id = r.TournamentId
where r.UserId IS NULL OR r.UserId = #user
-- this should not be required
RETURN
END
However, there is a problem with the logic:
#user is not nullable, so your procedure gives the impression that it looks data for a single user. However, your OR operator allows to select all records from unregistered users united with the record for the particular provided user (if exists).

SQL Server - UNION ALL

I'm new to SQL development and I need to do UNION on two select statements. Below is a sample query. The Join tables & conditions, where criteria, columns names and everything is the same in both the select statements except the the primary tables after the FROM clause. I just wanted to know if there is a way to have a single static select query, instead of repeating the same query twice for the UNION (without going for a dynamic query).
SELECT Sum(ABC.Intakes) As TotalIntakes, Sum(ABC.ClientTarget) as TotalClientTarget
FROM(
SELECT Sum(tt.IntakesReceived) As Intakes, Sum(tt.ClientTarget) As ClientTarget,
tt.ProgramId
FROM
(SELECT Count(DISTINCT ClientID) As IntakesReceived,
DATEDIFF(MONTH, L.AwardStartDate, L.AwardEndDate)*L.MonthlyClientTarget As ClientTarget,
L.AwardId, L.ProgramId
FROM IntakeCoverageLegacy As L
LEFT JOIN UserRoleEntity URE ON URE.EntityId = L.AwardId
LEFT JOIN CDPUserRole UR ON URE.UserRoleId = UR.Id AND UR.CDPUserId = #UserId
WHERE (#Program IS NULL OR L.ProgramId IN (SELECT ProgramID FROM #ProgramIDList)
AND (ufn_IsInternalUser(#UserId) = 1
OR (ufn_IsInternalUser(#UserId) = 0 AND UR.CDPUserId = #UserId ))
GROUP BY L.AwardId, L.ProgramId) As tt
GROUP BY tt.ProgramId, tt.ProgramName
UNION ALL
SELECT Sum(tt.IntakesReceived) As Intakes, Sum(tt.ClientTarget) As ClientTarget,
tt.ProgramId
FROM
(SELECT Count(DISTINCT C.ClientID) As IntakesReceived,
DATEDIFF(MONTH, C.AwardStartDate, C.AwardEndDate)*L.MonthlyClientTarget As ClientTarget,
C.AwardId, C.ProgramId
FROM IntakeCoverageCDP As C
LEFT JOIN UserRoleEntity URE ON URE.EntityId = L.AwardId
LEFT JOIN CDPUserRole UR ON URE.UserRoleId = UR.Id AND UR.CDPUserId = #UserId
WHERE (#Program IS NULL OR C.ProgramId IN (SELECT ProgramID FROM #ProgramIDList)
AND (ufn_IsInternalUser(#UserId) = 1
OR (ufn_IsInternalUser(#UserId) = 0 AND UR.CDPUserId = #UserId ))
GROUP BY C.AwardId, C.ProgramId) As tt
GROUP BY tt.ProgramId, tt.ProgramName
) As ABC
GROUP BY ABC.ProgramId
OK... What I posted earlier was a sample query and I've updated the sample to my actual query to make it more clear. It's just the primary tables that are different. My requirement is that - after doing UNION ALL, I need to sum the aggregate columns in the final result, grouping by ProgramId.
I would probably first use UNION for the Client and LegacyClient tables as a derived table and then perform the JOINs:
SELECT C.AwardId,
C.ProgramName,
COUNT(ClientId) AS Intakes
FROM ( SELECT AwardId,
ProgramName,
Id
FROM Client
WHERE Id = #ClientId
UNION
SELECT AwardId,
ProgramName,
Id
FROM LegacyClient
WHERE Id = #ClientId) C
LEFT JOIN UserRoleEntity URE
ON C.AwardId = URE.EntityId
LEFT JOIN UserRole UR
ON URE.UserRoleId = UR.Id AND UR.CDPUserId = #UserId
WHERE (testFunction(#UserId) = 0
OR (testFunction(#UserId) <> 0 AND UR.CDPUserId = #UserId))
GROUP BY C.AwardId,
C.ProgramName;
SELECT C.AwardId, C.ProgramName, Count(ClientId) as Intakes
FROM
(
SELECT Id, AwardId, ProgramName, ClientId FROM Client UNION ALL
SELECT Id, AwardId, ProgramName, ClientId FROM LegacyClient
) C
LEFT OUTER JOIN UserRoleEntity URE ON C.AwardId = URE.EntityId
LEFT OUTER JOIN UserRole UR ON URE.UserRoleId = UR.Id AND UR.CDPUserId = #UserId
WHERE
C.Id = #ClientId
AND (testFunction(#UserId) = 0 OR UR.CDPUserId = #UserId)
GROUP BY C.AwardId, C.ProgramName
Using testFunction() twice isn't really necessary (unless null is one of the outputs.)
You might also prefer to filter on ClientId outside of the union. I'm guess your purpose in rewriting it to avoid the duplicated logic. You might still want to see which one is better handled by the optimizer.
Also, I used a UNION ALL. I'm thinking you imagine only one result from one of the two tables. As you originally wrote it that count column is going to factor into the union.
Counting on ClientId seems odd. So does having a parameter named #ClientId that doesn't seem to match up with the ClientId column.