Counting one field of table in other table - sql

I wrote a script in oracle. But it does not give me the result that i want.
I need this one, imagine i have two table. Order_table and book table.
My order table is like this
ORDER_TABLE Table
ID TYPE_ID VALUE_ID
1 11 null
2 11 null
3 11 null
4 12 null
5 11 null
Book Table
ID ORDER_TYPE DELETED
1 1 F
2 null F
3 5 F
4 5 F
5 4 F
6 4 F
7 3 T
My script is like this
Select *
From (
Select Newtable.Counter As Value_id,
o.Id As Id,
o.Type_id As Type_id
From (
Select (Count B.Order_Type) As Counter,
B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc
) newtable,
order_table o
where o.id = newtable.id
and o.type_id = 11
)
order by id asc;
Result is like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
It is not showing that second and third id has 0 count, Have can i show 0 count too ?
Result should be like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
0 11 2
0 11 3

First, do not use implicit JOIN syntax(comma separated), that's one of the reason this mistakes are hard to catch! Use the proper JOIN syntax.
Second, your problem is that you need a left join, not an inner join , so try this:
Select *
From (Select coalesce(Newtable.Counter,0) As Value_id,
o.Id As Id,
o.Type_id As Type_id
From order_table o
LEFT JOIN (Select Count(B.Order_Type) As Counter, B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc) newtable
ON(o.id = newtable.id)
WHERE o.type_id = 11)
order by id asc;

Oracle Setup:
CREATE TABLE order_table ( id, type_id, value_id ) AS
SELECT 1, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 2, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 3, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 4, 12, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 5, 11, CAST( NULL AS INT ) FROM DUAL;
CREATE TABLE book ( id, order_type, deleted ) AS
SELECT 1, 1, 'F' FROM DUAL UNION ALL
SELECT 2, NULL, 'F' FROM DUAL UNION ALL
SELECT 3, 5, 'F' FROM DUAL UNION ALL
SELECT 4, 5, 'F' FROM DUAL UNION ALL
SELECT 5, 4, 'F' FROM DUAL UNION ALL
SELECT 6, 4, 'F' FROM DUAL UNION ALL
SELECT 7, 3, 'T' FROM DUAL;
Query:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o
LEFT OUTER JOIN
book b
ON ( o.id = b.order_type AND b.deleted = 'F' )
WHERE o.type_id = 11
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
Output:
VALUE_ID ID TYPE_ID
-------- -- -------
2 5 11
1 1 11
0 3 11
0 2 11
However, if you did want to use the legacy Oracle comma-join syntax then you can get the same result with:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o,
book b
WHERE o.type_id = 11
AND b.order_type (+) = o.id
AND b.deleted (+) = 'F'
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
But please don't as the ANSI/ISO joins are much easier to comprehend the join conditions.

You could also do this with a scalar subquery, which may or may not be more performant than the left join versions described in the other answers. (Quite possibly, the optimizer may rewrite it to be a left join anyway!):
with order_table ( id, type_id, value_id ) as (select 1, 11, cast( null as int ) from dual union all
select 2, 11, cast( null as int ) from dual union all
select 3, 11, cast( null as int ) from dual union all
select 4, 12, cast( null as int ) from dual union all
select 5, 11, cast( null as int ) from dual),
book ( id, order_type, deleted ) as (select 1, 1, 'F' from dual union all
select 2, null, 'F' from dual union all
select 3, 5, 'F' from dual union all
select 4, 5, 'F' from dual union all
select 5, 4, 'F' from dual union all
select 6, 4, 'F' from dual union all
select 7, 3, 'T' from dual)
-- end of mimicking your tables; you wouldn't need the above subqueries as you already have the tables.
-- See SQL below:
select (select count(*) from book bk where bk.deleted = 'F' and bk.order_type = ot.id) value_id,
ot.type_id,
ot.id
from order_table ot
order by value_id desc,
id desc;
VALUE_ID TYPE_ID ID
---------- ---------- ----------
2 11 5
2 12 4
1 11 1
0 11 3
0 11 2

