SQL: Process condition in each group - sql

I have a table like below.
The rule is, In each group, there is always one person with Code (>0), we can see that person as primary person in group.
In each group, if the primary person with Code > 0 and Status = "active", then we choose this record (Allen in group A).
However, if if the primary person with Code > 0 has Status != "active", then we need to seek other people in its group.
In group B, Amanda has Code but inactive so she is out, in the rest 3 people, the Status of Sarah has higher priority than Joe (Status priority: active -> pre_active -> pending -> inactive), so we choose Sarah and give her code with 2 (same as Amanda, the primary record in this group).
If there are multiple record in group that code = 0 and has same status, then we choose the one on top (by sequence).
In the end, I have to keep 1 record for each group, and give them code number from primary record if the selected record has code in 0.
Name
Code
Status
Group
Allen
8
active
A
Louis
0
inactive
A
Cindy
0
inactive
A
Joe
0
pending
B
Amanda
2
inactive
B
Sarah
0
pre_active
B
The result should be like below:
Name
Code
Status
Group
Allen
8
active
A
Sarah
2
pre_active
B
Thanks in advance!

I did it with two correlated queries, which would not be too efficient.
I also used GroupCode field name to avoid escaping the keyword.
In MySQL syntax:
SELECT Name,
(SELECT MAX(Code)
FROM YourTable tbl2
WHERE tbl1.GroupCode=tbl2.GroupCode) Code,
Status,
GroupCode
FROM YourTable tbl1
WHERE tbl1.Name = (SELECT Name
FROM YourTable tbl3
WHERE tbl1.GroupCode=tbl3.GroupCode
ORDER BY
-- active -> pre_active -> pending -> inactive
CASE Status
WHEN 'active' THEN 0
WHEN 'pre_active' THEN 1
WHEN 'pending' THEN 2
WHEN 'inactive' THEN 3
END
LIMIT 1);
DEMO Fiddle

Related

SQL Server -- Join Issue

I have a table with following rows :
SectionId
SectionStatus
1
Assigned
1
Approved
2
Assigned
2
Assigned
3
Assigned
Now I want to fetch the SectionId where al SectionStatus belong to that SectionId are not Approved.
Result expected for above table : 1,2,3
Another Example :
SectionId
SectionStatus
1
Approved
1
Approved
2
Assigned
2
Assigned
3
Assigned
Result expected for above table : 2,3
This looks like aggregation with a conditional count in the HAVING clause.
SELECT t.SectionId
FROM yourtable t
GROUP BY t.SectionId
HAVING COUNT(CASE WHEN t.SectionStatus = 'Approved' THEN 1 END) = 0;
db<>fiddle
One way to get that is by cross-applying the record and check whether that is 'Approved' or not.
Try the following:
select distinct SectionId from yourtable tab
cross apply (select 'Approved' Stat)t
where t.Stat <> tab.SectionStatus
Please see the db<>fiddle here.

How to: SELECT from multiple CTEs that are mutually exclusive

I am in a situation where I need to show stats of a user based on referrals that user made & then stats about those referred users' activities. So, if user A refers user B, C then it means user A referred 2 users. Now, I need to get how many of those referred users did take an action i.e. PURCHASED. But they may not purchase which counts to 0. I have following query using CTEs which does work as expected but it also returns some false negative results. i.e.
WITH direct_referrals AS (
SELECT id
FROM "user"
WHERE "user"."referredBy" = ${userId}
),
application_stats AS (
SELECT count(status), status
FROM "application"
WHERE "userId" IN (SELECT id FROM direct_referrals)
GROUP BY status
)
SELECT *, (SELECT count(id) FROM direct_referrals) AS "totalReferrals"
FROM application_stats;
This query returns correct result if at-least 1 referred user took some action but it fails when none has taken any action in which case this query returns the referred users to be 0 which is not true.
I can see that SELECT is dependent on application_stats CTE which may not return any result & hence the direct_referrals are also not returned. I am somewhat new to SQL so don't really know many options. Any help is appreciated.
Thanks
Update with sample Data & Expected Results
// User model
Id username referredBy
---- -------- -------------------
1 jon NULL
2 jane 1
3 doe 1
4 smith 2
5 john 1
// Application model
Id userId status
---- -------- -------------------
1 12 'APPLIED'
2 13 'APPLIED'
3 14 'VIEWED'
Expected Result (for userId = 1):
User (referral) stats Application stats
------------------- -------------------
3 0
Actual Result:
User (referral) stats Application stats
------------------- -------------------
0 0
Something like this should give you what you want:
WITH REFERRAL AS (
SELECT REFERREDBY, COUNT(USERNAME) AS "REF_CNT"
FROM USER_MODEL
WHERE REFERREDBY IS NOT NULL
GROUP BY REFERREDBY
),
APPLICATIONS AS (
SELECT UM.REFERREDBY, COUNT(ML.APPL_STATUS) AS "APPL_CNT"
FROM USER_MODEL UM
LEFT OUTER JOIN APPLICATION_MODEL ML ON UM.ID = ML.USER_ID AND ML.APPL_STATUS = 'PURCHASED'
GROUP BY UM.REFERREDBY
)
SELECT R.REFERREDBY, R.REF_CNT, A.APPL_CNT
FROM REFERRAL R
LEFT OUTER JOIN APPLICATIONS A ON R.REFERREDBY = A.REFERREDBY;

