Get total count using a window function - sql

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

Related

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

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 Server : JOIN query

I'm newbie in SQL Server and I need some help with SQL Server and JOIN method.
My code is:
SELECT TOP 1000
p.value AS userposition,
p2.value AS usercell,
t.id
FROM
[es] t
JOIN
[user] u ON t.user_uid = u.uid
JOIN
[user] su ON u.superior_uid = su.uid
JOIN
[user_params] up ON t.user_uid = up.user_uid
LEFT JOIN
[params] p ON up.param_id = p.id AND p.name_id = 1
JOIN
[user_params] up2 ON t.user_uid = up2.user_uid
LEFT JOIN
[params] p2 ON up2.param_id = p2.id AND p.name_id = 2
but it returns duplicated records. I want them just as many as rows in [es] table. In MySQL I would use GROUP BY t.id, but in SQL Server that method doesn't work.
Thanks in advance.
EDIT (clarification):
Thank you for your replies. Maybe I should describe my tables structure and what I need to display.
Table [ES]
[id],[user_uid],[more_data]
Table [User]
[uid],[superior_uid],[more_data]
Table [UserParams]
[id],[user_uid],[param_id]
Table [Params]
[id],[param_id],[value]
Now what I need is to get all records from [ES] add user data from [User] add his superior data on [User][superior_uid] which is also an [User] record, add [Params] with [Params][name_id] = 1 as value1 AND add [Params] with [Params][name_id] = 2 as value2 ... through [UserParams] if exists.
I think the problem is with JOIN or GROUP BY. [ES] records with users has no [UserParams] are shown only once, but those with [UserParams] are doubled.I tried LEFT OUTER JOIN but it doesn't work. :(
How about
SELECT DISTINCT TOP 1000
p.value AS userposition,
p2.value AS usercell,
t.id
FROM [es] t
JOIN [user] u ON t.user_uid = u.uid
JOIN [user] su ON u.superior_uid = su.uid
JOIN [user_params] up ON t.user_uid = up.user_uid
LEFT JOIN [params] p ON up.param_id = p.id AND p.name_id = 1
JOIN [user_params] up2 ON t.user_uid = up2.user_uid
LEFT JOIN [params] p2 ON up2.param_id = p2.id AND p.name_id = 2
ORDER BY (whichever rows that you want it to be ordered by) ?
all of your columns need to be in the group by, or part of an aggregate function
p.value AS userposition, #group by or agg func
p2.value AS usercell, #group by or agg func
t.id #group by
Wouldnt be certain without knowing what p.value and p2.value actually mean
Not to sure about the joins but I guess you know what you doing, to select distinct row you can use row_number() function as below
SELECT userposition
,usercell
,id
FROM (
SELECT TOP 1000
p.value AS userposition
,p2.value AS usercell
,t.id
,ROW_NUMBER() OVER (PARTITION BY t.user_uid ORDER BY t.user_uid) rn
FROM [es] t
INNER JOIN [user] u ON t.user_uid = u.[uid]
INNER JOIN [user] su ON u.superior_uid = su.[uid]
INNER JOIN [user_params] up ON t.user_uid = up.user_uid
LEFT JOIN [params] p ON up.param_id = p.id AND p.name_id = 1
INNER JOIN [user_params] up2 ON t.user_uid = up2.user_uid
LEFT JOIN [params] p2 ON up2.param_id = p2.id AND p.name_id = 2
) A
WHERE A.rn = 1
You can get rid of the last 2 joins by combining them, use DISTINCT to not get repeated entries, and use ORDER BY to sort it.
SELECT DISTINCT TOP 1000
p.value AS userposition,
p2.value AS usercell,
t.id
FROM [es] t
JOIN [user] u ON t.user_uid = u.uid
JOIN [user] su ON u.superior_uid = su.uid
JOIN [user_params] up ON t.user_uid = up.user_uid
LEFT JOIN [params] p ON up.param_id = p.id AND (p.name_id = 1 OR p.name_id = 2)
ORDER BY t.id

sql group by clause error