Related

How to select all data before current_state in history data in Oracle SQL?

So I have data look like in Picture.
Column name Track is to show the step of the state.
Column name Current_state is the status of the app right now.
Column name Current_state_hist is the history of the status.
So right now the current status now is AP.
I want to Select all the status before the last status right now (AP in Track 13 & 14) without remove the status AP in track 5 - 8.
Can somebody help me for this case? Thank you
Example of the data
You can use EXISTS for that:
Schema and insert statements:
create table table1(id int, track int, current_state varchar(10), current_state_hist varchar(10), total_unit int);
insert into table1 values(1,1,'AP','OD',1)
insert into table1 values(1,2,'AP','OD',1)
insert into table1 values(1,3,'AP','OD',1)
insert into table1 values(1,4,'AP','OD',1)
insert into table1 values(1,5,'AP','AP',1)
insert into table1 values(1,6,'AP','AP',1)
insert into table1 values(1,7,'AP','AP',1)
insert into table1 values(1,8,'AP','AP',1)
insert into table1 values(1,9,'AP','OD',1)
insert into table1 values(1,10,'AP','OD',1)
insert into table1 values(1,11,'AP','OD',1)
insert into table1 values(1,12,'AP','OD',1)
insert into table1 values(1,13,'AP','AP',1)
insert into table1 values(1,14,'AP','AP',1)
Query:
SELECT ID,TRACK,CURRENT_STATE,CURRENT_STATE_HIST,TOTAL_UNIT
FROM TABLE1 T1
WHERE EXISTS
(
SELECT 1 FROM TABLE1 T2
WHERE CURRENT_STATE<>CURRENT_STATE_HIST
AND T1.TRACK<=T2.TRACK
)
Output:
ID
TRACK
CURRENT_STATE
CURRENT_STATE_HIST
TOTAL_UNIT
1
1
AP
OD
1
1
2
AP
OD
1
1
3
AP
OD
1
1
4
AP
OD
1
1
5
AP
AP
1
1
6
AP
AP
1
1
7
AP
AP
1
1
8
AP
AP
1
1
9
AP
OD
1
1
10
AP
OD
1
1
11
AP
OD
1
1
12
AP
OD
1
db<>fiddle here
You can find the latest status without having to query the table twice using the ROW_NUMBER analytic function:
SELECT id, track, current_state, current_state_hist, total_unit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (ORDER BY track DESC)
- ROW_NUMBER() OVER (
PARTITION BY current_state_hist ORDER BY track DESC
) AS rn
FROM table_name t
)
WHERE rn > 0;
Or, from Oracle 12:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
ORDER BY track DESC
ALL ROWS PER MATCH
PATTERN ( ^ {- same_hist+ -} any_hist* )
DEFINE
same_hist AS FIRST(current_state_hist) = current_state_hist
)
Which, for the sample data:
CREATE TABLE table_name (id, track, current_state, current_state_hist, total_unit) AS
SELECT 1, 1, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 2, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 3, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 4, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 5, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 6, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 7, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 8, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 9, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 10, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 11, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 12, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 13, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 14, 'AP', 'AP', 1 FROM DUAL;
Both output:
ID
TRACK
CURRENT_STATE
CURRENT_STATE_HIST
TOTAL_UNIT
1
12
AP
OD
1
1
11
AP
OD
1
1
10
AP
OD
1
1
9
AP
OD
1
1
8
AP
AP
1
1
7
AP
AP
1
1
6
AP
AP
1
1
5
AP
AP
1
1
4
AP
OD
1
1
3
AP
OD
1
1
2
AP
OD
1
1
1
AP
OD
1
I want the ouput is to select all except the last 2 row... because it's current status...
If you just want to ignore the last 2 rows then: order the rows, then assign a ROWNUM pseudo-column to the ordered rows, then filter on the ROWNUM to exclude the latest two rows:
SELECT *
FROM (
SELECT t.*,
ROWNUM AS rn
FROM (
SELECT *
FROM table_name
ORDER BY track DESC
) t
)
WHERE rn >= 3;
Or, using the ROW_NUMBER analytic function:
SELECT *
FROM (
SELECT t.*,
ROW_NUMMBER() OVER (ORDER BY track DESC) AS rn
FROM table_name t
)
WHERE rn >= 3;
Or, from Oracle 12:
SELECT *
FROM table_name
ORDER BY track DESC
OFFSET 2 ROWS
FETCH FIRST 100 PERCENT ONLY;
db<>fiddle here