SQL: Compare 2 tables and state if data was found

I'm a novice when it comes to SQL, so forgive me if this is a dumb question.
I have 2 tables, one with a list of users, and one that holds email history data.
Users Table:
userID fName lName ...
1 John Smith
2 Jane Doe
3 Kevin Cooper
Email History Table:
emailID userID subject sendDate ...
1 6 welcome 2020-10-17
2 3 hello 2020-10-20
3 7 welcome 2020-10-23
I am wanting to do some sort of select statement that would compare every customer in table 1, to every email in table 2 based on some sort of search query (in this case where subject = "hello" and sendDate = "2020-10-20" and would return something like this:
Returned Query:
userID fName lName ... emailSent?
1 John Smith ... No
2 Jane Doe ... No
3 Kevin Cooper ... Yes
One option uses exists and a correlated subquery:
select u.*,
exists (
select 1
from emailhistory eh
where eh.userid = u.userid and eh.subject = 'hello' and eh.senddate = '2020-20-20'
) emailSent
from users u
This gives you 0/1 values in column emailSent, where 1 indicates that a match exists. As compared to the left join approach, the upside is that it does not "multiplies" the user rows if more than one match is found in the history table.
For performance, consider an index on emailhistory(userid, subject, senddate).
You can left join the email table on, putting your date and subject criteria in the where clause:
SELECT
u.userid,
u.fname,
u.lname,
case when eh.emailid is null then 'No' else 'Yes' end as emailsent
FROM
users u
LEFT JOIN
emailhistory eh
ON
u.userid = eh.emailid AND
eh.subject = 'hello' AND
eh.senddate = '2020-10-20'
This conceptually filters the email table down to just that subject and day, then joins those records onto the users table. You get every row from users and only rows from emailhistory that match on userid, and also had that subject/date. You can then examine whether the emailid (a key of the join) is null or not; the only way it can be null is if no email was sent to that user on that date with that subject

Set Duplicate Values to Null in PostgresSQL retaining one of the values

I have a database like this:
id name email
0 Bill bill#fakeemail.com
1 John john#fakeemail.com
2 Susan susan#fakeemail.com
3 Susan J susan#fakeemail.com
I want to remove duplicate emails by setting the value to null, but retain at least 1 email on one of the rows (doesn't really matter which one).
So that the resulting database would look like this:
id name email
0 Bill bill#fakeemail.com
1 John john#fakeemail.com
2 Susan susan#fakeemail.com
3 Susan J
I was able to target the rows like this
SELECT COUNT(email) as count FROM users WHERE count > 1
But can't figure out how to set the value to null while still retaining at least 1.
Update the rows which have the same email but greater id:
update my_table t1
set email = null
where exists (
select from my_table t2
where t1.email = t2.email and t1.id > t2.id
);
Working example in rextester.
You can use a windowed partition to assign a row number to each email group, and then use that generated row number to modify all rows except for one. Something like this:
WITH annotated_persons AS(
SELECT
id,
name,
email,
ROW_NUMBER () OVER (PARTITION BY email) AS i
FROM
persons;
)
UPDATE persons
SET email = null
WHERE id = annotated_persons.id AND annotated_persons.i <> 1
You may have to use another subquery in order to gather the IDs of persons whose row number != 1, and then change your update query to
WHERE id IN person_ids
It's been awhile since I've used a window.

SQL Count non existing item with two column

I have asked similar question SQL Count non existing item.
But I need another point of view. Here is my different version of question.
I have table Groups,
ID NUMBER
STATUS VARCHAR2(20 BYTE)
OWNER VARCHAR2(20 BYTE)
I am able to count the number of status as following. (taking account owner tuple as well)
select g.owner, g.status, count(*) countRS from groups g group by g.owner, g.status order by g.owner ;
OWNER STATUS COUNTRS
-------------------- -------------------- ----------
JOHN NOK 1
JOHN OK 2
MARK OK 1
I have another status ,say PENDING, REJECTED. But there is no item exists in table, but I want them to be shown with zero count as following.
OWNER STATUS COUNTRS
-------------------- -------------------- ----------
JOHN NOK 1
JOHN OK 2
JOHN PENDING 0
JOHN REJECTED 0
MARK OK 1
MARK NOK 0
MARK PENDING 0
MARK REJECTED 0
This query below will get the cartesian product of unique owner on table group to the number of records of status. The result rows will then be joined on table groups so we can be able to count the number of status per owner.
SELECT a.owner, b.status,
COUNT(g.status) TotalCount
FROM (SELECT DISTINCT OWNER FROM groups) a
CROSS JOIN
(
SELECT 'NOK' `status` UNION ALL
SELECT 'OK' `status` UNION ALL
SELECT 'PENDING' `status` UNION ALL
SELECT 'REJECTED' `status`
) b
LEFT JOIN groups g
ON a.owner = g.owner
AND b.status = g.status
GROUP BY a.owner, b.status
ORDER BY a.owner, b.status
SQLFiddle Demo
Add a "Status" table to your DB. Change "Status" field in "Groups" table to "StatusId", which should link to that new table. Then in your select you can right join "Status" table, and this will give rows in query result for missing status too.