How to pick records based on specific condition - sql

I have a table named as "ticket_lc" look like below
From the above table i need to pick only the records which satisfy the condition
condition: the ticket status should be in "assigned" and "closed" and "resolved"
so in the above table only 102 ticket is satisfying the condition, if the ticket contains other than these 3 then my query should not pick those tickets.
can anybody help me on this..!!!
Thanks

Below is for BigQuery Standard SQL
#standardSQL
SELECT ticket_id
FROM `project.dataset.ticket_lc`
GROUP BY ticket_id
HAVING COUNT(DISTINCT status) = 3
AND COUNTIF(LOWER(status) NOT IN ('assigned', 'closed', 'resolved')) = 0
Yo can test, play with above using sample data from your question as in below example
#standardSQL
WITH `project.dataset.ticket_lc` AS (
SELECT 101 ticket_id, 'Assigned' status UNION ALL
SELECT 101, 'Pending' UNION ALL
SELECT 101, 'Resolved' UNION ALL
SELECT 101, 'Closed' UNION ALL
SELECT 102, 'Assigned' UNION ALL
SELECT 102, 'Resolved' UNION ALL
SELECT 102, 'Closed' UNION ALL
SELECT 103, 'Assigned' UNION ALL
SELECT 103, 'Pending' UNION ALL
SELECT 103, 'Pending' UNION ALL
SELECT 103, 'Assigned' UNION ALL
SELECT 103, 'Resolved' UNION ALL
SELECT 103, 'Closed'
)
SELECT ticket_id
FROM `project.dataset.ticket_lc`
GROUP BY ticket_id
HAVING COUNT(DISTINCT status) = 3
AND COUNTIF( LOWER(status) NOT IN ('assigned', 'closed', 'resolved')) = 0
with result
Row ticket_id
1 102

You can do aggregation :
select ticket_id
from table t
group by ticket_id
having sum( case when status not in ('assigned', 'closed', 'resolved') then 1 else 0 end ) = 0 and
count(*) = 3;
If you have a duplicate status for ticket then use distinct inside count().

Kind of an interesting problem. I came up with this checking if everything is covered and the amount is correct:
WITH t AS (SELECT * FROM UNNEST(
[struct(101 as ticket_id, 'assigned' as status),(101,'closed'),
(102,'assigned'),(102,'resolved'),(102,'closed'),
(104,'assigned'),(104,'assigned'),(104,'closed'),
(103,'assigned'),(103,'pending'),(103,'pending'),(103,'assigned'),(103,'resolved'),(103,'closed')]
)
)
SELECT ticket_id, array_agg(distinct status) as st
FROM t
group by 1
having (SELECT count(1)=3 FROM unnest(st) a left join unnest(['assigned','resolved','closed']) b on a=b)
Includes adjusted sample data to cover more problem cases.

Related

How to compare different values within the same column

