Rewriting query to prevent multiple calls to a function - sql

How would I rewrite this query to be performant by executing the SQL function only once?
SELECT Top 1 Id, Name
FROM Users U
INNER JOIN UserDetail D on U.Id = D.Id
LEFT OUTER JOIN CreditCards C ON C.Id = U.Id AND UserHasCC(U.Id) = 1
LEFT OUTER JOIN CreditCardDetails CD on C.CCID = C.CCID AND UserHasCC(U.Id) = 1
WHERE
((CD.active = 1 and UserHasCC(U.Id) = 1) OR UserHasCC(U.Id) = 0) and
U.active = 1 and
((C.IsInternational = 1 and UserHasCC(U.Id) = 1) OR UserHasCC(U.id = 0)
Basically, the query gets all users that don't have credit cards and those that have active international credit cards.

Based on your explanation of the intented query behavior, I believe that this might be close:
SELECT Top 1 Id, Name
FROM Users U
INNER JOIN UserDetail D on U.Id = D.Id
LEFT OUTER JOIN CreditCards C ON C.Id = U.Id
LEFT OUTER JOIN CreditCardDetails CD on C.CCID = C.CCID AND CD.Active = 1
WHERE U.Active = 1 and (c.id is null
or (c.IsInternational = 1
and UserHasCC(U.Id) = 1))
I could not be certain but connections and names of objects suggest to me that UserHasCC has no real value. Perhaps it encapsulates flags from tables not shown here.

This looks like it accomplishes the same logic.
SELECT Top 1 Id, Name
FROM Users U
INNER JOIN UserDetail D on U.Id = D.Id
LEFT OUTER JOIN CreditCards C ON C.Id = U.Id
LEFT OUTER JOIN CreditCardDetails CD on C.CCID = C.CCID
WHERE
U.active = 1
AND
(
UserHasCC(U.Id) = 0
OR
(
UserHasCC(U.Id) = 1
and
(
C.IsInternational = 1
OR
CD.active = 1
)
)

Related

Getting extra Row containing Null Value for specific Names

In my Query if CompanyName has Coamount = NULL then Final should be as of Contract amount. If CompanyName has Coamount then Final would be ContractAmount + Coamount.
I am getting all these answers but the query is also returning me rows which has NULL value in Coamount for those company name in which coamount is already present.
for eg: Row1, why is that row coming when I am getting row 2 as my answer according to my query.
Here is the code:
SELECT DISTINCT
HBRegions.RegionName,PropertyGroups.Name AS Owner,Properties.EntityID, Properties.Address,Vendors.CompanyName,
SubContractorDrawDetails.InvoiceUploadDate,SubContractorDrawDetails.InvoiceAmount,SubContractorDrawDetails.PaymentDate,
LienWaivers.FinalLienWaiverRcvdDate,SubContractorDrawDetails.PaymentAmount, subcontractors.ContractAmount,cc.Coamount ,
(subcontractors.ContractAmount + cc.Coamount ) AS ActualContractAmount,
case when cc.Coamount is null then SubContractors.ContractAmount
when CC.Coamount is not null then (subcontractors.ContractAmount + cc.Coamount ) end as Final
FROM Users inner JOIN
Vendors ON Users.UserId = Vendors.UserID inner JOIN
LienWaivers ON Users.UserId = LienWaivers.UserID inner JOIN
Constructions ON LienWaivers.ConstructionID = Constructions.ConstructionId inner JOIN
Properties ON Constructions.HBId = Properties.HBId inner JOIN
ChangeOrderDetails ON Constructions.ConstructionId = ChangeOrderDetails.ConstructionId left JOIN
ChangeOrderLineItems ON Users.UserId = ChangeOrderLineItems.SubContractorId AND
ChangeOrderDetails.ChangeOrderId = ChangeOrderLineItems.ChangeOrderId
left join
(select sum(ChangeOrderDetails.ChangeOrderApprovedAmountContractor) as Coamount, ChangeOrderLineItems.SubContractorId,Constructions.ConstructionId
from ChangeOrderDetails inner join ChangeOrderLineItems
on ChangeOrderDetails.ChangeOrderId = ChangeOrderLineItems.ChangeOrderId
inner join Users on ChangeOrderLineItems.SubContractorId = Users.UserId
left join Constructions on Constructions.ConstructionId = ChangeOrderDetails.ConstructionId
-- left join Properties on Properties.HBId = Constructions.HBId
where ChangeOrderDetails.ChangeOrderApprovedAmountContractor is not null
group by ChangeOrderLineItems.SubContractorId,Constructions.ConstructionId
) AS cc on cc.SubContractorId = ChangeOrderLineItems.SubContractorId and cc.ConstructionId = Constructions.ConstructionId
inner join HBRegions ON Properties.RegionId = HBRegions.RegionID inner JOIN
PropertyGroups ON Properties.PropertyGroup = PropertyGroups.PropertyGroupId
inner join SubContractors ON Users.UserId = SubContractors.UserID AND Properties.HBId = SubContractors.HBId
inner join SubContractorDrawDetails ON SubContractors.SubContractorId = SubContractorDrawDetails.SubContractorId
WHERE (Properties.Address = '470 ROYCROFT BLVD BUFFALO NY 14225')[enter image description here][1]

Select totals to return only one record in SQL Server

After spending more than 3 hours on this I gave up.
I have four tables:
Users, Approvals, Centers, Managements
My ultimate goal is to get the total number of users in each management separated by the user role (I have two roles here : Parents and Society members)
I've been using the following code
select
(select count(r.StudentId)
from Users u
where u.UserId = r.StudentId and u.RoleId = 10) as Parents,
(select count(r.StudentId)
from Users u
where u.UserId = r.StudentId and u.RoleId = 11) as SocietyMembers,
e.ManagementId, e.ManagmentTitle
from
CentersRegistrationsApprovals r --ON r.StudentId = u.UserId
inner join
Centers c ON c.CenterId = r.CenterId
inner join
Managments e ON e.ManagementId = c.EducationManagementId
group by
e.ManagementId, e.ManagmentTitle, StudentId
I'm expecting the query result to be as the following :
Parents SocietyMambers ManagementId ManagementName
----------------------------------------------------------------
3 3 10 North Region
However the result set always gives me
Parents SocietyMambers ManagementId ManagementName
----------------------------------------------------------------
3 NULL 10 North Region
NULL 3 10 North Region
Any ideas how to consolidate the result to only 1 record?
You can query like below:
select
Sum(case when u.roleId = 10 then 1 else 0 end) as Parents,
Sum(case when u.roleId = 11 then 1 else 0 end) as SocietyMembers,
e.ManagementId, e.ManagmentTitle
from
CentersRegistrationsApprovals r --ON r.StudentId = u.UserId
inner join
Centers c ON c.CenterId = r.CenterId
inner join
Managments e ON e.ManagementId = c.EducationManagementId
Join Users u ON r.StudentId = u.UserId
group by
e.ManagementId, e.ManagmentTitle
Please try something like this (not tested)
; with CTE1 as (
select
(select count(r.StudentId)
from Users u
where u.UserId = r.StudentId and u.RoleId = 10) as Parents,
(select count(r.StudentId)
from Users u
where u.UserId = r.StudentId and u.RoleId = 11) as SocietyMembers,
e.ManagementId, e.ManagmentTitle
from
CentersRegistrationsApprovals r --ON r.StudentId = u.UserId
inner join
Centers c ON c.CenterId = r.CenterId
inner join
Managments e ON e.ManagementId = c.EducationManagementId
group by
e.ManagementId, e.ManagmentTitle, StudentId
)
SELECT MAX(Parents), MAX(SocietyMembers), ManagementId, StudentId
FROM CTE1
GROUP BY ManagementId, StudentId

Get total count using a window function

Hey all this is the query I have so far:
WITH LIMIT AS
(SELECT
U.userID
,U.username
,U.fname
,U.mname
,U.lname
,U.email
,U.active
,S.sName
,S.sID
,T.[value]
,T.trackingNumberID
,SU.primaryLocation
,row_number() OVER (ORDER BY U.userid) AS RN
,COUNT(*) OVER (ORDER BY U.userid) AS CNT
,UR.roleID
FROM
[---].[dbo].[tblUsers] AS U
LEFT OUTER JOIN [---].[dbo].[tblTrackingNumbers] AS T
ON T.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblSU] AS SU
ON U.userID = SU.userID
LEFT OUTER JOIN [---].[dbo].[tblS] AS S
ON SU.sID = S.sID
LEFT OUTER JOIN [---].[dbo].[tblUserRoles] AS UR
ON UR.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblRoles] AS R
ON UR.roleID = R.roleID
WHERE
U.active = 1
AND
SU.primaryLocation = 1
AND
SU.active = 1
AND
U.orgID = 1
AND
S.ID = 35
AND U.userID IN (SELECT userID
FROM [---].[dbo].[tblSU] AS SU
INNER JOIN [].[dbo].[tblS] AS S
ON S.sID = SU.sID
WHERE
SU.active = 1
AND
S.sID = 35)
) SELECT * FROM LIMIT WHERE RN Between 0 AND 10000
As you can see by the query above I am trying COUNT(*) OVER (ORDER BY U.userid) AS CNT which gives me the same count as RN.
What I need is the total amount of records this would be bringing back (842 rows).
COUNT(*) OVER (ORDER BY U.userid) AS CNT calulates a "running count" - the count until "that" row. If you want to count all rows in the complete result, use the window function without the order by
COUNT(*) OVER () AS CNT
this might sound cuckoo, but i found with large tables you get better performance if you select the count into a variable and then select your records and just add the variable. something with the count(*) over() causes bad performance when tables get too large.
DECLARE #RecordCount INT
SELECT #RecordCount = COUNT(*)
FROM [---].[dbo].[tblUsers] AS U
LEFT OUTER JOIN [---].[dbo].[tblTrackingNumbers] AS T ON T.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblSU] AS SU ON U.userID = SU.userID
LEFT OUTER JOIN [---].[dbo].[tblS] AS S ON SU.sID = S.sID
LEFT OUTER JOIN [---].[dbo].[tblUserRoles] AS UR ON UR.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblRoles] AS R ON UR.roleID = R.roleID
WHERE U.active = 1
AND SU.primaryLocation = 1
AND SU.active = 1
AND U.orgID = 1
AND S.ID = 35
AND U.userID IN (SELECT userID
FROM [---].[dbo].[tblSU] AS SU
INNER JOIN [].[dbo].[tblS] AS S ON S.sID = SU.sID
WHERE SU.active = 1
AND S.sID = 35)
SELECT U.userID,
U.username,
U.fname,
U.mname,
U.lname,
U.email,
U.active,
S.sName,
S.sID,
T.[value],
T.trackingNumberID,
SU.primaryLocation,
#RecordCount AS CNT,
UR.roleID
FROM [---].[dbo].[tblUsers] AS U
LEFT OUTER JOIN [---].[dbo].[tblTrackingNumbers] AS T ON T.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblSU] AS SU ON U.userID = SU.userID
LEFT OUTER JOIN [---].[dbo].[tblS] AS S ON SU.sID = S.sID
LEFT OUTER JOIN [---].[dbo].[tblUserRoles] AS UR ON UR.userID = U.userID
LEFT OUTER JOIN [---].[dbo].[tblRoles] AS R ON UR.roleID = R.roleID
WHERE U.active = 1
AND SU.primaryLocation = 1
AND SU.active = 1
AND U.orgID = 1
AND S.ID = 35
AND U.userID IN (SELECT userID
FROM [---].[dbo].[tblSU] AS SU
INNER JOIN [].[dbo].[tblS] AS S ON S.sID = SU.sID
WHERE SU.active = 1
AND S.sID = 35)
ORDER BY U.userID
OFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY

