Combining two counts with joins in one query - sql

Ok - hoping someone can help as I'm struggling here. Have tried using sum(case when), count(*), subqueries with no success.
Essentially I have two queries I am trying to combine into one, grouped by the same parameter. The queries:
Query 1:
SELECT
u.username,
count(*) as 'Total'
FROM log1 L1
JOIN users u on u.userref = L1.userref
WHERE L1.date between #FromDate and #ToDate
GROUP BY u.username
ORDER BY u.username
Query 2:
SELECT
u.username,
count(*) as 'Total'
FROM log2 L2
LEFT JOIN users u on u.userref = L2.userref
WHERE L2.date between #FromDate and #ToDate and L2.message like '%None%'
GROUP BY u.username
ORDER BY u.username
What I would like is a single query with a username column (u.username), a column showing the results of query 1, and a column showing the results of query two. Any help appreciated!

You can put a case statement inside the count function to only count when certain criteria is met:
SELECT u.username,
Total = COUNT(*),
Total2 = COUNT(CASE WHEN L.message LIKE '%None%' THEN 1 END)
FROM log1 AS L
JOIN users AS u
ON u.userref = L.userref
WHERE L.date BETWEEN #FromDate and #ToDate
GROUP BY u.username
ORDER BY u.username;
Of note:
BETWEEN can cause problems, especially when working with dates
Using string literals for column aliases is on the deprecation list
EDIT
Completely missed that you had two separate log tables:
SELECT u.username,
Total = COUNT(Count1),
Total2 = COUNT(Count2)
FROM ( SELECT l.UserRef, Count1 = 1, Count2 = NULL
FROM log1 AS L
WHERE L.date BETWEEN #FromDate and #ToDate
UNION ALL
SELECT l.UserRef, Count1 = NULL, Count2 = 1
FROM log2 AS L
WHERE L.date BETWEEN #FromDate and #ToDate
AND L.message LIKE '%None%'
) AS l
JOIN users AS u
ON u.userref = L.userref
GROUP BY u.username
ORDER BY u.username;

SELECT X.USERNAME,X.TOTAL,Y.TOTAL
FROM(SELECT
u.username,
count(*) as 'Total'
FROM log1 L1
JOIN users u on u.userref = L1.userref
WHERE L1.date between #FromDate and #ToDate
GROUP BY u.username
) as x
INNER JOIN
(
SELECT
u.username,
count(*) as 'Total'
FROM log2 L2
LEFT JOIN users u on u.userref = L2.userref
WHERE L2.date between #FromDate and #ToDate and L2.message like '%None%'
GROUP BY u.username
) AS Y ON Y.USERNAME = X.USERNAME

Related

How to convert rows in columns

I have some problems with a SQL query, I try to convert row in columns.
select U.FirstName,U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime from dbo.[User] as U
inner join dbo.Employee as E on U.UserId = E.UserId
inner join dbo.ScheduleStaff as SH on SH.EmployeeId = E.EmployeeId
where ((Sh.[Day] < getdate()+ 4 ) and sh.[Day] >= getdate())
group by U.FirstName, U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime
This query gives me the below result:
How can I make that there is one employee per line, and the days turn into columns?
I try this:
select FirstName,LastName,Day1,Day2,Day3
from
(
select U.FirstName,U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime,concat('Day',ROW_NUMBER() over (partition by
U.FirstName,U.LastName order by U.FirstName,U.LastName)) as Tests
from dbo.[User] as U
inner join dbo.Employee as E on U.UserId = E.UserId
inner join dbo.ScheduleStaff as SH on SH.EmployeeId = E.EmployeeId
where ((Sh.[Day] < getdate()+ 4 ) and sh.[Day] >= getdate()-1)
group by U.FirstName, U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime
)Temp
pivot
(
Max([Day])
for Tests in (Day1,Day2,Day3)
)Piv
But this is not exactly what I need, how can I make the days as columns and their time as values?
This will work if you have static [Date] value. But if you want [Date] dynamic then you need to collect date value and put into pivot section.
Your question was[how can I make the days as columns and their time as values]
select FirstName,LastName,[2020-08-28],[2020-08-29] from
(select FirstName,LastName,[Date], CONCAT(StartTime,'-to-'+EndTime) as [Time] from tbl_user) t
pivot(MAX(t.[Time]) for [Date] in([2020-08-28],[2020-08-29]) )pvt
Note: for dynamic date value. You can follow Terence link mentioned in comment section.

