Counting rows where a column may different values - sql

I have a table like the following:
id letter number
1 A 1
2 A 2
3 A 3
4 B 1
5 C 1
6 C 2
I need to count the number of id's where the letter A has a number 1 OR 2. That's the easy one, and I got it. But now I need to count id's where the letter A has a number 1 AND a number 2, etc. Then the same for each letter. So I would get:
letter count
A 3
C 2
I dont care about B since the count would be 1. Thanks.

Use a combination of analytical functions (PARTITION BY) and GROUP BY:
with v_data as (
select 1 id, 'A' letter, 1 num from dual union all
select 2 id, 'A' letter, 2 num from dual union all
select 3 id, 'A' letter, 3 num from dual union all
select 4 id, 'B' letter, 1 num from dual union all
select 5 id, 'C' letter, 1 num from dual union all
select 6 id, 'C' letter, 2 num from dual
)
select letter, sum(cnt1) as cnt1, sum(cnt2) as cnt2, count(*) cnt_overall from (
select v1.*,
sum(case when num = 1 then 1 else 0 end) over (partition by letter) as cnt1,
sum(case when num = 2 then 1 else 0 end) over (partition by letter) as cnt2
from v_data v1
)
group by letter
having sum(cnt1) > 0 and sum(cnt2) > 0
Explanation:
the inner query returns alls rows and computes the number of occurrences of 1 / 2 for the row's id
the outer query computes the number of rows for a given id and get rids of the ids that don't have both values 1 and 2

Related

Oracle - generate a running number by group

I need to generate a running number / group sequence inside a select statement for a group of data.
For example
Group Name Sequence
1 a 1
1 b 2
1 c 3
2 d 1
2 e 2
2 f 3
So for each group the sequence should be a running number starting with 1 depending on the order of column"Name".
I already pleayed around with Row_Number() and Level but I couldn't get a solution.
Any idea how to do it?
Analytic functions help.
SQL> with test (cgroup, name) as
2 (select 1, 'a' from dual union all
3 select 1, 'b' from dual union all
4 select 1, 'c' from dual union all
5 select 2, 'd' from dual union all
6 select 2, 'e' from dual union all
7 select 2, 'f' from dual
8 )
9 select cgroup,
10 name,
11 row_number() over (partition by cgroup order by name) sequence
12 from test
13 order by cgroup, name;
CGROUP N SEQUENCE
---------- - ----------
1 a 1
1 b 2
1 c 3
2 d 1
2 e 2
2 f 3
6 rows selected.
SQL>
Try this
SELECT
"Group",
Name,
DENSE_RANK() OVER (PARTITION BY "Group" ORDER BY Name) AS Sequence
FROM table;

Get last value from a certain group (Oracle)

I have something like this
Date Group ID
11/01 'A' 1
12/01 'A' 2
13/01 'B' 3
14/01 'B' 4
What i basically want is to get for example the latest from group 'A'
Date Group ID LatestID_from_GROUP_A_ordered_by_recent_date
11/01 'A' 1 2
12/01 'A' 2 2
13/01 'B' 3 2
14/01 'B' 4 2
or at least something like this
Date Group ID LatestID_from_GROUP_A_ordered_by_recent_date
11/01 'A' 1 null
12/01 'A' 2 null
13/01 'B' 3 2
14/01 'B' 4 2
How about this:
with demo (somedate, somegroup, id) as
( select date '2018-01-11', 'A', 1 from dual union all
select date '2018-01-12', 'A', 2 from dual union all
select date '2018-01-13', 'B', 3 from dual union all
select date '2018-01-14', 'B', 4 from dual union all
select date '2018-01-15', 'A', 5 from dual -- example from comments
)
select somedate, somegroup, id
, ( select max(id) keep (dense_rank last order by somedate)
from demo
where somegroup = 'A' ) as last_a
from demo;
SOMEDATE SOMEGROUP ID LAST_A
----------- --------- ---------- ----------
11/01/2018 A 1 5
12/01/2018 A 2 5
13/01/2018 B 3 5
14/01/2018 B 4 5
15/01/2018 A 5 5
Note the max(id) is only a tiebreaker in the event of multiple rows with the last date.
Gordon was almost there.
You want to create a window over your whole query, but only pick the biggest value of 'A':
select
t.*,
max(case when group = 'A' then id end) over (partition by 1) as latest_from_a
from t
'partition by 1' will create a window of your complete result set because it only groups by a single static value: 1.
The logic seems to be:
select t.*,
max(case when group = 'A' then id end) over (order by date) as latest_from_a
from t;
The above gets the cumulative maximum up to each date. If you want the overall maximum:
select t.*,
max(case when group = 'A' then id end) over () as latest_from_a
from t;

counting most recent consecutive rows with like data using tabibitosan