I having two tables emp and type.
create table EMP(ID number(10), effective_date date);
EID Effective_date
--------------------
1 02/14/2023
2 02/15/2023
3 04/30/2023
4 03/24/2023
create table type(ID number(10),contract_type varchar2(2));
TID contract_type
------------------
1 P
1 S
1 P
2 S
2 S
3 P
3 S
4 S
I am looking EID which is having contract type is 'S' in type table. (or emp table with effective date is greater than sysdate and in the type table with only contract_type ='S')
Actual result :
2
4
My query is not giving the correct results.
select emp.EID
from emp,type
where EID = TID
contract_type ='S'
effective_date >= sysdate
group by TID
having count(TID) >= 1;
If you want to keep your idea with COUNT and GROUP BY, you should count other contract types than the 'S' ones and check this is 0:
SELECT e.eid
FROM emp e
JOIN type t ON e.eid = t.tid
WHERE
e.effective_date >= sysdate
GROUP BY e.eid
HAVING COUNT(CASE WHEN t.contract_type <> 'S' THEN 1 END) = 0;
This query will return 2 and 4 for your sample data.
Try out: db<>fiddle
Another option is as already said here using NOT EXISTS.
Take care of following difference to the NOT EXISTS approach: The query in Tim's answer will also fetch id's of table "emp" that don't appear at all in table "type". My query here will not fetch such id's.
It's up to you to decide whether this is possible at all and what to do in this case.
Changing JOIN to LEFT JOIN in above query will eliminate this difference.
I would use exists logic here:
SELECT EID
FROM EMP e
WHERE effective_date >= SYSDATE AND
NOT EXISTS (
SELECT 1
FROM "type" t
WHERE t.TID = e.EID AND
t.contract_type <> 'S'
);
You could use Count() Over() analytic function to check for type 'S' and number of different types per ID.
SELECT DISTINCT ID
FROM ( Select e.EID "ID",
Count(CASE t.CONTRACT_TYPE WHEN 'S' THEN 'S' END) Over(Partition By t.ID Order By t.ID) "NUM_OF_S",
Count(Distinct t.CONTRACT_TYPE) Over(Partition By t.ID) "NUM_OF_TYPES",
TRUNC(e.EFFECTIVE_DATE) - TRUNC(SYSDATE) "DAYS_AFTER_SYSDATE"
From emp_cte e
Inner Join type_cte t ON(t.ID = e.EID) )
WHERE NUM_OF_S > 0 And -- Type 'S' exists for ID AND
NUM_OF_TYPES = 1 And -- It is the only type AND
DAYS_AFTER_SYSDATE > 0 -- EFFECTIVE_DATE is after SYSDATE
With your sample data ...
WITH
emp_cte(EID, EFFECTIVE_DATE) AS
(
Select 1, To_Date('02/14/2023', 'mm/dd/yyyy') From Dual Union All
Select 2, To_Date('02/15/2023', 'mm/dd/yyyy') From Dual Union All
Select 3, To_Date('04/30/2023', 'mm/dd/yyyy') From Dual Union All
Select 4, To_Date('03/24/2023', 'mm/dd/yyyy') From Dual
),
type_cte(ID, CONTRACT_TYPE) AS
(
Select 1, 'P' From Dual Union All
Select 1, 'S' From Dual Union All
Select 1, 'P' From Dual Union All
Select 2, 'S' From Dual Union All
Select 2, 'S' From Dual Union All
Select 3, 'P' From Dual Union All
Select 3, 'S' From Dual Union All
Select 4, 'S' From Dual
)
... result would be ...
-- ID
-- ----------
-- 2
-- 4

Apply value to group

need help with,
if any ID with same Groupid has Yes in Payable, add Yes value to Results, otherwise blank.
This should be applicable for hundreds of IDs grouped in hundreds of GroupIDs.
ID
GroupID
Payable
Result
111
a
Yes
Yes
222
a
Yes
333
a
Yes
444
b
Yes
555
b
Yes
Yes
777
b
Yes
888
c
I tried to group based on groupID and created a case where groupId count equals or is higher as 1 and the eligibility is Yes.
Your question is a bit unclear due to lack of information about constraints, all the values in table etc.
With the given information tho, your task might be done by following query:
with groupData as
(
Select groupid,payable from put_your_table_name_here
where payable is not null
group by groupid
)
Select pt.id
,pt.groupId
,gd.payable
,pt.result
from put_your_table_name_here pt
left join groupData gd on gd.groupid=pt.groupid
The query has it drawbacks- you should give some more info about constraints, but generally it should work.
If you wouldnt want to have null values in payable column, you could change left join to join.
WITH CTE(ID, GroupID, Payable)AS
(
SELECT 111,'A','YES'
UNION ALL
SELECT 222,'A',''
UNION ALL
SELECT 333,'A',''
UNION ALL
SELECT 444,'B',''
UNION ALL
SELECT 555,'B','YES'
UNION ALL
SELECT 777,'B',''
UNION ALL
SELECT 888,'C',''
)
SELECT C.ID,C.GroupID,C.Payable,F.FLAG
FROM CTE AS C
JOIN
(
SELECT X.GROUPID,MAX(PAYABLE)FLAG
FROM CTE AS X
GROUP BY X.GroupID
)F ON C.GroupID=F.GroupID
ORDER BY C.ID;
You can try something like this
or
SELECT C.ID,C.GroupID,C.Payable,
FIRST_VALUE(C.PAYABLE)OVER(PARTITION BY C.GROUPID ORDER BY C.PAYABLE DESC)XCOL
FROM CTE AS C
ORDER BY C.ID;
with data (ID,GroupID,Payable) as (
Select 111, 'a', 'Yes' from dual union all
Select 222, 'a', null from dual union all
Select 333, 'a', null from dual union all
Select 444, 'b', null from dual union all
Select 555, 'b', 'Yes' from dual union all
Select 777, 'b', null from dual union all
Select 888, 'c', null from dual
)
,result as(
select GroupID, case when Count(*) > 1 then 'Yes' else null end Result
from data
group by GroupID)
Select * from data
Join result on data.GroupID = result.GroupID
order by data.ID,data.GroupID
Db fiddle link

