Filter rows using sql query - mysql

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;

Related

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.

update a column in sql using two conditions

I have a table in mysql in which i want to update the records in a column called status to 'Duplicate'. I want to mark a record 'duplicate' on the basis of 2 conditions.
The records have duplicate customer id.
Those records which don't have the recent modified date will be marked duplicate.
I have tried the below code but it gives me an error:
UPDATE test_sql_duplicate
SET status = 'Duplicate'
WHERE test_sql_duplicate.modi_date NOT IN (
SELECT *, max(modi_date)
FROM test_sql_duplicate
GROUP BY cust_id
HAVING COUNT(cust_id > 1)
You could use a LEFT JOIN antipattern to identify the records to update. Basically we use a subquery to identify the latest record for each customer, then we use it to exclude the corresponding from the update query:
UPDATE test_sql_duplicate t
LEFT JOIN (
SELECT cust_id, MAX(modi_date) modi_date FROM test_sql_duplicate GROUP BY cust_id
) m ON m.cust_id = t.cust_id and m.modi_date = t.modi_date
SET t.status = 'Duplicate'
WHERE m.cust_id IS NULL
I suspect we may be wanting a query something like this:
UPDATE TEST_SQL_DUPLICATE t
JOIN (
SELECT n.cust_id
, MAX(n.modi_date) AS max_modi_date
FROM TEST_SQL_DUPLICATE n
GROUP
BY n.cust_id
HAVING COUNT(n.cust_id) > 1
) d
ON d.cust_id = t.cust_id
AND d.max_modi_date > t.modi_date
SET t.status = 'Duplicate'
Given sample data:
_row cust_id modi_date
------- ----------
1 444 2019-10-28
2 444 2019-10-28
3 444 2019-10-29
4 444 2019-10-30
5 444 2019-10-30
the query in this answer would flag rows 1 thru 3, set status column to to 'Duplicate'. Rows 4 and 5 would not be marked, because they both have the same (maximum) modi_date.
We would also achieve the same result if we omitted the HAVING clause from the inline view query.
Here is a quick and dirty way:
UPDATE test_sql_duplicate SET status = 'Duplicate'
WHERE cust_id IN (
SELECT t.id FROM (
SELECT
modi_date date,
cust_id id,
COUNT(*) OVER(PARTITION BY cust_id) cnt,
MAX(modi_date) OVER() maxDate
FROM test_sql_duplicate
) t
WHERE t.date < maxDate OR t.cnt > 1);

SQL/MySQL DELETE all rows EXCEPT 2 of them

I have a database table setup like this:
id | code | group_id | status ---
---|-------|---------|------------
1 | abcd1 | group_1 | available
2 | abcd2 | group_1 | available
3 | adsd3 | group_1 | available
4 | dfgd4 | group_1 | available
5 | vfcd5 | group_1 | available
6 | bgcd6 | group_2 | available
7 | abcd7 | group_2 | available
8 | ahgf8 | group_2 | available
9 | dfgd9 | group_2 | available
10 | qwer6 | group_2 | available
In the example above, each group_id has 5 total rows (arbitrary for example, total rows will be dynamic and vary), I need to remove every row that matches available in status except for 2 of them (which 2 does not matter, as long as there are 2 of them remaining)
Basically every unique group_id should only have 2 total rows with status of available. I am able to do a simple SQL query to remove all of them, but struggling to come up with a SQL query to remove all except for 2 ... please helppppp :)
If code is unique, you can use subqueries to keep the "min" and "max"
DELETE FROM t
WHERE t.status = 'available'
AND (t.group_id, t.code) NOT IN (
SELECT group_id, MAX(code)
FROM t
WHERE status = 'available'
GROUP BY group_id
)
AND (t.group_id, t.code) NOT IN (
SELECT group_id, MIN(code)
FROM t
WHERE status = 'available'
GROUP BY group_id
)
Similarly, with an auto increment id:
DELETE FROM t
WHERE t.status = 'available'
AND t.id NOT IN (
SELECT MAX(id) FROM t WHERE status = 'available' GROUP BY group_id
UNION
SELECT MIN(id) FROM t WHERE status = 'available' GROUP BY group_id
)
I reworked the subquery into a UNION instead in this version, but the "AND" format would work just as well too. Also, if "code" was unique across the whole table, the NOT IN could be simplified down to excluding the group_id as well (though it would still be needed in the subqueries' GROUP BY clauses).
Edit: MySQL doesn't like subqueries referencing tables being UPDATEd/DELETEd in the WHERE of the query doing the UPDATE/DELETE; in those cases, you can usually double-wrap the subquery to give it an alias, causing MySQL to treat it as a temporary table (behind the scenes).
DELETE FROM t
WHERE t.status = 'available'
AND t.id NOT IN (
SELECT * FROM (
SELECT MAX(id) FROM t WHERE status = 'available' GROUP BY group_id
UNION
SELECT MIN(id) FROM t WHERE status = 'available' GROUP BY group_id
) AS a
)
Another alternative, I don't recall if MySQL complains as much about joins in DELETE/UPDATE....
DELETE t
FROM t
LEFT JOIN (
SELECT MIN(id) AS minId, MAX(id) AS maxId, 1 AS keep_flag
FROM t
WHERE status = 'available'
GROUP BY group_id
) AS tKeep ON t.id IN (tKeep.minId, tKeep.maxId)
WHERE t.status = 'available'
AND tKeep.keep_flag IS NULL
To keep the min and max ids, I think a join is the simplest solution:
DELETE t
FROM t LEFT JOIN
(SELECT group_id, MIN(id) as min_id, MAX(id) as max_id
FROM t
WHERE t.status = 'available'
GROUP BY group_id
) tt
ON t.id IN (tt.min_id, tt.max_id)
WHERE t.status = 'available' AND
tt.group_id IS NULL;
If the column "id" is the PRIMARY KEY or a UNIQUE KEY, then we could use a correlated subquery to get the second lowest value for a particular group_id.
We could then use that to identify rows for group_id that have higher values of the "id" column.
A query something like this:
SELECT t.`id`
, t.`group_id`
FROM `setup_like_this` t
WHERE t.`status` = 'available'
AND t.`id`
> ( SELECT s.`id`
FROM `setup_like_this` s
WHERE s.`status` = 'available'
AND s.`group_id` = t.`group_id`
ORDER
BY s.`id`
LIMIT 1,1
)
We test that as a SELECT first, to examine the rows that are returned. When we are satisfied this query is returning the set of rows we want to delete, we can replace SELECT ... FROM with DELETE t.* FROM to convert it to a DELETE statement to remove the rows.
Error 1093 encountered converting to DELETE statement.
One workaround is to make the query above into a inline view, and then join to the target table
DELETE q.*
FROM `setup_like_this` q
JOIN ( -- inline view, query from above returns `id` of rows we want to delete
SELECT t.`id`
, t.`group_id`
FROM `setup_like_this` t
WHERE t.`status` = 'available'
AND t.`id`
> ( SELECT s.`id`
FROM `setup_like_this` s
WHERE s.`status` = 'available'
AND s.`group_id` = t.`group_id`
ORDER
BY s.`id`
LIMIT 1,1
)
) r
ON r.id = q.id
select id, code, group_id, status
from (
select id, code, group_id, status
, ROW_NUMBER() OVER (
PARTITION BY group_id
ORDER BY id DESC) row_num
) rownum
from a
) q
where rownum < 3

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
;

How to select more rows from subquery

When I'm trying to run this query:
select * FROM `activity`
WHERE user_id = 1
AND activity_id NOT LIKE (select activity_id from activity where user_id = 1 ORDER BY activity_id DESC LIMIT 8)
I get the follow error:
Subquery returns more than 1 row
How can I solve this problem? I want to select the activity_id from the table excluding the latest 8 activity_id's for a certain user.
NOT LIKE is expecting an expression or a value to compare against and not a resultset.
Change NOT LIKE for NOT IN
Try this one:
SELECT * FROM `activity`
WHERE user_id = 1 AND activity_id NOT IN (
SELECT activity_id FROM activity WHERE user_id = 1
ORDER BY activity_id DESC LIMIT 8)
Solved it by doing this:
$sql2 = "DELETE t1.*
FROM activity t1
left join (select activity_id from activity where user_id = '".$row['user_id']."' ORDER BY activity_id DESC LIMIT 8) t2
on (t1.activity_id = t2.activity_id)
where t2.activity_id is null
and t1.user_id = '".$row['user_id']."'";