My project is using an Oracle SQL database. I have a historical table that appends task status on a weekly basis, and am attempting to query the number of weeks a task that is currently off track has been off track. Here's an example excerpt from my source historical table:
ID WEEK ON_TRACK
1 1 N
1 2 Y
1 3 N
1 4 N
1 5 N
2 1 N
2 2 N
2 3 Y
2 4 Y
2 5 N
3 1 N
3 2 N
3 3 Y
3 4 Y
3 5 Y
I'm looking to return the count of consecutive "N" values in ON_TRACK starting backwards from the latest append. For the above example data, I'd like the query to return:
ID WKS_OFF_TRACK
1 3
2 1
3 0
I've done some research, and it looks like the Tabibitosan method is the most logical approach, and I've found ample examples to give the max consecutive values that match 1 criteria, but I'm having trouble tweaking to return the most recent consecutive values that match 2 criteria (ID and ON_TRACK).
Here's what I have so far
--this step creates a temp table with unique IDs for each weekly append to the historical table, and a 1 (if ON_TRACK = N) or 0 (if ON_TRACK = Y). This results in the expected info.
WITH HIST_TBL AS (
SELECT DISTINCT(ID),
CASE ON_TRACK
WHEN 'N' THEN 1
ELSE 0
END AS OFF_TRACK,
WEEK
FROM SOURCE_HISTORICAL_TBL
ORDER BY ID,WEEK DESC)
-- end of temp table
--this is where Im struggling I want one line per project number, and the sum of the latest string of 1s (weeks the task has been off track), until a 0 is reached.
SELECT ID,
SUM(OFF_TRACK) AS WKS_OFF_TRACK
FROM (SELECT WEEK,
ID,
OFF_TRACK,
ROW_NUMBER() OVER (ORDER BY WEEK DESC) - ROW_NUMBER() OVER
(PARTITION BY ID,OFF_TRACK ORDER BY WEEK DESC) GRP
FROM HIST_TBL)
GROUP BY ID, GRP
ORDER BY ID;
This code results in the a cumulative sum of all weeks each project has been off track, which for my example data would be:
ID WKS_OFF_TRACK
1 4
2 3
3 2
Any ideas where I'm going wrong?
Here is one method that assumes people were "on track" at some point in time:
select sht.id, count(*)
from SOURCE_HISTORICAL_TBL sht
where sht.week > (select max(sht2.week)
from SOURCE_HISTORICAL_TBL sht2
where sht2.id = sht.id and sht2.on_track = 'Y'
)
group by sht.id;
Otherwise, you need one more condition:
select sht.id, count(*)
from SOURCE_HISTORICAL_TBL sht
where sht.week > (select max(sht2.week)
from SOURCE_HISTORICAL_TBL sht2
where sht2.id = sht.id and sht2.on_track = 'Y'
) or
not exists (select 1
from SOURCE_HISTORICAL_TBL sht2
where sht2.id = sht.id and sht2.on_track = 'Y'
)
group by sht.id;
You can also phrase these as analytic functions:
select id,
sum(case when week > max_week_y or max_week_y is null then 1 else 0 end) as max_off_track
from (select sht.*,
max(case when on_track = 'Y' then week end) over (partition by id) as max_week_y
from SOURCE_HISTORICAL_TBL sht
) sht
group by id;
Note that this version will return 0s for people currently on track.
You can do it in a single table scan:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE SOURCE_HISTORICAL_TBL ( ID, WEEK, ON_TRACK ) AS
SELECT 1, 1, 'N' FROM DUAL UNION ALL
SELECT 1, 2, 'Y' FROM DUAL UNION ALL
SELECT 1, 3, 'N' FROM DUAL UNION ALL
SELECT 1, 4, 'N' FROM DUAL UNION ALL
SELECT 1, 5, 'N' FROM DUAL UNION ALL
SELECT 2, 1, 'N' FROM DUAL UNION ALL
SELECT 2, 2, 'N' FROM DUAL UNION ALL
SELECT 2, 3, 'Y' FROM DUAL UNION ALL
SELECT 2, 4, 'Y' FROM DUAL UNION ALL
SELECT 2, 5, 'N' FROM DUAL UNION ALL
SELECT 3, 1, 'N' FROM DUAL UNION ALL
SELECT 3, 2, 'N' FROM DUAL UNION ALL
SELECT 3, 3, 'Y' FROM DUAL UNION ALL
SELECT 3, 4, 'Y' FROM DUAL UNION ALL
SELECT 3, 5, 'Y' FROM DUAL UNION ALL
SELECT 4, 1, 'N' FROM DUAL UNION ALL
SELECT 5, 1, 'Y' FROM DUAL;
Query 1:
SELECT ID,
GREATEST(
COALESCE( MAX( CASE ON_TRACK WHEN 'N' THEN WEEK END ), 0 )
- COALESCE( MAX( CASE ON_TRACK WHEN 'Y' THEN WEEK END ), 0 ),
0
) AS weeks
FROM SOURCE_HISTORICAL_TBL
GROUP BY id
ORDER BY id
Results:
| ID | WEEKS |
|----|-------|
| 1 | 3 |
| 2 | 1 |
| 3 | 0 |
| 4 | 1 |
| 5 | 0 |

