incorrect joins - 1 row per result set - sql

I trying to determine which people in my databases have either unsubscribed from my news letters, which people have bad email addresses and which dont have either. I have activities activities for both iUnsub' and 'iBadEmail'.
the code i wrote was
select distinct
n.id,
'Unsubscribe' =
case
when a.activity_type = 'iUnsub' then '1'
end,
'Bad Email' =
case
when a.activity_type = 'iBadEmail' then '1'
end
from name n
left join activity a on n.id = a.id
where n.id in
(
'1002421',
'1005587',
'1009073',
'1001102'
)
the results i receive creates 2 results for each id
id Unsubscribe Bad Email
1001102 NULL NULL
1002421 NULL NULL
1002421 1 NULL
1005587 NULL NULL
1005587 1 NULL
1009073 NULL 1
1009073 NULL NULL
i would like to the code to only give me one row for each id like below
id Unsubscribe Bad Email
1001102 NULL NULL
1002421 1 NULL
1005587 1 NULL
1009073 NULL 1

The problem is that you have multiple activity rows 3 or your names, and you are returning a row in the result for each activity. Name 1001102 either has no or only one activity which is neither Unsub or BadEmail.
select n.Id,
sum(case when a.activity_id = 'iUnSub' then 1 else 0 end) UnSub,
sum(case when a.activity_id = 'iBadEmail' then 1 else 0 end) BadEmail
from name
left outer join activity a on n.id = a.id
where a.activity
and n.id in
(
'1002421',
'1005587',
'1009073',
'1001102'
)
group by n.Id
This will give you a non-zero figure if UnSubbed or BadEmail, and if both are 0, then it's presumably OK.
The left outer join is for cases whene a name has no activity rows. If you don't include that then they will not be included in the output. If that's fine, change it to an inner join.

Related

SQL COUNT return multiple rows

I have those two Tables:
tblCommentReactions
id
Liked
CommentID
1
0
1
2
1
1
3
1
1
4
0
2
1 is Like and 0 is dislike.
tblComments:
id
userID
message
1
1
message 1
2
1
message 2
3
2
message 1
I tried to select all comments and Count the dislikes and likes and give the result in the same Row.
SELECT c.ID as CommentID, c.message,
COUNT(case when Liked = 1 AND r.CommentID = c.ID then 1 else null end) as likes,
COUNT(case when Liked = 0 AND r.CommentID = c.ID then 1 else null end) as dislikes
FROM tblcomments as c LEFT JOIN tblcommentreactions as r ON c.ID = r.CommentID
WHERE c.userID = 1;
Expected Output should be:
CommentID
message
likes
dislikes
1
message 1
2
1
2
message 2
0
1
On my Return it counts everything and only returns the first message. Could you tell me what i need to change in my request, to get my expected output?
There are two issues in your query:
you have no GROUP BY clause in presence of non-aggregated fields inside the SELECT clause, which will bring you have an error fired by the DBMS in the best case scenario, no error but random/subtle semantic errors in the worst one.
you are attempting to filter your rows (the WHERE condition) before the aggregation is applied.
In order to solve:
the first problem, you need to add the GROUP BY clause with the two missing selected fields, namely "c.ID" and "c.message"
the second problem, you need to transform your current WHERE clause into an HAVING one (as long as this one applies after the aggregation has been carried out) and add the checked field, namely "c.userID", inside the GROUP BY clause, as long as it is a field that was selected along with the fields in the SELECT clause.
SELECT c.ID as CommentID,
c.message,
COUNT(CASE WHEN Liked = 1 THEN 1 END) AS likes,
COUNT(CASE WHEN Liked = 0 THEN 1 END) AS dislikes
FROM tblComments AS c
LEFT JOIN tblCommentReactions AS r
ON c.ID = r.CommentID
GROUP BY c.ID,
c.message,
c.userID
HAVING c.userID = 1
Minor fixes on the CASE construct that doesn't require "AND r.CommentID = c.ID" as already pointed in the comments section, but also the non-required "ELSE NULL" condition, that is considered by PostgreSQL as default for this construct.
Here's a demo in MySQL, though this should work in the most common DBMS' more or less.
Try using group by clause e.g
select cr.CommentID ,c.message,
COUNT(case when Liked = 1 AND cr.CommentID = c.ID then 1 else null end) as likes ,
COUNT(case when Liked = 0 AND cr.CommentID = c.ID then 1 else null end) as dislikes
from [tblCommentReactions] cr inner join tblComments c on cr.CommentID = c.id
group by cr.CommentID , c.message
SELECT c.Id, COUNT(cr1.Liked), COUNT(cr2.Liked)
FROM Comments c
LEFT JOIN CommentReactions cr1 ON cr1.CommentId = c.Id AND cr1.Liked = 0
LEFT JOIN CommentReactions cr2 ON cr2.CommentId = c.Id AND cr2.Liked = 1
GROUP BY c.Id

