Group by select based on OR condition - mysql

After using UNION with two select queries, I'm getting following results
UserId Name Status
------ ------ --------
1 User1 Active
2 User2 Active
1 User1 InActive
3 User3 InActive
But the expected results is
UserId Name Status
---------------------
1 User1 Active
2 User2 Active
3 User3 InActive
Here what I need is, I want to group by column Id and get status as Active if any one result is active. How to form a SQL query for this?
Can anyone suggest query for any one of the following DB?
MSSQL
Oracle
MySQL
PostgreSQL
Edit:
This is the query I've tried in PostgreSQL
(SELECT DISTINCT User.Id,User.DisplayName,AppAccessToUsers.IsActive='1' AND User.IsActive='1' AS IsStatusActive
FROM Applications Left JOIN AppAccessToUsers ON (Applications.Id=AppAccessToUsers.ApplicationId)
Left JOIN User ON (AppAccessToUsers.UserId=User.Id) WHERE Applications.ClientId='e7e66c1b-b3b8-4ffb-844b-fc4840803265')
UNION
(SELECT DISTINCT User.Id,User.DisplayName,AppAccessToGroups.IsActive='1' AND Group.IsActive='1' AND UserGroup.IsActive='1' AND User.IsActive='1' AS IsStatusActive
FROM Applications Left JOIN AppAccessToGroups ON (Applications.Id=AppAccessToGroups.ApplicationId)
Left JOIN Group ON (AppAccessToGroups.GroupId=Group.Id) Left JOIN UserGroup ON (Group.Id=UserGroup.GroupId)
Left JOIN User ON (UserGroup.UserId=User.Id) WHERE Applications.ClientId='e7e66c1b-b3b8-4ffb-844b-fc4840803265')

Use this query,
SELECT UserId
,Name
,CASE WHEN min(status) = 'Active' THEN 'Active' ELSE 'InActive' END
FROM users GROUP BY UserId,Name

I would do the following, assuming a) your tables are called t1 and t2 (amend as appropriate for your actual table names) and b) the names for each userid in both tables are the same - ie. for userid = 1, both tables have the same name:
SELECT userid,
NAME,
MIN(status)
FROM (SELECT userid, NAME, status FROM t1
UNION ALL
SELECT userid, NAME, status FROM t2)
GROUP BY userid, NAME;
This works in Oracle, and I'm pretty sure it'll work in the other database platforms you mentioned.
N.B. I used MIN(status) since you appear to want a status of Active to override a status of Inactive, and A comes before I in the alphabet.

In Sql-server, you could use group by or Row_number like this
DECLARE #SampleData AS TABLE
(
UserId int,
Name varchar(20),
Status varchar(10)
)
INSERT INTO #SampleData
(
UserId,Name,Status
)
VALUES
(1,'User1', 'Active'),
(2,'User2', 'Active'),
(1,'User1', 'InActive'),
(3,'User3', 'InActive')
-- use row_number
;WITH temp AS
(
SELECT *, row_number() OVER(PARTITION BY sd.UserId ORDER BY sd.Status ) AS Rn
FROM #SampleData sd
)
SELECT t.UserId, t.Name, t.Status
FROM temp t WHERE t.Rn = 1
--or use group by
SELECT sd.UserId, sd.Name, min(sd.Status) AS status
FROM #SampleData sd
GROUP BY sd.UserId, sd.Name
Results:
UserId Name Status
1 User1 Active
2 User2 Active
3 User3 InActive

In case of MS Sql Server you can try row_number
;with cte as (
select top 1 with ties * from
( select * from #youruser
union all
select * from #youruser) a
order by row_number() over (partition by userid order by [status] desc)
) select * from cte where status = 'Active'

select your_table.* from your_table
inner join (
select UserId, min(Status) as st from your_table
group by UserId
) t
on your_table.UserId = t.UserId and your_table.Status = t.st
Note: if same UserId can have same Status more than 1 times, then this returns duplicated results.

;With cte (UserId, Name,Status)
AS
(
SELECT 1,'User1','Active' Union all
SELECT 2,'User2','Active' Union all
SELECT 1,'User1','InActive' Union all
SELECT 3,'User3','InActive'
)
SELECT UserId
,NAME
,[Status]
FROM (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY UserId
,NAME ORDER BY STATUS
) AS Seq
FROM cte
) dt
WHERE dt.Seq = 1
OutPut
UserId Name Status
-----------------------
1 User1 Active
2 User2 Active
3 User3 InActive