This is the error
Msg 8120, Level 16, State 1, Procedure FollowingUpdates, Line 10
Column 'TopicsComplete.TopicCreationDate' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
This is after adding these 2 lines, I need to count a separate table rows (the amount of rows not the count of the topicid) and include in result any ideas? thanks
,COUNT(DISTINCT MC.topicid) AS NewMessagesCount
LEFT OUTER JOIN Messages AS MC ON MC.TopicId = T.TopicId AND MC.userid = #id
#id int = null
,#UserGroupId int = null
AS
SELECT
*
FROM
(SELECT
ROW_NUMBER()
OVER ( ORDER BY TopicOrder desc
, CASE WHEN M.MessageCreationDate > T.TopicCreationDate
THEN M.MessageCreationDate
ELSE T.TopicCreationDate
END desc )
AS RowNumber,
T.TopicId, T.TopicTitle, T.TopicShortName, T.TopicDescription, T.TopicCreationDate, T.TopicViews, T.TopicReplies, T.UserId, T.TopicTags, T.TopicIsClose, T.TopicOrder, T.LastMessageId, T.UserName, M.MessageCreationDate, T.ReadAccessGroupId, T.PostAccessGroupId, U.UserGroupId, U.UserPhoto, T.UserFullName ,M.UserId AS MessageUserId ,MU.UserName AS MessageUserName
,COUNT(DISTINCT MC.topicid) AS NewMessagesCount
FROM TopicsComplete AS T
LEFT OUTER JOIN Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1
LEFT OUTER JOIN Messages AS MC ON MC.TopicId = T.TopicId AND MC.userid = #id
INNER JOIN Users AS U ON U.UserId = T.UserId
LEFT JOIN Users MU ON MU.UserId = M.UserId
WHERE EXISTS
(SELECT * FROM TopicsComplete
LEFT OUTER JOIN Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1 INNER JOIN
topicfollows AS TF ON T.TopicId != TF.topicid INNER JOIN
Users AS U ON U.UserId = T.UserId LEFT OUTER JOIN
Users AS MU ON MU.UserId = M.UserId
WHERE (T.UserId = #id)
UNION SELECT * FROM TopicsComplete
LEFT OUTER JOIN Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1 INNER JOIN
topicfollows AS TF ON T.TopicId = TF.topicid INNER JOIN
Users AS U ON U.UserId = T.UserId LEFT JOIN
Users MU ON MU.UserId = M.UserId
WHERE (TF.userid = #id)
)
) T
When you have an aggregation function in the select, SQL Server quite reasonably assumes that you want to do an aggregation. All columns not in aggregation functions should then be in the group by clause.
In your case, you have COUNT(DISTINCT MC.topicid) AS NewMessagesCount in a select expression. All the other columns should be in the group by. There is no group by, but you get the error anyway, because one should be there.
You need to have any column not contained in an aggregate (max, min, count, sum, etc.) in the GROUP BY clause.

sql union with different expressions

I have this sql which doesnt work as both unions have different expressions, how can this be done? thanks (mssql stored procedure) have no idea where to go from here
SELECT
*
FROM
(SELECT
ROW_NUMBER()
OVER
(ORDER BY T.TopicCreationDate desc)
AS RowNumber
,T.TopicId, T.TopicTitle, T.TopicShortName, T.TopicDescription, T.TopicCreationDate, T.TopicViews, T.TopicReplies, T.UserId, T.TopicTags, T.TopicIsClose,
T.TopicOrder, T.LastMessageId, T.UserName, M.MessageCreationDate, T.ReadAccessGroupId, T.PostAccessGroupId, U.UserGroupId, U.UserPhoto, T.UserFullName
,M.UserId AS MessageUserId
,MU.UserName AS MessageUserName
FROM TopicsComplete AS T LEFT OUTER JOIN
Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1 INNER JOIN
Users AS U ON U.UserId = T.UserId
LEFT JOIN Users MU ON MU.UserId = M.UserId
WHERE EXISTS
( SELECT *
FROM TopicsComplete
LEFT OUTER JOIN
Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1 INNER JOIN
Users AS U ON U.UserId = T.UserId LEFT OUTER JOIN
Users AS MU ON MU.UserId = M.UserId
WHERE (T.UserId = #id)
UNION
SELECT *
FROM TopicsComplete
LEFT OUTER JOIN
Messages AS M ON M.TopicId = T.TopicId AND M.MessageId = T.LastMessageId AND M.Active = 1 INNER JOIN
topicfollows AS TF ON T.TopicId = TF.topicid INNER JOIN
Users AS U ON U.UserId = T.UserId LEFT JOIN
Users MU ON MU.UserId = M.UserId
WHERE (TF.userid = #id)
)
) T
All the columns returned by both sides of the union have to match (the number of columns and their data types, not the actual names/aliases).
First of all, don't specify the returned columns with SELECT *. It's a bad habit that leads to problems, e.g. when you add a new column to one of the tables.
Secondly, if one part of the union doesn't have enough columns, specify some constants to provide values for those columns (adding an alias for clarity), e.g.
SELECT 0 AS RowNumber, T.TopicId, T.TopicTitle, T.TopicShortName, T.TopicDescription, T.TopicCreationDate, T.TopicViews, T.TopicReplies, T.UserId, T.TopicTags, T.TopicIsClose,
T.TopicOrder, T.LastMessageId, T.UserName, M.MessageCreationDate, T.ReadAccessGroupId, T.PostAccessGroupId, U.UserGroupId, U.UserPhoto, T.UserFullName