How to merge two rows having null values into one row replacing null values?

I'm having the following results from my sql query:
id
sp_firstname
sp_lastname
member_firstname
member_lastname
1
NULL
NULL
John
Smith
2
Dejuan
McLaughlin
NULL
NULL
2
NULL
NULL
Jack
Sparrow
3
John
Walker
NULL
NULL
3
NULL
NULL
Sherlock
Holmes
4
Mellie
Durgan
NULL
NULL
4
NULL
NULL
John
Waston
5
Lucy
Snider
NULL
NULL
Whereas what I need to achieve is this:
id
sp_firstname
sp_lastname
member_firstname
member_lastname
1
NULL
NULL
John
Smith
2
Dejuan
McLaughlin
Jack
Sparrow
3
John
Walker
Sherlock
Holmes
4
Mellie
Durgan
John
Waston
5
Lucy
Snider
NULL
NULL
Basically, I need to somehow merge pairs of rows that sort of have nulls crosswise.
After looking through SO answers, I could only find variants of this problem when NULL values needed to be substituted by numbers, and in that case people used max function combined with group by.
However I have several joins in my table and my NULL values need to be substituted with strings, not numbers, so max wouldn't really work here (as I thought).
Here's my sql code:
select
meeting.id,
(case when salesprofile.userid = "user".id then "user".firstname end) as sp_firstname,
(case when salesprofile.userid = "user".id then "user".lastname end) as sp_lastname,
(case when business.userid = "user".id then "user".firstname end) as member_firstname,
(case when business.userid = "user".id then "user".lastname end) as member_lastname
from
meeting
join project on project.id = meeting.projectid
left join business on business.id = project.businessid
left join salesprofile on salesprofile.id = meeting.salesprofileid
join "user" on "user".id = business.userid or "user".id = salesprofile.userid
group by
"user".id,
business.userid,
meeting.id,
salesprofile.userid;
These firstnames and lastnames come from the exact same user table, but they are taken based on different relations found in the same meeting table.
Basically, one meeting has two users: member and sp. And I needed a way to get meetings along with its member and sp users in one row.
How can I modify my sql query so that it would merge these pairs of rows with crosswise nulls into one row with data and without nulls?
Just use aggregation:
select meeting.id,
max(case when salesprofile.userid = "user".id then "user".firstname end) as sp_firstname,
max(case when salesprofile.userid = "user".id then "user".lastname end) as sp_lastname,
max(case when business.userid = "user".id then "user".firstname end) as member_firstname,
max(case when business.userid = "user".id then "user".lastname end) as member_lastname
from meeting join
project
on project.id = meeting.projectid left join
business
on business.id = project.businessid left join
salesprofile
on salesprofile.id = meeting.salesprofileid left join
"user"
on "user".id = business.userid or "user".id = salesprofile.userid
group by meeting.id;
Note the change to the group by as well.
You can wrap your results with an outer query to aggregate the columns using max and group by the id
select id, Max(sp_firstname) as sp_firstname, Max(sp_lastname) as sp_lastname...
from (
<inner query>
)x
group by id

MSSQL Count multiple conditions in subquery or outer apply?