for postgres you can use CASE and bool_or, eg:
t=# with a(i,n,b) as (
values (1,'a','active'), (1,'a','inactive'), (2,'b','inactive'), (2,'b','inactive')
)
select i,n,case when bool_or(b = 'active') then 'active' else 'inactive' end
from a
group by i,n
;
i | n | case
---+---+----------
1 | a | active
2 | b | inactive
(2 rows)

Another approach:
Note : Group by is to remove duplicate
select
A.USERID, A.NAME,A.STATUS
from TAB_1 A
LEFT JOIN
(SELECT * FROM TAB_1 WHERE STATUS='Active') B
ON A.USERID=B.USERID
WHERE
( B.STATUS IS NULL OR A.STATUS=B.STATUS)
GROUP BY A.USERID, A.NAME,A.STATUS
ORDER BY A.USERID
;

Related

How join statements execute in sql

I'm trying to fetch the data from user table such that every row contains date value(not null). If value is null then it should be view that column with a date of id of above date which have same id.
Without updating the table rows, only with select statement?
Here is the table
NAME, DATE, ID
A, 2021-01-21, 1
B, null, 1
C, null, 1
D, 2021-01-18, 2
D, null, 2
It should be viewed like
A, 2021-01-21, 1
B, 2021-01-21, 1
C, 2021-01-21, 1
D, 2021-01-18, 2
D, 2021-01-18, 2
Now the query I think is =>
select t1.name, t2.date ,t1.id from user t1
left join (select id ,date from user where id=1) t2
on t1.id=t2.id;
But this query doesn't work like I thought.
Can anyone please tell me how above join query works ? And how can I improve it ? So that I got the required result.
For testing of above query use this queries =>
create table user(
name varchar(20),
date date,
id integer
);
insert into user values("A",'2021-01-21',1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("B",'2021-01-20',2);
select t1.name, t2.date ,t1.id from user t1
left join (select id ,date from user where id=1) t2
on t1.id=t2.id;
The first problem is that you are joining a table with itself on the condition t1.id = t2.id. So if you have 4 rows with id=1 and 3 rows with id=2 just as an example, you will end up with a result that had 4 * 4 + 3 * 3 = 25 rows. In your specific case you will end up with 6 * 6 + 1 * 1 = 37 rows.
The second problem is that you have hard-code selecting id=1 in your subquery:
(select id ,date from user where id=1) t2
This can't be the appropriate value for all possible rows.
You could try the obvious:
select
t1.name,
ifnull(t1.date, (select t2.date from user t2 where t2.date is not null and t2.id = t1.id limit 1)) as date,
t1.id
from user t1
;
see db-fiddle
name
id
date
A
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
B
2
2021-01-20
But better would be to use a join:
select u.name, ifnull(u.date, sq.date) as date, u.id
from user u join (
select id, min(date) as date from user group by id
) sq on u.id = sq.id
;
see db-fiddle
I would expect the second version using a join to be more efficient because the first version has a dependent subquery that has to get executed for every row that has a null date.
You don't need a join. Just use a window function:
select name,
max(date) over (partition by id) as date,
id
from users;
Note that your sample data doesn't match the data in the question. That data suggests:
select max(name) over (partition by id) as name,
max(date) over (partition by id) as date,
id
from user;
Here is a db<>fiddle.

SQL: select a count distinct for entries with higher ID than previous and conditions met

Say that I have the following data in a table:
ID ENTRY NAME ENTRY_ID
6 REMOVE ALICE 333
5 ADD JOHN 333
4 REMOVE JOHN 222
3 ADD ALICE 222
2 ADD AMANDA 111
1 ADD JOHN 111
I am trying to get a count for all who has an "ADD" in their latest entry which is determined by having a higher number in the "ENTRY_ID".
So in this case the count I am looking for is going to be 2, as "JOHN" in 333 has an "ADD" and "AMANDA" in 111 has an "ADD" - and none of the two has a higher ENTRY_ID with "REMOVE", as is the case with "ALICE", who is not suppose to be counted as her newest (highest) ENTRY_ID is a "REMOVE".
How can I most easily achieve this?
You can use window functions:
select count(*)
from (
select t.*, row_number() over(partition by name order by entry_id) rn
from mytbale t
) t
where rn = 1 and entry = 'ADD'
Or using first_value():
select count(*) cnt
from (
select t.*, first_value(entry) over(partition by name order by entry_id desc) last_entry
from mytbale t
) t
where last_entry = 'ADD'
This requires MySQL 8.0. In earlier versions, one option uses a correlated subquery for filtering:
select count(*)
from mytable t
where
t.entry = 'ADD'
and t.entry_id = (select max(t1.entry_id) from mytable t1 where t1.name = t.name)
You can get the list using aggregation:
select name
from t
group by name
having max(entry_id) = max(case when entry = 'ADD' then entry_id end);
This gets all names where the entry id of "ADD" matches the last entry id.
You can use a subquery and get the count:
select count(*)
from (select name
from t
group by name
having max(entry_id) = max(case when entry = 'ADD' then entry_id end)
) t;
Otherwise, I might suggest a correlated subquery:
select count(*)
from t
where t.entry = 'ADD' and
t.entry_id = (select max(t2.entry_id) from t t2 where t2.name = t.name);

SQL count of distinct union

I have a query that captures customer ids from three tables (each table is a different contact method).
I want to get the count of distinct customer ids after the unions.
The SQL statement below is working and returns a list of unique customer ids (no dups):
SELECT DISTINCT customer_id
FROM email_contact
WHERE info_id = 1
AND status = 'SENT'
UNION
SELECT DISTINCT customer_id
FROM call_contact
WHERE info_id = 1
AND status = 'CALLED'
UNION
SELECT DISTINCT customer_id
FROM mail_contact
WHERE info_id = 1
AND status = 'MAILED'
From that query I want a count of customers, but my attempts to wrap the query in a select count keep producing syntax errors. How can wrap the unions to provide me with a count of the clients?
I would recommend:
SELECT COUNT(DISTINCT customer_id)
FROM (
SELECT customer_id FROM email_contact WHERE info_id = 1 AND status = 'SENT'
UNION ALL SELECT customer_id FROM call_contact WHERE info_id = 1 AND status = 'CALLED'
UNION ALL SELECT customer_id FROM mail_contact WHERE info_id = 1 AND status = 'MAILED'
) t
I removed the DISTINCT and I changed the UNIONs to UNION ALL, so the database just gathers all the rows from the 3 union members without attempting to manage duplicates (this is fast). Then, you can use COUNT(DISTINCT ...) in the outer query.
You can wrap it like this
SELECT COUNT(*)
FROM
( SELECT DISTINCT customer_id
FROM email_contact
WHERE info_id = 1
AND status = 'SENT'
UNION
SELECT DISTINCT customer_id
FROM call_contact
WHERE info_id = 1
AND status = 'CALLED'
UNION
SELECT DISTINCT customer_id
FROM mail_contact
WHERE info_id = 1
AND status = 'MAILED') t1
Is this what you ant?
SELECT COUNT(*)
FROM ((SELECT customer_id
FROM email_contact
WHERE info_id = 1 AND status = 'SENT'
) UNION -- on purpose to remove duplicates
(SELECT customer_id
FROM call_contact
WHERE info_id = 1 AND status = 'CALLED'
) UNION
(SELECT customer_id
FROM mail_contact
WHERE info_id = 1 AND status = 'MAILED'
)
) c;
Note that all your DISTINCTs are unnecessary because UNION removes duplicates.

Correlated subquery with row number count

I have a table as follows and what I want is to use get the initial row with least id of each uid group.
The table is as follows
_id uid type
1 a a
2 b bbb #satisfied
3 b ccc
4 b aaa #satisfied
5 a aaa #satisfied
6 b eee
I can already get the initial row using the following correlated subquery
SELECT *
FROM table
WHERE _id IN (
SELECT MIN(_id)
FROM table
WHERE type IN ('aaa','bbb')
GROUP BY uid
);
However, I want the 4th column shown the count of rows satisfied the condition (type IN ('aaa','bbb')), as cnt shown below:
_id uid type cnt
5 a aaa 1
2 b bbb 2
I think I can count this use several joins and then join the result to my code...But this is ugly...Is there any elegant way to achieve this...
You can try this:
SELECT t1.*, t2.cnt
FROM table t1 INNER JOIN (
SELECT MIN(_id) AS id, COUNT(_id) AS cnt
FROM table
WHERE type IN ('aaa','bbb')
GROUP BY uid
) t2 ON t1._id = t2.id
ORDER BY t1.uid
If you are running MySQL 8.0, you can just use window functions for this:
select _id, uid, type, cnt
from (
select
t.*,
count(*) over(partition by uid) cnt,
row_number() over(partition by uid order by _id) rn
from mytable t
where type in ('aaa', 'bbb')
) t
where rn = 1
You can do this without a subquery. In MySQL 8+, you can use this logic:
SELECT DISTINCT MIN(_id) OVER (PARTITION BY uid) as _id,
uid,
FIRST_VALUE(type) OVER (PARTITION BY uid ORDER BY _id) as type,
COUNT(*) OVER (PARTITION BY uid) as cnt
FROM table
WHERE type IN ('aaa', 'bbb');
Unfortunately, MySQL doesn't have a "first" aggregation function, but there is a trick if you like:
SELECT MIN(_id) as _id, uid,
SUBSTRING_INDEX(GROUP_CONCAT(type ORDER BY _id), ',', 1) as type,
COUNT(*) as cnt
FROM table
WHERE type IN ('aaa', 'bbb')
GROUP BY uid;

SQL Create Unique Value Flag

There are lots of questions/answers about selecting unique values in a MySQL query but I haven't seen any on creating a unique value flag.
I have a customer_ID that can appear more than once in a query output. I want to create a new column that flags whether the customer_ID is unique or not (0 or 1).
The output should look something like this:
ID | Customer ID | Unique_Flag
1 | 1234 | 1
2 | 2345 | 1
3 | 2345 | 0
4 | 5678 | 1
Please let me know if anybody needs clarifications.
You seem to want to mark the first occurrence as unique, but not others. So, let's join in the comparison value:
select t.*,
(id = min_id) as is_first_occurrence
from t join
(select customer_id, min(id) as min_id
from t
group by customer_id
) tt
on t.customer_id = tt.customer_id;
For most people, a "unique" flag would mean that the overall count is "1", not that this is merely the first appearance. If that is what you want, then you can use similar logic:
select t.*,
(id = min_id) as is_first_occurrence,
(cnt = 1) as is_unique
from t join
(select customer_id, min(id) as min_id, count(*) as cnt
from t
group by customer_id
) tt
on t.customer_id = tt.customer_id;
And, in MySQL 8+, you would use window functions:
select t.*,
(row_number() over (partition by customer_id order by id) = 1) as is_first_occurrence,
(count(*) over (partition by customer_id) = 1) as is_unique
from t;
You can try below
select id,a.customerid, case when cnt=1 then 1 else 0 end as Unique_Flag
from tablename a
left join
(select customerid, count(*) as cnt from tablename
group by customerid
)b on a.customerid=b.customerid
You can use lead function as given below to get the required output.
SELECT ID, CUSTOMER_ID,
CASE
WHEN CUSTOMER_ID != CUSTOMER_ID_NEXT THEN 1
ELSE 0
END AS UNIQUE_FLAG FROM
(SELECT ID, CUSTOMER_ID,LEAD(CUSTOMER_ID, 1, 0) OVER (ORDER BY CUSTOMER_ID) AS CUSTOMER_ID_NEXT FROM TABLE)T