SQL Statement that Subs 0 for no Results - sql

I am having an issue getting the results I want from my SQL statement. I know I'm probably missing something simple but I just can't see it.
Here are my tables.
Table: Users (RoleID is linked to ID in Roles Table)
ID, FirstName, LastName, RoleID
1, Matt, Ryan, 1
2, Chipper, Jones, 1
3, Julio, Jones, 2
4, Jason, Bourn, 3
Table: Roles
ID, Name
1, Field Rep
2, Tech
3, Admin
Table: FRrequests (UserID is linked to ID in Users table)
ID, UserID, Status
1, 1, Open
2, 1, Submitted
3, 1, Delayed
4, 1, Complete
What I want is an SQL statement that shows me a count of all the "Submitted" & "Delayed" requests for all the Field Reps. Below is an example of desired results.
Name Count
Chipper Jones 0
Matt Ryan 2
Here is the statement I have so far and the results it gives me.
SELECT Users.FirstName + ' ' + Users.LastName AS Name, COUNT(FRrequests.ID) AS 'Open Requests'
FROM Users INNER JOIN
Roles ON Users.RoleID = Roles.ID LEFT OUTER JOIN
FRrequests ON Users.ID = FRrequests.UserID
WHERE (Roles.Name = N'Field Rep') AND (FRrequests.Status = 'Submitted' OR FRrequests.Status = 'Delayed')
GROUP BY Users.FirstName, Users.LastName
Name Count
Matt Ryan 2
I know that the "AND (FRrequests.Status = 'Submitted' OR FRrequests.Status = 'Delayed')" part is what is breaking it. If I run it without that in the statement I get all the users but it counts all status not just submitted and delayed. I just can't figure out what I'm missing to get this to work. Any help would be greatly appreciated.

You are really close, try the following:
SELECT U.FirstName + ' ' + U.LastName AS Name, COUNT(F.ID) AS 'Open Requests'
FROM Users U
INNER JOIN Roles R
ON U.RoleID = R.ID
LEFT JOIN ( SELECT * FROM FRrequests
WHERE Status IN ('Submitted','Delayed')) F
ON U.ID = F.UserID
WHERE R.Name = N'Field Rep'
GROUP BY U.FirstName, U.LastName

You need to have a row for each Field Rep if you wish to include Field Reps that have no requests. Therefore you need to use a left outer join along the following lines:
SELECT
u.FirstName || ' ' || u.LastName as Name
, COALESCE(frr.Counter,0) as Count
FROM
Users u
LEFT OUTER JOIN (
SELECT
UserID
,count(*) as Counter
FROM
FRrequests
WHERE
Status IN ('Submitted', 'Delayed')
GROUP BY
UserID
) frr ON frr.UserID = u.ID
, Roles r
WHERE
u.ID = f.UserID
AND u.RoleID = r.ID
AND r.Name = 'Field Rep'
;
A left outer join will provide a join only where a match occurs, so it does not remove rows from the naturally joined set. In this case you will get a list of all Users who are Field Reps, together with a count of their FRrequests with a 'Submitted' or 'Delayed' status if any.
COALESCE is used (in Postgresql at least) to check for a null value and then coerce it to the supplied value, in this case 0. So if a NULL value occurs (because, for instance, there was no 'Counter' value returned from the left outer join) it is replaced by 0.

Related

How to sum up max values from another table with some filtering