I have a query which currently has a few counts in the field selection part.
SELECT userid,
(SELECT Count(*) AS Expr1
FROM dbo.relationships
WHERE ( authorised IS NOT NULL
AND expired IS NULL
AND relationshipended IS NULL )
AND ( mentorid = u.userid )) AS MenteeCount,
(SELECT Count(*) AS Expr1
FROM dbo.relationships AS Relationships_3
WHERE ( authorised IS NULL )
AND (rejecteddate IS NULL)
AND (mentorid = u.userid)) AS UnansweredRequests
FROM users
Would it be better (more streamlined) to do this using an outer apply as I have two counts coming from the same external table?
e.g. Using CASE WHEN?
You don't need to use apply, you can just use sum with case:
SELECT
u.userId,
MenteeCount = SUM(CASE WHEN authorised IS NOT NULL AND expired IS NULL AND relationshipended IS NULL THEN 1 ELSE 0 END),
UnansweredRequests =SUM(CASE WHEN authorised IS NULL AND rejecteddate IS NULL THEN 1 ELSE 0 END)
FROM
users u
INNER JOIN
dbo.relationships r
ON u.userid = mentorid
Yes. You should do the calculations in a single subquery:
SELECT u.userid, r.MenteeCount, r.UnansweredRequests
FROM users u OUTER APPLY
(SELECT SUM(CASE WHEN authorised IS NOT NULL AND expired IS NULL AND relationshipended IS NULL
THEN 1 ELSE 0
END) as MenteeCount
SUM(CASE WHEN authorised IS NULL AND rejecteddate IS NULL
THEN 1 ELSE 0
END) as UnansweredRequests
FROM dbo.relationships r
WHERE r.mentorid = u.userid
) r ;
That way, the processing for summarizing the relationships table is done only once for each mentorid. Note that when specifying correlation clauses (for either subqueries or apply, you should always use fully qualified column names. This helps avoid problems in the future.

Create overview table with true false values based on rows exist in child table

I have a table with cases, and another table with notifications.
For simplicity let's say the case table contains
id int
name nvarchar(100)
The notification table contains:
id int
caseid int
notificationtype string
Notification types can be either 'standard' or 'critical'.
I'd like an sql that can give me an overview for each case, and if they have any critical or standard notifications.
So a result like this:
CaseId CaseName StdNotification CriticalNotification
1 Test case yes no
I tried this SQL:
select distinct case.id as CaseId,
case.name as CaseName,
notifications.notificationtype,
case notifications when 'standard' then 'yes' else 'no' end as StdNotification,
case notifications when 'critical' then 'yes' else 'no' end as CriticalNotification
from cases
inner join notifications on Notifications.caseid = case.id
But this gives me duplicate rows for each combination
CaseId CaseName StdNotification CriticalNotification
1 Test case yes no
1 Test case no yes
So, how do I construct a sql that wil make some kind of "sum" and only return one row for each case?
You don't want distinct. You want group by. Your data structure suggests that a given case could have more than one notification, so I would go with counts using conditional aggregation:
select c.id as CaseId, c.name as CaseName,
sum(case when n.notificationtype = 'Standard' then 1 else 0 end) as NumStandard,
sum(case when n.notificationtype = 'Critical' then 1 else 0 end) as numCritical
from cases c left join
notifications n
on n.caseid = c.id
group by c.id, c.name;
You can convert these to "yes" and "no" using another case.
Also, note that I changed the inner join to a left join, so you'll get cases that have no notifications at all.
SELECT C.id as CaseId,
C.name as CaseName,
IIF(n1.notificationtype IS null, 'no', 'yes') as StdNotification,
IIF(n2.notificationtype IS null, 'no', 'yes') as CriticalNotification
FROM [case] C
left join notification n1
on C.id = n1.caseid and n1.notificationtype = 'standard'
left join notification n2
on C.id = n2.caseid and n2.notificationtype = 'critical'

Enriching Table

This is my query:
SELECT
a.account_type AS ACCOUNT_TYPE
,b.at_account_type_desc
,COUNT(a.BAN) AS num_BAN
FROM csm_adx.billing_account_act AS a
LEFT OUTER JOIN csm_adx.account_type_act AS b ON a.account_type = b.at_acc_type
GROUP BY 1,2
Now I want to connect it to another table TABLE_C which contains the information is the account: tentative, cancelled, closed, suspended, open.
I would like my result table to contain aditional three columns: ACTIVE_BAN, SUSPENDED_BAN and CANCELLED_BAN
and that each value contains the number of current active, suspended and cancelled bans. I´m using Teradata.
Can you please help me do this?
This is the result when the table is connected with another table which contains BAN status:
SELECT
a.account_type AS ACCOUNT_TYPE
,b.at_account_type_desc
,c.description
,COUNT(a.BAN) AS num_BAN
FROM csm_adx.billing_account_act AS a
LEFT OUTER JOIN csm_adx.account_type_act AS b
ON a.account_type = b.at_acc_type
LEFT OUTER JOIN csm_adx.acct_status AS c
ON a.ban_status = c.original_status_code
GROUP BY 1,2,3
SELECT
a.account_type AS ACCOUNT_TYPE
,b.at_account_type_desc
,COUNT(a.BAN) AS num_BAN ,
sum(case when a.column=value then 1 else 0 end) as 'user_colname1',
sum(case when b.column=value then 1 else 0 end) as 'user_colname2'
FROM csm_adx.billing_account_act AS a
LEFT OUTER JOIN csm_adx.account_type_act AS b
ON a.account_type = b.at_acc_type
GROUP BY 1,2