How can i find rows before a specific value?

I have the next row and what I want to do is to select all the rows before the type "shop". I tried using case in the "where clause" but I didn't get any result. How can I do it?
|id|visitnumber|type |
|01| 1|register|
|01| 2|visit |
|01| 3|visit |
|01| 4|shop |
|01| 5|visit |
For example, what I want to get is the visitnumber before type = "shop".
it would be very helpful because what I'm trying to do is to get all the actions that happened before an specific event on big query.
|id|numberofvisits|
|01| 3|
One method uses correlated subqueries:
select id, count(*)
from t
where visitnumber < (select min(t2.visitnumber) from t t2 where t2.id = t.id and type = 'shop')
group by id;
However, in BigQuery, I prefer an approach using window functions:
select id, countif(visitnumber < visitnumber_shop)
from (select t.*,
min(case when type = 'shop' then visitnumber end) over (partition by id) as visitnumber_shop
from t
) t
group by id;
This has the advantage of keeping all ids even those that don't have a "shop" type.
One option uses a subquery for filtering:
select id, count(*) number_of_visits
from mytable t
where t.visit_number < (
select min(t1.visit_number)
from mytable t
where t1.id = t.id and t1.type = 'shop'
)
group by id
You can also use window functions:
select id, count(*) number_of_visits
from (
select
t.*,
countif(type = 'shop') over(partition by id order by visit_number) has_shop
from mytable t
) t
where has_shop = 0
group by id
Below option is for BigQuery Standard SQL
#standardSQL
SELECT id,
ARRAY_LENGTH(SPLIT(REGEXP_EXTRACT(',' || STRING_AGG(type ORDER BY visitnumber), r'(.*?),shop'))) - 1 AS number_of_visits_before_first_shop
FROM `project.dataset.table`
GROUP BY id
You can test, play with above using dummy data as in below example
#standardSQL
WITH `project.dataset.table` AS (
SELECT '01' id, 1 visitnumber, 'register' type UNION ALL
SELECT '01', 2, 'visit' UNION ALL
SELECT '01', 3, 'visit' UNION ALL
SELECT '01', 4, 'shop' UNION ALL
SELECT '01', 5, 'visit' UNION ALL
SELECT '02', 1, 'register' UNION ALL
SELECT '02', 2, 'visit' UNION ALL
SELECT '02', 3, 'visit' UNION ALL
SELECT '03', 1, 'shop' UNION ALL
SELECT '03', 2, 'shop' UNION ALL
SELECT '03', 3, 'visit'
)
SELECT id,
ARRAY_LENGTH(SPLIT(REGEXP_EXTRACT(',' || STRING_AGG(type ORDER BY visitnumber), r'(.*?),shop'))) - 1 AS number_of_visits_before_first_shop
FROM `project.dataset.table`
GROUP BY id
with result
Row id number_of_visits_before_first_shop
1 01 3
2 02 null
3 03 0
This is the query i run on Big Query with an Analytics 360 test dataset:
select
id,
visitnumber,
countif(hit_number < hitnumber_quickviewclick) as hitsprev_quickviewclick
from (
select
a.fullVisitorID as id,
a.visitnumber as visitnumber,
h.hitNumber as hit_number,
MIN (case when h.eventInfo.eventAction = 'Quickview Click' then h.hitNumber end) over (partition by a.fullVisitorID) as hitnumber_quickviewclick
FROM `bigquery-public-data.google_analytics_sample.ga_sessions_20170725` as a
CROSS JOIN UNNEST(hits) as h
) as T
group by 1,2;
I wanted to make a query where i could find the total number of hits before the event action 'quickview click' hitted. If this is wrong or can be improved let me know!
Thanks a lot, guys!
This is how I would approach in SQL in general:
select count(*)
from yourtable yt
where type = 'visit' and not exists (
select 1
from yourtable yt2
where yt.id > yt2.id and yt2.type = 'shop'
)
However, I would very much think about situations when we want to find visits before the next shop... And the next shop... And the next shop. For that purpose you could find out the ids of shop and group by intervals.

Condition in subquery- select one value if subquery return 2 records else the actual value