Optimize sql with unions

I try to insert data in a temp table but i cant get it to work.
I try to select 1000 items from each select.
INSERT INTO #Data(user_id, created_at)
SELECT TOP(1000) u.id, c.created_at
FROM comment as c
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0 and o.user_id = 645 AND c.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
UNION
SELECT TOP(1000) u.id, l.created_at
FROM like as l
inner join outfit as o on o.id = l.outfit_id and o.deleted = 0 and o.user_id = 645
inner join user as u on u.id = l.user_id and u.is_active = 1
UNION
SELECT TOP(1000) u.id, f.created_at
FROM friend_user as f
inner join user as u on u.id = f.user_id and u.is_active = 1
where f.friend_id = 645 AND f.approved = 1
UNION
SELECT TOP(1000) u.id, c.created_at
FROM comment_tagged_user AS T
INNER JOIN comment as c ON c.id = T.comment_id and T.user_id = 645 AND c.deleted = 0
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
ORDER BY o.created_at DESC
Now i try to select total 1000 rows of the same sql above. (I remove the TOP 1000 on each SELECT)
INSERT INTO #Data(user_id, created_at)
SELECT TOP 1000 * FROM (
SELECT u.id, c.created_at
FROM comment as c
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0 and o.user_id = 645 AND c.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
UNION
SELECT u.id, l.created_at
FROM like_like as l
inner join outfit as o on o.id = l.outfit_id and o.deleted = 0 and o.user_id = 645
inner join user as u on u.id = l.user_id and u.is_active = 1
UNION
SELECT u.id, f.created_at
FROM friend_user as f
inner join user as u on u.id = f.user_id and u.is_active = 1
where f.friend_id = 645 AND f.approved = 1
UNION
SELECT u.id, c.created_at
FROM comment_tagged_user AS T
INNER JOIN comment as c ON c.id = T.comment_id and T.user_id = 645 AND c.deleted = 0
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
) AS Data ORDER BY created_at DESC
But the result is not the same. This is what i get:
Left image is from the first sql. The second image shows the correct result. But, the second SQL takes 7,8s, the first SQL takes only 0.7s.
So, what am i doing wrong in the first sql? Should i not see the same result in the beginning of the list?
I use Azure Sql
With 4000 rows, you are sorting on 4000 rows, after ONLY the first 1000 of each of the 4 tables have been retrieved, so on the 1st query, the top 1000 rows of each table, with NO sorting, are returned, then the sorting is performed on that 4000 row result. On the second query, all resulting rows are returned, and then sorting is performed, to return only the top 1000 rows.
This explains both the difference in output AND the difference in performance.
In your first query, each part of the union is not ordered. If you want them to be ordered you have to put them in sub-queries like this:
INSERT INTO #Data (user_id, created_at)
select top 1000 *
from
(
(
SELECT TOP(1000) u.id, c.created_at
FROM comment as c
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0 and o.user_id = 645 AND c.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
order by created_at DESC
)
UNION
(
SELECT TOP(1000) u.id, l.created_at
FROM like as l
inner join outfit as o on o.id = l.outfit_id and o.deleted = 0 and o.user_id = 645
inner join user as u on u.id = l.user_id and u.is_active = 1
order by created_at DESC
)
UNION
(
SELECT TOP(1000) u.id, f.created_at
FROM friend_user as f
inner join user as u on u.id = f.user_id and u.is_active = 1
where f.friend_id = 645 AND f.approved = 1
order by created_at DESC
)
UNION
(
SELECT TOP(1000) u.id, c.created_at
FROM comment_tagged_user AS T
INNER JOIN comment as c ON c.id = T.comment_id and T.user_id = 645 AND c.deleted = 0
inner join outfit as o on o.id = c.outfit_id and o.deleted = 0
inner join user as u on u.id = c.user_id and u.is_active = 1
order by created_at DESC
)
) Data
ORDER BY created_at DESC
This query should return equivalent results as your second query.