I have 3 tables
User Table
id
Name
1
Mike
2
Sam
Score Table
id
UserId
CourseId
Score
1
1
1
5
2
1
1
10
3
1
2
5
Course Table
id
Name
1
Course 1
2
Course 2
What I'm trying to return is rows for each user to display user id and user name along with the sum of the maximum score per course for that user
In the example tables the output I'd like to see is
Result
User_Id
User_Name
Total_Score
1
Mike
15
2
Sam
0
The SQL I've tried so far is:
select TOP(3) u.Id as User_Id, u.UserName as User_Name, SUM(maxScores) as Total_Score
from Users as u,
(select MAX(s.Score) as maxScores
from Scores as s
inner join Courses as c
on s.CourseId = c.Id
group by s.UserId, c.Id
) x
group by u.Id, u.UserName
I want to use a having clause to link the Users to Scores after the group by in the sub query but I get a exception saying:
The multi-part identifier "u.Id" could not be bound
It works if I hard code a user id in the having clause I want to add but it needs to be dynamic and I'm stuck on how to do this
What would be the correct way to structure the query?
You were close, you just needed to return s.UserId from the sub-query and correctly join the sub-query to your Users table (I've joined in reverse order to you because to me its more logical to start with the base data and then join on more details as required). Taking note of the scope of aliases i.e. aliases inside your sub-query are not available in your outer query.
select u.Id as [User_Id], u.UserName as [User_Name]
, sum(maxScore) as Total_Score
from (
select s.UserId, max(s.Score) as maxScore
from Scores as s
inner join Courses as c on s.CourseId = c.Id
group by s.UserId, c.Id
) as x
inner join Users as u on u.Id = x.UserId
group by u.Id, u.UserName;

Postgres - rows to columns

What i have
Query:
SELECT u.firstname,u.lastname, u.institution as department, u.department as company, c.shortname,
to_char(to_timestamp(p.timecompleted)::date,'YYYY-MM-DD') AS completed
FROM mdl_course_completions AS p
JOIN mdl_course AS c ON p.course = c.id
JOIN mdl_user AS u ON p.userid = u.id
WHERE c.enablecompletion = 1 AND u.firstname is NOT NULL AND p.timecompleted is NOT NULL
ORDER BY u.firstname
Results:
firstname lastname department company course completed
u1 u1 x x c1 date
u1 u1 x x c2 date
What i need
I need to be able to transport course to columns. Resulting in something similar to this:
firstname lastname department company c1 c2
u1 u1 x x date date
u2 u2 x x date date
I have tried using crosstab, but I am not skilled enough on SQL. Could someone please help?
EDIT: the number of courses are in the hundreds, so it needs to be dynamic.
(Also: English is not my first language, so please excuse any unclarities).
This is not what you request, but maybe be useful for you. Last column is an array with the structure: {'course_name: course_completed', ...}.
SELECT u.firstname,u.lastname, u.institution as department, u.department as company
array_agg(c.shortname || ': ' || to_char(to_timestamp(p.timecompleted)::date,'YYYY-MM-DD')) AS courses_completed
FROM mdl_course_completions AS p
JOIN mdl_course AS c ON p.course = c.id
JOIN mdl_user AS u ON p.userid = u.id
WHERE c.enablecompletion = 1 AND u.firstname is NOT NULL AND p.timecompleted is NOT NULL
GROUP BY u.firstname,u.lastname, u.institution, u.department
ORDER BY u.firstname
output will be:
firstname lastname department company courses_completed
u1 u1 x x {"c1: date", "c2: date"}
u2 u2 x x {"c1: date", "c2: date"}
Other option is to use crosstab function (like you said): Postgresql crosstab documentation. You have to install tablefunc module to use crosstab. Read the documentation and if you think it could be useful for you I could help you to write the query (but I will need to know the tables structure and some example data).
You can use conditional aggregate, for example by using the FILTER clause:
SELECT
firstname, lastname, department, company,
MAX(completed) FILTER (WHERE course = 'c1') as c1,
MAX(completed) FILTER (WHERE course = 'c2') as c2
FROM
-- <joins>
GROUP BY firstname, lastname, department, company
You need to group by the columns which are part of the same group. Then you can execute any aggregation function you like on the columns you want to pivot. With the appended FILTER clause you can filter all records which should be recognized for the aggregate.

Count for all values of enum in PostgreSQL

I have a table called users, which has the following columns:
id: INT NOT NULL
face: face_type
face_type is an ENUM type that has the following values: 'square', 'round' and 'triangle'.
And I have another table called houses, which has the following columns:
id: INT NOT NULL
user_id: INT NOT NULL
Now, I want to get all the houses grouped by the different type of face types. So, what I have so far is this:
SELECT users.face_type, COUNT(*)
FROM users
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY users.face_type
The problem is that I also want to get rows for face_type which none of the users have, as well as a result for NULL face_type. So, for example, if I have the following data:
users (id, face_type)
1, 'round'
2, 'triangle'
houses (id, user_id)
1, 1
2, 1
3, 2
I would expect the result to be:
face_type, count
'round' 2
'triangle' 1
'square' 0
null 0
I know how to get all the potential values of the face_type ENUM, by doing :
SELECT unnest(enum_range(NULL::face_type)) AS face_types;
But I don't know how to use that to count all potential face types in the aggregate, as well as also calculating for NULL face types.
You can use LEFT JOIN:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
To get NULL, just use UNION ALL:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
UNION ALL
SELECT NULL
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
Of course, the = will not every match. If that is possible, then you want to change the JOIN condition to u.face_type is not distinct from ft.face_type.
to COUNT(houses.*)
SELECT face_type.type, COUNT(houses.*)
FROM (SELECT unnest(enum_range(NULL::face_type))) AS face_type(type)
FULL JOIN users ON users.face_type=face_type.type
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY face_type.type
A LEFT JOIN starting from the ENUM and going to users and houses will allow you to recover totals for each enumerated value. To also display the NULL face types, you can use a UNION query.
SELECT
ft.face_type,
COUNT(ho.user_id) as cnt
FROM
(SELECT unnest(enum_range(NULL::face_type)) AS face_types) ft
LEFT JOIN users us ON us.face_type = ft.fact_type
LEFT JOIN houses ho ON ho.user_id = us.id
GROUP BY ft.face_type
UNION
SELECT
null,
COUNT(ho.user_id)
FROM houses ho
INNER JOIN users us ON ho.user_id = us.id AND us.face_type IS NULL
ORDER BY cnt desc

Concat multiple results with left join not working

In my project I have a user table with regular information such as name, age, and the phone numbers are listed on another table with a relationship table to connect them.
Something like this.
TBL_User (name, dateofbirth, mail)
TBL_PhoneType (id, phonetype) //like, cellphone, home phone, etc.
TBL_PhoneNumber (id, phonetype, phonenuber) //lists muliple values for each user
TBL_PhoneRelation (userid, pnid)
I'm trying to make a selection to return the user information and a CONCAT version of the phone numbers, but the problem is the result that I get.
My first try is the GROUP_CONCAT, something like
SELECT us.name, us.dateofbirth, GROUP_CONCAT(' ', pt.phonetype, ' ', p.phonenumber)
FROM TBL_User AS us
LEFT JOIN TBL_PhoneRelation AS pr ON pr.userid = us.id
LEFT JOIN TBL_PhoneNumber AS p ON p.id = pr.pnid
The problem is that I get only one row and not all the values from the database and a regular CONCAT show only one phone number, and sub selection gives a error because I have more than 1 row in my result.
I'm trying something like this
User name | Phone number | E-mail
Adrian | Cellphone 11..., Home phone 22... | adrian...
Suzan | Cellphone 32..., Commercial phone 44... | sue...
I would do something like this:
select
U.Name,
group_concat(', ', T.PhoneType + ' ' + N.PhoneNumber)
U.Mail
from
TBL_user as U
left join
TBL_PhoneRelation AS R ON
R.UserID = U.ID
LEFT JOIN
TBL_PhoneNumber AS N ON
N.ID = R.PhoneID
left join
TBL_PhoneType as T on
T.ID = N.PhoneTypeID
group by
U.Name
select
U.Name,
group_concat(CONCAT(T.PhoneType + ' ' + N.PhoneNumber) SEPARATOR ','),
U.Mail
from
TBL_user as U
left join
TBL_PhoneRelation AS R ON R.UserID = U.ID
LEFT JOIN
TBL_PhoneNumber AS N ON N.ID = R.PhoneID
left join
TBL_PhoneType as T on T.ID = N.PhoneTypeID
group by
U.ID

SQL query involving three tables and checking whether some result has entry in another table

I have 3 tables in my SQL database as follows
Users2
UserID,
EID,
Name,
Email,
Department,
Enabled
Sites
SiteID,
SiteCode,
SiteName
UserSites2
UsersSitesID,
UserID,
SiteID
What I need to do is, given EID and SiteID, get a full row from the Users2 table AND the SiteID, SiteCode and SiteName from the Sites table WHEN the userID of the retrieved record has an entry in the UserSites2
Example of expected result:
Users2
1, 12345, Me, me#email.com, Support, True
2, 12346, you, you#email.com, Service, True
Sites
1, 123, Regional HQ
2, 234, National HQ
UserSites2
1, 1, 1
2, 1, 2
3, 2, 2
So given EID 12345 and SiteID 2 I should get the result
1, 12345, Me, me#email.com, Support, True, 2, 234, National HQ
and for EID 12346 and SiteID 1 I should get nothing
I know how to join Users2 and Sites to get the full row I want but I don't understand how to make it depend on whether there is an entry in the lookup table for it.
select u2.UserID, u2.EID, u2.Name, u2.Email, u2.Department, u2.Enabled,
s.SiteID, s.SiteCode, s.SiteName
from users2 u2
left outer join usersites us on u2.UserID = us.UserID
left outer join sites s on s.SiteID = us.SiteID
where u2.EID = 12345 and us.SiteID = 2
I have tested this. It will give you no records if it is not mapped in UserSites. So for EID 12346 and SiteID 1 you will get nothing.
you would do something like this:
select col1,col2,.....from Users, Sites, UserSites
where Users.eid = 12345 and Sites.siteid = 2
and users.user_id = userSites.userid
and sites.siteis = usersites.siteid
I like to specifically list the joins where possible. I believe something like this would work:
select u2.UserID, u2.EID, u2.Name, u2.Email, u2.Department, u2.Enabled,
s.SiteID, s.SiteCode, s.SiteName
from Users2 u2 join UserSites2 us2 on u2.UserID=us2.UserID
join sites s on us2.SiteID=s.SideID
where u2.EID=<eid> and
us2.SiteID=<siteid>
i know how to join Users2 and Sites to get the full row I want, but I don't understand how to make it depend on wether there is an entry in the lookup table for it.
Using a LEFT JOIN means there might not be a supporting record, based on the join criteria. Where records are missing, you'll see null in appropriate column. If you want to only return rows based on the criteria, use a [INNER] JOIN. Here's a good link showing the differences between JOINs and it includes UNIONs.
Here's your query:
SELECT u.userid,
u.eid,
u.name,
u.email,
u.department,
u.enabled,
s.siteid,
s.sitecode,
s.sitename
FROM USERS2 u
JOIN USERSITES2 us ON us.userid = u.userid
JOIN SITES s ON s.siteid = us.siteid
WHERE u.eid = ?
AND us.siteid = ?