SQL : Get Column table twice with differents clause where

I try to get the same column in the same table twice with different clauses :
My query:
SELECT
*
FROM
(SELECT TOP 10
CONVERT(DATE, attemptdate) AS Date,
Max(currentcount) AS A
FROM
logintracking
INNER JOIN
maxuser ON logintracking.loginid = maxuser.loginid
INNER JOIN
site ON site.siteid = maxuser.defsite
WHERE
attemptdate BETWEEN #dateDebut AND #dateFin
AND logintracking.clientaddr IN ('10.118.254.21', '10.118.254.156')
GROUP BY
CONVERT(DATE, attemptdate)
ORDER BY
CONVERT(DATE, attemptdate) ASC
) AS T1,
(SELECT TOP 10
CONVERT(DATE, attemptdate) AS Date,
MAX(currentcount) AS B
FROM
logintracking
INNER JOIN
maxuser ON logintracking.loginid = maxuser.loginid
INNER JOIN
site ON site.siteid = maxuser.defsite
WHERE
attemptdate BETWEEN #dateDebut AND #dateFin
AND logintracking.clientaddr = '10.118.254.35'
GROUP BY
CONVERT(DATE, attemptdate)
ORDER BY
CONVERT(DATE, attemptdate) ASC) AS T2
Result:
Desired result:
My objective is to get the same column 'max(currentcount)' twice and to apply different where clauses so to get two columns named (A & B), and i need also to show the date in the first column, can you please help ? Thanks
Since the only difference between A and B is logintracking.clientaddr, you can put that condition within a CASE statement within the MAX function:
SELECT CONVERT(DATE, attemptdate) AS Date,
MAX(CASE WHEN logintracking.clientaddr IN ( '10.118.254.21', '10.118.254.156' ) THEN currentcount END) AS A,
MAX(CASE WHEN logintracking.clientaddr IN ( '10.118.254.35' ) THEN currentcount END) AS B
FROM logintracking
INNER JOIN maxuser
ON logintracking.loginid = maxuser.loginid
INNER JOIN site
ON site.siteid = maxuser.defsite
WHERE attemptdate BETWEEN #dateDebut AND #dateFin
GROUP BY CONVERT(DATE, attemptdate)
ORDER BY CONVERT(DATE, attemptdate) ASC

Confusing sum result

I've 3 tables as below.
Users
StatusTable
Time_Tracker
And the relation is as below.
users have username and userid, StatusTable has userName and Time_Tracker has userName
Below is my table Data.
And here I'm trying to get the sum
Problem:
I want to update the productiontime as sum of time taken[minutes] by joining case owner to username and TT.userId = Users.userId and the login = GetDate()
My Query is
update Time_Tracker set ProductionTime = (select sum(ST.[time taken(minutes)])
from statustable as st inner join users as u
on st.[case owner] = u.username
inner join Time_Tracker as TT
on u.userId = TT.userId
where cast(st.[Start Time] AS DATE) = CAST(GETDATE() as Date)
Group By TT.UserId, u.UserId) where CAST(Login AS DATE) = CAST(GETDATE() as Date)
And the blue box in my image is my current O/p.
When I run
select sum([time taken(minutes)]) as totalTme from StatusTable where cast([Start Time] AS DATE) = CAST(GETDATE() as Date)
I get the O/p as 2.05 which is correct one.
please let me know where am I going wrong in my first query(printing 10.25) instead of 2.05.
How can I fix this.
Thanks
I think you have too many joins in the subquery:
update Time_Tracker tt
set ProductionTime = (select sum(ST.[time taken(minutes)])
from statustable st inner join
users u
on st.[case owner] = u.username
where u.userId = tt.userId and
cast(st.[Start Time] AS DATE) = CAST(GETDATE() as Date)
)
where CAST(Login AS DATE) = CAST(GETDATE() as Date);
Your version is multiplying the result for each row in TimeTracker. The correlation clause is what you need.
Also very important. Such a correlated subquery should not have a GROUP BY clause. The GROUP BY could return multiple rows, but by definition the correlated subquery should return at most one row.

Remove grouped data set when total of count is zero with subquery