I have a subquery inside a big query which returns multiple values sometime and some time only one value. Below is my query and the returned values
select tran.customer_type from transaction_record tran where tran.TRANSACTION_ID=txn.id
customer_type can be 2 records - "LP" and "NA"
or
customer_type can be 2 records - "SOEMTHING ELSE" and "NA"
or
customer_type can be 1 records - "NA"
Here my probem is if i have 2 records i have to print value without NA and if i have one record i have to print what ever be the value is
Not exectly efficient (2 queries), but it should work!
Inner query counts status, id combinatios per group and outer query
removes all NA statuses that have another record on same ID.
Innermost query is just for table simulation (I like it more than create table, insert scripts).
SELECT * FROM
(
SELECT status, id, count(*)
OVER (PARTITION BY id ORDER BY 3 ) AS rn
from (
SELECT 'NA' status, 1 id FROM dual
UNION ALL
SELECT 'LP' status, 1 id FROM dual
UNION ALL
SELECT 'NA' status, 2 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 2 id FROM dual
UNION ALL
SELECT 'NA' status, 3 id FROM dual
UNION ALL
SELECT 'NA' status, 5 id FROM dual
UNION ALL
SELECT 'LP' status, 5 id FROM dual
UNION ALL
SELECT 'NA' status, 6 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 6 id FROM dual
UNION ALL
SELECT 'NA' status, 22 id FROM dual
))
WHERE NOT (status = 'NA' AND rn=2)

Comparing between rows in same table in Oracle SQL

I'm trying to find the best way to compare between rows by CustomerID and Status. In other words, only show the CustomerID when the status are equal between multiple rows and CustomerID. If not, don't show the CustomerID.
Example data
CUSTOMERID STATUS
1000 ACTIVE
1000 ACTIVE
1000 NOT ACTIVE
2000 ACTIVE
2000 ACTIVE
RESULT I'm hoping for
CUSTOMERID STATUS
2000 ACTIVE
You can do this with a WHERE NOT EXISTS:
Select Distinct CustomerId, Status
From YourTable A
Where Not Exists
(
Select *
From YourTable B
Where A.CustomerId = B.CustomerId
And A.Status <> B.Status
)
SELECT DISTINCT o.*
FROM
(
SELECT
CustomerId
FROm
TableName
GROUP BY
CustomerId
HAVING
COUNT(DISTINCT Status) = 1
) t
INNER JOIN TableName o
ON t.CustomerId = o.CustomerId
The only "Code" here is the last 4 lines in the code block. The other is establishing sample data.
with T1 as (
Select 1000 as CUSTOMERID, 'ACTIVE' as STATUS from dual union all
select 1000, 'ACTIVE' from dual union all
select 1000, 'NOT ACTIVE' from dual union all
select 2000, 'ACTIVE' from dual union all
select 2000, 'ACTIVE' from dual )
SELECT customerID, max(status) as status
FROM T1
GROUP BY customerID
HAVING count(distinct Status) = 1
I used a CTE to setup sample data and called this Common table Expression T1.
Order of operations matter here. First the table T1 is identified
second the engine groups by customer ID
third the engine limits the results to those records having a distinct record status matching 1 and only 1 value.
4th the engine picks the max status which will always be 1 value. min/max it doesn't matter as there is only 1 possible value. note, we have to use an aggregate here since we can't group by status or you wouldn't get the desired results.
Here's a pretty simple one using IN:
SELECT DISTINCT CustomerID, Status
FROM My_Table
WHERE CustomerID IN
(SELECT CustomerID
FROM My_Table
GROUP BY CustomerID
HAVING COUNT(Distinct Status) = 1)
Addition: based on your comment, it seems what you really want is all the IDs that do not have a 'Not Active' row, which is actually easier:
SELECT Distinct CustomerID, Status
FROM My_Table
WHERE CustomerID NOT IN
(SELECT CustomerID
FROM My_Table
WHERE Status = 'Not Active')
This is a SQL Server answer, I believe it should work in Oracle.
SELECT
a.AGMTNUM
FROM TableA a
WHERE NOT EXISTS (SELECT 1 FROM TableB b WHERE b.Status = 'NOT ACTIVE' AND a.AGMTNUM = b.AGMTNUM)
AND EXISTS (SELECT 1 FROM TableB c WHERE c.Status = 'ACTIVE' AND a.AGMTNUM = c.AGMTNUM)
This will only return values that have at least one 'ACTIVE' value and no 'NOT ACTIVE' values.