ORACLE SQL | If a column contains a value, then it will exclude a different value from the same column

I have this query that returns the data below it
select LISTAGG(d.DOCUMENT_TYPE_CD, ',') WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d;
VALUE
---------
CI,ECI,POA
now I'm trying to add a condition whenever 'ECI' value is present, it should exclude 'CI' in the result like this one below
VALUE
---------
ECI,POA
I tried using case statement in where condition it prompted an error
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d
where CASE d.DOCUMENT_TYPE_CD
WHEN 'ECI' THEN d.DOCUMENT_TYPE_CD <> 'CI'
END;
ORA-00905: missing keyword
00905. 00000 - "missing keyword"
*Cause:
*Action:
Error at Line: 7 Column: 36
is there any other way I could resolve this?
See if this helps; read comments within code.
SQL> with
2 test (id, document_type_cd) as
3 -- sample data
4 (select 1, 'ECI' from dual union all
5 select 1, 'CI' from dual union all
6 select 1, 'POA' from dual union all
7 --
8 select 2, 'CI' from dual union all
9 select 2, 'POA' from dual union all
10 --
11 select 3, 'XYZ' from dual union all
12 select 3, 'ABC' from dual
13 ),
14 temp as
15 -- see whether CI and ECI exist per each ID
16 (select id,
17 sum(case when document_type_cd = 'CI' then 1 else 0 end) sum_ci,
18 sum(case when document_type_cd = 'ECI' then 1 else 0 end) sum_eci
19 from test
20 group by id
21 ),
22 excl as
23 -- exclude CI rows if ECI exist for that ID
24 (select a.id,
25 a.document_type_cd
26 from test a join temp b on a.id = b.id
27 where a.document_type_cd <> case when b.sum_ci > 0 and b.sum_eci > 0 then 'CI'
28 else '-1'
29 end
30 )
31 -- finally:
32 select e.id,
33 listagg(e.document_type_cd, ',') within group (order by e.document_type_cd) result
34 from excl e
35 group by e.id;
ID RESULT
---------- --------------------
1 ECI,POA
2 CI,POA
3 ABC,XYZ
SQL>
Something like this:
select LISTAGG(d.DOCUMENT_TYPE_CD, ',')
WITHIN GROUP (ORDER BY D.DOCUMENT_TYPE_CD) as value
from test_table d,
(select sum (case when DOCUMENT_TYPE_CD = 'CI' then 1 else 0 end) C
from test_table) A
where d.DOCUMENT_TYPE_CD <> case when A.c > 0 then 'CI' when A.c = 0 then ' ' end;
DEMO
You may identify the presence of both the values with two conditional aggregations in the same group by and then replace CI inside the result of listagg in one pass.
with a(id, cd) as (
select 1, 'ABC' from dual union all
select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
select 2, 'XYZ' from dual union all
select 2, 'ECI' from dual union all
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
select 3, 'CI' from dual union all
select 3, 'POA' from dual union all
select 4, 'ABC' from dual union all
select 4, 'DEF' from dual
)
select
id,
ltrim(
/*Added comma in case CI will be at the beginning*/
replace(
',' || listagg(cd, ',') within group (order by cd asc),
decode(
/*If both are present, then replace CI. If not, then do not replace anything*/
max(decode(cd, 'CI', 1))*max(decode(cd, 'ECI', 1)),
1,
',CI,'
),
','
),
','
) as res
from a
group by id
ID | RES
-: | :----------
1 | ABC,ECI,POA
2 | ECI,POA,XYZ
3 | CI,POA
4 | ABC,DEF
db<>fiddle here
Instead of using GROUP BY, you can also use windowing (aka analytic) functions to check the presence of ECI per group (test data shamelessly stolen from #littlefoot):
with
test (id, document_type_cd) as
-- sample data
(select 1, 'ECI' from dual union all
select 1, 'CI' from dual union all
select 1, 'POA' from dual union all
--
select 2, 'CI' from dual union all
select 2, 'POA' from dual union all
--
select 3, 'XYZ' from dual union all
select 3, 'ABC' from dual
),
temp as
(select id,
document_type_cd,
sum(case when document_type_cd = 'ECI' then 1 else 0 end) over (partition by id) as sum_eci
from test
)
select a.id,
listagg(a.document_type_cd, ',') within group (order by a.document_type_cd) result
from temp a
where a.document_type_cd != 'CI' or sum_eci = 0
group by a.id;

How do I display Rows in a table where all values but the first one for a column is null

So I am trying to pull rows from a table where there are more than one version for an ID that has at least one person for the ID that is not null but the versions that come after it are null.
So, if i had a statement like:
select ID, version, person from table1
the output would be:
ID Version Person
-- ------- ------
1 1 Tom
1 2 null
1 3 null
2 1 null
2 2 null
2 3 null
3 1 Mary
3 2 Mary
4 1 Joseph
4 2 null
4 3 Samantha
The version number can have an infinite value and is not limited.
I want to pull ID 1 version 2/3, and ID 4 Version 2.
So in the case of ID 2 where the person is null for all three rows I don't need these rows. And in the case of ID 3 version 1 and 2 I don't need these rows because there is never a null value.
This is a very simple version of the table I am working with but the "real" table is a lot more complicated with a bunch of joins already in it.
The desired output would be:
ID Version Person
-- ------- ------
1 2 null
1 3 null
4 2 null
The result set that I am looking for is where in a previous version for the same ID there was a person listed but is now null.
You are seeking all rows where the person is not null and that id has null rows, and the not null person version is less than the null version for the same person id:
Edited predicate based on comment
with sample_data as
(select 1 id, 1 version, 'Tom' person from dual union all
select 1, 2, null from dual union all
select 1, 3, null from dual union all
select 2, 1, null from dual union all
select 2, 2, null from dual union all
select 2, 3, null from dual union all
select 3, 1, 'Mary' from dual union all
select 3, 2, 'Mary' from dual union all
select 4, 1, 'Joseph' from dual union all
select 4, 2, null from dual union all
select 4, 3, 'Samantha' from dual)
select *
from sample_data sd
where person is null
and exists
(select 1 from sample_data
where id = sd.id
and person is not null
and version < sd.version);
/* Old predicate
and id in
(select id from sample_data where person is not null);
*/
I think this query translates pretty nicely into what you asked for?
List all the rows (R) where the person is null, but only if a previous row (P) with a non-null name exists.
select *
from table1 r
where r.person is null
and exists(
select 'x'
from table1 p
where p.id = r.id
and p.version < r.version
and p.person is not null
);
I believe the below should work.
select ID, listagg(version, ', ') within group (order by version) as versions
from table1 t1
where 0 < (select count(*) from table1 t1A where t1A.ID = t1.ID and t1A.version is not null)
and 0 < (select count(*) from table1 t1B where t1B.ID = t1.ID and t1B.version is null)
and person is null
group by ID
This should do what you want:
select id, version, person
from
(
select id, version, person,
lag(person, 1) ignore nulls
over (partition by id
order by version) as x
from table1
) dt
where person is null
and x is not null

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.

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