I'm generating a data set that looks like this
category user total
1 jonesa 0
2 jonesa 0
3 jonesa 0
1 smithb 0
2 smithb 0
3 smithb 5
1 brownc 2
2 brownc 3
3 brownc 4
Where a particular user has 0 records in all categories is it possible to remove their rows form the set? If a user has some activity like smithb does, I'd like to keep all of their records. Even the zeroes rows. Not sure how to go about that, I thought a CASE statement may be of some help but I'm not sure, this is pretty complicated for me. Here is my query
SELECT DISTINCT c.category,
u.user_name,
CASE WHEN (
SELECT COUNT(e.entry_id)
FROM category c1
INNER JOIN entry e1
ON c1.category_id = e1.category_id
WHERE c1.category_id = c.category_id
AND e.user_name = u.user_name
AND e1.entered_date >= TO_DATE ('20140625','YYYYMMDD')
AND e1.entered_date <= TO_DATE ('20140731', 'YYYYMMDD')) > 0 -- I know this won't work
THEN 'Yes'
ELSE NULL
END AS TOTAL
FROM user u
INNER JOIN role r
ON u.id = r.user_id
AND r.id IN (1,2),
category c
LEFT JOIN entry e
ON c.category_id = e.category_id
WHERE c.category_id NOT IN (19,20)
I realise the case statement won't work, but it was an attempt on how this might be possible. I'm really not sure if it's possible or the best direction. Appreciate any guidance.
Try this:
delete from t1
where user in (
select user
from t1
group by user
having count(distinct category) = sum(case when total=0 then 1 else 0 end) )
The sub query can get all the users fit your removal requirement.
count(distinct category) get how many category a user have.
sum(case when total=0 then 1 else 0 end) get how many rows with activities a user have.
There are a number of ways to do this, but the less verbose the SQL is, the harder it may be for you to follow along with the logic. For that reason, I think that using multiple Common Table Expressions will avoid the need to use redundant joins, while being the most readable.
-- assuming user_name and category_name are unique on [user] and [category] respectively.
WITH valid_categories (category_id, category_name) AS
(
-- get set of valid categories
SELECT c.category_id, c.category AS category_name
FROM category c
WHERE c.category_id NOT IN (19,20)
),
valid_users ([user_name]) AS
(
-- get set of users who belong to valid roles
SELECT u.[user_name]
FROM [user] u
WHERE EXISTS (
SELECT *
FROM [role] r
WHERE u.id = r.[user_id] AND r.id IN (1,2)
)
),
valid_entries (entry_id, [user_name], category_id, entry_count) AS
(
-- provides a flag of 1 for easier aggregation
SELECT e.[entry_id], e.[user_name], e.category_id, CAST( 1 AS INT) AS entry_count
FROM [entry] e
WHERE e.entered_date BETWEEN TO_DATE('20140625','YYYYMMDD') AND TO_DATE('20140731', 'YYYYMMDD')
-- determines if entry is within date range
),
user_categories ([user_name], category_id, category_name) AS
( SELECT u.[user_name], c.category_id, c.category_name
FROM valid_users u
-- get the cartesian product of users and categories
CROSS JOIN valid_categories c
-- get only users with a valid entry
WHERE EXISTS (
SELECT *
FROM valid_entries e
WHERE e.[user_name] = u.[user_name]
)
)
/*
You can use these for testing.
SELECT COUNT(*) AS valid_categories_count
FROM valid_categories
SELECT COUNT(*) AS valid_users_count
FROM valid_users
SELECT COUNT(*) AS valid_entries_count
FROM valid_entries
SELECT COUNT(*) AS users_with_entries_count
FROM valid_users u
WHERE EXISTS (
SELECT *
FROM user_categories uc
WHERE uc.user_name = u.user_name
)
SELECT COUNT(*) AS users_without_entries_count
FROM valid_users u
WHERE NOT EXISTS (
SELECT *
FROM user_categories uc
WHERE uc.user_name = u.user_name
)
SELECT uc.[user_name], uc.[category_name], e.[entry_count]
FROM user_categories uc
INNER JOIN valid_entries e ON (uc.[user_name] = e.[user_name] AND uc.[category_id] = e.[category_id])
*/
-- Finally, the results:
SELECT uc.[user_name], uc.[category_name], SUM(NVL(e.[entry_count],0)) AS [entry_count]
FROM user_categories uc
LEFT OUTER JOIN valid_entries e ON (uc.[user_name] = e.[user_name] AND uc.[category_id] = e.[category_id])
Here's another method:
WITH totals AS (
SELECT
c.category,
u.user_name,
COUNT(e.entry_id) AS total,
SUM(COUNT(e.entry_id)) OVER (PARTITION BY u.user_name) AS user_total
FROM
user u
INNER JOIN
role r ON u.id = r.user_id
CROSS JOIN
category c
LEFT JOIN
entry e ON c.category_id = e.category_id
AND u.user_name = e.user_name
AND e1.entered_date >= TO_DATE ('20140625', 'YYYYMMDD')
AND e1.entered_date <= TO_DATE ('20140731', 'YYYYMMDD')
WHERE
r.id IN (1, 2)
AND c.category_id IN (19, 20)
GROUP BY
c.category,
u.user_name
)
SELECT
category,
user_name,
total
FROM
totals
WHERE
user_total > 0
;
The totals derived table calculates the totals per user and category as well as totals across all categories per user (using SUM() OVER ...). The main query returns only rows where the user total is greater than zero.