Running count but reset on some column value in select query

I want to achieve a running value, but condition is reset on some specific column value.
Here is my select statement:
with tbl(emp,salary,ord) as
(
select 'A',1000,1 from dual union all
select 'B',1000,2 from dual union all
select 'K',1000,3 from dual union all
select 'A',1000,4 from dual union all
select 'B',1000,5 from dual union all
select 'D',1000,6 from dual union all
select 'B',1000,7 from dual
)
select * from tbl
I want to reset count on emp B if the column value is B, then count is reset to 0 and started again increment by 1:
emp salary ord running_count
A 1000 1 0
B 1000 2 1
K 1000 3 0
A 1000 4 1
B 1000 5 2
D 1000 6 0
B 1000 7 1
Here order column is ord.
I want to achieve the whole thing by select statement, not using the cursor.
You want to define groups were the counting takes place. Within a group, the solution is row_number().
You can define the group by doing a cumulative sum of B values. Because B ends the group, you want to count the number of B after each record.
This results in:
select t.*,
row_number() over (partition by grp order by ord) - 1 as running_count
from (select t.*,
sum(case when emp = 'B' then 1 else 0 end) over (order by ord desc) as grp
from tbl t
) t;

Get distinct rows based on priority?

I have a table as below.i am using oracle 10g.
TableA
------
id status
---------------
1 R
1 S
1 W
2 R
i need to get distinct ids along with their status. if i query for distinct ids and their status i get all 4 rows.
but i should get only 2. one per id.
here id 1 has 3 distinct statuses. here i should get only one row based on priority.
first priority is to 'S' , second priority to 'W' and third priority to 'R'.
in my case i should get two records as below.
id status
--------------
1 S
2 R
How can i do that? Please help me.
Thanks!
select
id,
max(status) keep (dense_rank first order by instr('SWR', status)) as status
from TableA
group by id
order by 1
fiddle
select id , status from (
select TableA.*, ROW_NUMBER()
OVER (PARTITION BY TableA.id ORDER BY DECODE(
TableA.status,
'S',1,
'W',2,
'R',3,
4)) AS row_no
FROM TableA)
where row_no = 1
This is first thing i would do, but there may be a better way.
Select id, case when status=1 then 'S'
when status=2 then 'W'
when status=3 then 'R' end as status
from(
select id, max(case when status='S' then 3
when status='W' then 2
when status='R' then 1
end) status
from tableA
group by id
);
To get it done you can write a similar query:
-- sample of data from your question
SQL> with t1(id , status) as (
2 select 1, 'R' from dual union all
3 select 1, 'S' from dual union all
4 select 1, 'W' from dual union all
5 select 2, 'R' from dual
6 )
7 select id -- actual query
8 , status
9 from ( select id
10 , status
11 , row_number() over(partition by id
12 order by case
13 when upper(status) = 'S'
14 then 1
15 when upper(status) = 'W'
16 then 2
17 when upper(status) = 'R'
18 then 3
19 end
20 ) as rn
21 from t1
22 ) q
23 where q.rn = 1
24 ;
ID STATUS
---------- ------
1 S
2 R
select id,status from
(select id,status,decode(status,'S',1,'W',2,'R',3) st from table) where (id,st) in
(select id,min(st) from (select id,status,decode(status,'S',1,'W',2,'R',3) st from table))
Something like this???
SQL> with xx as(
2 select 1 id, 'R' status from dual UNION ALL
3 select 1, 'S' from dual UNION ALL
4 select 1, 'W' from dual UNION ALL
5 select 2, 'R' from dual
6 )
7 select
8 id,
9 DECODE(
10 MIN(
11 DECODE(status,'S',1,'W',2,'R',3)
12 ),
13 1,'S',2,'W',3,'R') "status"
14 from xx
15 group by id;
ID s
---------- -
1 S
2 R
Here, logic is quite simple.
Do a DECODE for setting the 'Priority', then find the MIN (i.e. one with Higher Priority) value and again DECODE it back to get its 'Status'
Using MOD() example with added values:
SELECT id, val, distinct_val
FROM
(
SELECT id, val
, ROW_NUMBER() OVER (ORDER BY id) row_seq
, MOD(ROW_NUMBER() OVER (ORDER BY id), 2) even_row
, (CASE WHEN id = MOD(ROW_NUMBER() OVER (ORDER BY id), 2) THEN NULL ELSE val END) distinct_val
FROM
(
SELECT 1 id, 'R' val FROM dual
UNION
SELECT 1 id, 'S' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
UNION
SELECT 2 id, 'R' val FROM dual
UNION -- comment below for orig data
SELECT 3 id, 'K' val FROM dual
UNION
SELECT 4 id, 'G' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
))
WHERE distinct_val IS NOT NULL
/
ID VAL DISTINCT_VAL
--------------------------
1 S S
2 R R
3 K K
4 G G