SQL Counting # of times a member shows up with certain code values

I am running a query that returns the location of a member and the product the member is enrolled in. Each time a member makes a claim with their product, they get a revenue code associated to them. Below is my query that I have now:
SELECT DISTINCT
e.State,
f.Product,
d.MemberID,
b.RevenueCode
FROM
Claims a
INNER JOIN
dw.Revenue b
ON
a.RevenueKey = b.RevenueKey
INNER JOIN
dw.Member d
ON
a.MemberKey = d.MemberKey
INNER JOIN
dw.Product f
ON
a.ProductKey = f.ProductKey
INNER JOIN
dw.State
ON
a.StateKey = f.StateKey
WHERE
b.RevenueCode IN ('0134', '0135')
It returns a set like the following:
State Product MemberID RevenueCode
MN xxx 945-234-245 0134
MN xxx 945-234-245 0135
SD xxx 231-345-235 0134
When a MemberID has both 0134 and 0135 RevenueCodes associated with it, they are considered to be in a special category. How would I modify my above query to count the number of times a MemberID has both RevenueCodes by State and by Product?
SELECT DISTINCT
e.State
,f.Product
,d.MemberID
,b.RevenueCode
,(SELECT 1
FROM Claims AS a1
INNER JOIN dw.Revenue AS b1 ON a1.RevenueKey = b1.RevenueKey
WHERE b1.RevenueCode IN ('0134', '0135')
AND b.revenuekey = b1.revenuekey
AND a.MemberKey = a1.Memberkey
HAVING COUNT(DISTINCT b1.RevenueCode) = 2) AS SpecialCategory
FROM Claims a
INNER JOIN dw.Revenue b ON a.RevenueKey = b.RevenueKey
INNER JOIN dw.Member d ON a.MemberKey = d.MemberKey
INNER JOIN dw.Product f ON a.ProductKey = f.ProductKey
INNER JOIN dw.State ON a.StateKey = f.StateKey
WHERE b.RevenueCode IN ('0134', '0135')
SELECT DISTINCT
e.State,
f.Product,
d.MemberID,
b.RevenueCode.
CASE WHEN EXISTS(
SELECT NULL
FROM Claims a1
JOIN dw.Revenue b1 ON a1.RevenueKey = b1.RevenueKey
JOIN dw.Member d1 ON a1.MemberKey = d1.MemberKey
JOIN dw.Product f1 ON a1.ProductKey = f1.ProductKey
WHERE b1.RevenueCode IN('0134', '0135') AND
d1.MemberID = d.MemberID AND
f1.ProductKey = f.ProductKey AND
f1.StateKey = f.StateKey
) THEN 1 ELSE 0 END As IsSpecialCategory
FROM
...
Figured it out...Simply needed to Count the Distinct RevenueCodes and Group By State, Product, and MemberID