Multiple counts on different tables in same query

We have a tool that allows users to create their own groups. Within these groups, users can write posts. What I am trying to determine is the relationship between size of the group and total number of posts in that group.
I can do SQL statements to get a list of group names and the number of users in that group (Query 1) and a list of group names and the number of posts (Query 2) but I would like for both to be in the same query.
Query 1
select count(pg.personID) as GroupSize, g.GroupName
from Group g inner join PersonGroup pg g.GroupID = pg.GroupID
where LastViewed between #startDate and #enddate and
g.Type = 0
group by g.GroupID, g.GroupName
order by GroupSize
Query 2
select count(gp.PostID) as TotalPosts, g.GroupName
from Group g inner join GroupPost gp on g.GroupID = gp.GroupID
inner join Post p on gp.PostID = p.PostID
where g.Type = 0 and
gp.Created between #startDate and #enddate
group by g.GroupID, g.GroupName
order by TotalPosts
**Note: A person can post the same "post" to multiple groups
I believe from this data I could build a Histogram (# of groups with 10-20 users, 21-30 users, etc..) and incorporate average number of posts for groups in those different bins.
A simple solution would be to use those queries as Sub queries, and combine them:
SELECT
grps.GroupName,
grps.GroupSize,
psts.TotalPosts
FROM (
select count(pg.personID) as GroupSize, g.GroupName, g.GroupID
from Group g inner join PersonGroup pg g.GroupID = pg.GroupID
where LastViewed between #startDate and #enddate and
g.Type = 0
group by g.GroupID, g.GroupName
order by GroupSize) grps
JOIN (
select count(gp.PostID) as TotalPosts, g.GroupName, g.groupID
from Group g inner join GroupPost gp on g.GroupID = gp.GroupID
inner join Post p on gp.PostID = p.PostID
where g.Type = 0 and
gp.Created between #startDate and #enddate
group by g.GroupID, g.GroupName
order by TotalPosts) psts
ON psts.GroupID = grps.GroupID
Paul's solution assumes that the two sets of groups (by posts and by users) is the same. This may not be true, so either a full outer join or union all is needed.
My preference is the following:
with groups as
(
select *
from Group g
where g.Type = 0
and g.LastViewed between #startDate and #enddate
)
select GroupId, GroupName, SUM(GroupSize) as GroupSize, SUM(TotalPosts) as TotalPosts)
from
(
(select groups.GroupId, groups.GroupName, 1 as GroupSize, 0 as TotalPosts
from groups
join PersonGroup pg
on pg.GroupId = groups.groupId
)
union all
(select groups.GroupId, groups.GroupName, 0 as GroupSize, 1 as TotalPosts
from groups
join GroupPost gp
on groups.GroupId = gp.GroupId
join Post p
on gp.PostId = p.PostId
)
)
group by GroupId, GroupName
The "with" clause defines the set of groups that you are using. This places the definition in one place, making it obvious that the two subqueries have the same filtering. The two subqueries simply have flags indicating each of the two variables, which are then aggregated at the higher level. Sometimes it is more efficient to also do the aggregation inside the subqueries, particularly when there are indexes.