SQL count of distinct union - mysql

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.

Related

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 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

How can I total up 3 select statement count?

I have three select statements as follows and I would like to sum up total number of records. How can I do that?
SELECT COUNT(*) AS Number FROM tableA where user_id = 5 //Total 5 records
SELECT COUNT(*) AS Number FROM tableB where user_id = 5 //Total 6 records
SELECT COUNT(*) AS Number FROM tableC where user_id = 5 //Total 1 records
so return result will be 12.
You could apply count(*) to the result of a union all:
SELECT COUNT(*)
FROM (SELECT user_id FROM tablea
UNION ALL
SELECT user_id FROM tableb
UNION ALL
SELECT user_id FROM tablec) t
WHERE user_id = 5
I think this should do the trick:
SELECT SUM(Number)
FROM (
SELECT COUNT(*) AS Number FROM tableA where user_id = 5
UNION ALL
SELECT COUNT(*) AS Number FROM tableB where user_id = 5
UNION ALL
SELECT COUNT(*) AS Number FROM tableC where user_id = 5
)
select ( select count(*) from tableA where user_id = 5 )
+ ( select count(*) from tableB where user_id = 5 )
+ ( select count(*) from tableC where user_id = 5 )
as total_rows
from dual

Filter rows using sql query

After executing some SQL query, I get some records for two fields: order_id and status_id as below:
order_id status_id
---------------------------------
'102764334679', 'ITEM_PROCESSING'
'102764334679', 'ITEM_PROCESSING'
'102764334679', 'ITEM_PROCESSING'
'112202812293', 'ITEM_PROCESSING'
'112202812293', 'ITEM_COMPLETED'
'112217253712', 'READY_TO_PACK'
How can I fetch count of order_id's which are having status_id as 'ITEM_PROCESSING' and if an order_id has more than one status_id, then it will not be counted .
For example , for above records, final count will be 1 because order_id '102764334679' has status as 'ITEM_PROCESSING' in all its rows while order_id '112202812293' has multiple status_id . so it will not be counted.
Here is one method:
select count(distinct order_id)
from t
where t.status_id = 'ITEM_PROCESSING' and
not exists (select 1
from t t2
where t2.order_id = t.order_id and t2.status_id <> 'ITEM_PROCESSING'
);
Although the above might be the most efficient, I would be intended to solve this using double aggregation:
select count(*)
from (select t.order_id
from t
group by t.order_id
having min(status_id) = max(status_id) and min(status_id) = 'ITEM_PROCESSING'
) tt;

Group by select based on OR condition

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
;