Select Group on Department - oracle

I have data like below. I want to select those Emp_id where only HR is the only DEPT present. From the below, only 100994 and 100998 should be selected.
**EMP_ID DEPT**
100017 FIN
100017 HR
100017 ADMIN
100994 HR
100997 ADMIN
100997 FIN
100998 HR
100999 FIN

Here's one option:
SQL> with test (emp_id, dept) as
2 (select 100017, 'FIN' from dual union all
3 select 100017, 'HR' from dual union all
4 select 100017, 'ADMIN' from dual union all
5 select 100994, 'HR' from dual union all
6 select 100997, 'ADMIN' from dual union all
7 select 100997, 'FIN' from dual union all
8 select 100998, 'HR' from dual union all
9 select 100999, 'FIN' from dual
10 )
11 select emp_id
12 from test
13 group by emp_id
14 having min(dept) = max(dept)
15 and min(dept) = 'HR';
EMP_ID
----------
100998
100994
SQL>

I would recomment to use a subquery to count the records.
If records count for specific id is more than 1 then skip the id.
SELECT `emp_id`, `dept` FROM (
SELECT SUM(1) AS `count`, `emp_id`, `dept`
FROM `table1`
GROUP BY `emp_id`
) AS `derived`
WHERE `count` = 1 AND `dept` = 'HR'

Related

PL/SQL program using CURSOR for frequency distribution in table

finaltableA has two columns WORDS, WCOUNTS. The program should insert every word of intitaltableA into finaltableA exactly once. In the column WCOUNTS the program should put how often the word occurs.
intitaltableA
COVID
is
a
disease
COVID
can
be
treated
with
antibodies
COVID
is
a
serious
disease
there
is
a
vaccination
available
for
COVID
finaltableA should look like this -
WORDS
WCOUNTS
a
3
antibodies
1
available
1
be
1
can
1
COVID
4
disease
2
for
1
is
3
serious
1
there
1
treated
1
vaccination
1
with
1
I should use a cursor to insert and update the table. I am new to PL/SQL.
It is quite obvious that you can do this without any cursor in PLSQL, so I guess that means that you are trying to solve a homework problem or a training question. Anyway, here you can see two simple options to get the result you want, with and without PLSQL.
Option 1 - Without PLSQL
with x ( words )
as
(
select 'COVID' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'disease' from dual union all
select 'COVID' from dual union all
select 'can' from dual union all
select 'be' from dual union all
select 'treated' from dual union all
select 'with' from dual union all
select 'antibodies' from dual union all
select 'COVID' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'serious' from dual union all
select 'disease' from dual union all
select 'there' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'vaccination' from dual union all
select 'available' from dual union all
select 'for' from dual union all
select 'COVID' from dual
)
select words, count(words) as counter
from x
group by words
order by 2 desc;
If you execute it, it gives you the output you want
SQL> with x ( words )
2 as
3 (
4 select 'COVID' from dual union all
5 select 'is' from dual union all
6 select 'a' from dual union all
7 select 'disease' from dual union all
8 select 'COVID' from dual union all
9 select 'can' from dual union all
10 select 'be' from dual union all
11 select 'treated' from dual union all
12 select 'with' from dual union all
13 select 'antibodies' from dual union all
14 select 'COVID' from dual union all
15 select 'is' from dual union all
16 select 'a' from dual union all
17 select 'serious' from dual union all
18 select 'disease' from dual union all
19 select 'there' from dual union all
20 select 'is' from dual union all
21 select 'a' from dual union all
22 select 'vaccination' from dual union all
23 select 'available' from dual union all
24 select 'for' from dual union all
25 select 'COVID' from dual
26 )
27 select words, count(words) as counter
28 from x
29 group by words
30* order by 2 desc
SQL> /
WORDS COUNTER
----------- ----------
COVID 4
is 3
a 3
disease 2
there 1
available 1
for 1
serious 1
antibodies 1
can 1
vaccination 1
WORDS COUNTER
----------- ----------
treated 1
be 1
with 1
14 rows selected.
Option 2 - Using PLSQL and a CURSOR
In the below example, I'm going to use dbms_output to show the results. Keep in mind that here there are dozens of ways to do the same, I am just using the simplest one I can think of.
SQL> create table t ( words varchar2(40) ) ;
Table created.
SQL> insert into t
2 with x ( words )
3 as
4 (
5 select 'COVID' from dual union all
6 select 'is' from dual union all
7 select 'a' from dual union all
8 select 'disease' from dual union all
9 select 'COVID' from dual union all
10 select 'can' from dual union all
11 select 'be' from dual union all
12 select 'treated' from dual union all
13 select 'with' from dual union all
14 select 'antibodies' from dual union all
15 select 'COVID' from dual union all
16 select 'is' from dual union all
17 select 'a' from dual union all
18 select 'serious' from dual union all
19 select 'disease' from dual union all
20 select 'there' from dual union all
21 select 'is' from dual union all
22 select 'a' from dual union all
23 select 'vaccination' from dual union all
24 select 'available' from dual union all
25 select 'for' from dual union all
26 select 'COVID' from dual
27* ) select words from x
SQL> /
22 rows created.
SQL> commit ;
Commit complete.
SQL> declare
2 begin
3 for i in ( select words, count(words) as counter from t group by words order by 2 desc )
4 loop
5 dbms_output.put_line( 'Words: '||i.words||' - Wcount is: '||i.counter||' ');
6 end loop;
7* end;
SQL> /
Words: COVID - Wcount is: 4
Words: is - Wcount is: 3
Words: a - Wcount is: 3
Words: disease - Wcount is: 2
Words: antibodies - Wcount is: 1
Words: for - Wcount is: 1
Words: be - Wcount is: 1
Words: vaccination - Wcount is: 1
Words: can - Wcount is: 1
Words: available - Wcount is: 1
Words: treated - Wcount is: 1
Words: there - Wcount is: 1
Words: with - Wcount is: 1
Words: serious - Wcount is: 1
PL/SQL procedure successfully completed.

Duplicated rows numbering

I need to number the rows so that the row number with the same ID is the same. For example:
Oracle database. Any ideas?
Use the DENSE_RANK analytic function:
SELECT DENSE_RANK() OVER (ORDER BY id) AS row_number,
id
FROM your_table
Which, for the sample data:
CREATE TABLE your_table ( id ) AS
SELECT 86325 FROM DUAL UNION ALL
SELECT 86325 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86352 FROM DUAL UNION ALL
SELECT 86353 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL;
Outputs:
ROW_NUMBER
ID
1
86325
1
86325
2
86326
2
86326
3
86352
4
86353
5
86354
5
86354
db<>fiddle here

Is there a way to accept comma separated input in PLSQL

I am trying to confirm if it's acceptable to provide a comma separated input through a prompt in PLSQL query. For example the input to be like order_no = 12345;67890;09876
Thanks in advance!
Here is my code:
select order_no
from CUSTOMER_ORDER_JOIN
where order_no like nvl('&Order_no', '%')
Generally speaking, no, you can't do that because Oracle considers string you enter as a whole. For example, this query returns nothing because there's no job whose name is Clerk,Manager (both values).
SQL> with emp (ename, job) as
2 (select 'Smith', 'Clerk' from dual union all
3 select 'Allen', 'Salesman' from dual union all
4 select 'Ward' , 'Salesman' from dual union all
5 select 'Jones', 'Manager' from dual
6 )
7 select ename, job
8 from emp
9 where job in ('&par_jobs');
Enter value for par_jobs: Clerk,Manager
no rows selected
SQL>
However, if you have Apex installed in your database, and that Apex version supports apex_string.split function, then you can do something which is close to what you're looking for:
SQL> with emp (ename, job) as
2 (select 'Smith', 'Clerk' from dual union all
3 select 'Allen', 'Salesman' from dual union all
4 select 'Ward' , 'Salesman' from dual union all
5 select 'Jones', 'Manager' from dual
6 )
7 select ename, job
8 from emp
9 where job in (select * from table(apex_string.split('&par_jobs', ',')));
Enter value for par_jobs: Clerk,Manager
ENAME JOB
----- --------
Smith Clerk
Jones Manager
SQL>
Otherwise, you'll have to type a little bit more to achieve that, e.g.
SQL> with emp (ename, job) as
2 (select 'Smith', 'Clerk' from dual union all
3 select 'Allen', 'Salesman' from dual union all
4 select 'Ward' , 'Salesman' from dual union all
5 select 'Jones', 'Manager' from dual
6 )
7 select ename, job
8 from emp
9 where job in (select regexp_substr('&&par_jobs', '[^,]+', 1, level)
10 from dual
11 connect by level <= regexp_count('&&par_jobs', ',') + 1
12 );
Enter value for par_jobs: Clerk,Manager
ENAME JOB
----- --------
Smith Clerk
Jones Manager
SQL>
Basically, YMMV.

Select default in case of no value returned

I am trying to get some default value in my resultset if query does not return anything. I am trying nvl for the same but it is not returning the expected default value. To simulate, Consider following query,
select nvl(null, '10') from dual where 1=0;
I want to get 10 in case of given condition is not true and query does not return any value. However above query not returning any row.
Your query returns zero rows. NVL() isn't going to change that (*).
The correct solution is for the program which executes the query to handle NO_DATA_FOUND exception rather than fiddling the query.
However, you need a workaround so here is one using two sub-queries, one for your actual query, one to for the default.
When your_query returns an empty set you get this:
SQL> with your_qry as
2 ( select col1 from t42 where 1=0 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
10
SQL>
And when it returns a row you get this:
SQL> with your_qry as
2 ( select col1 from t42 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
12
13
SQL>
The WITH clause is optional here, it just makes it easier to write the query without duplication. This would have the same outcome:
select col1
from t42
where col0 is null
union all
select 10
from dual
where not exists (select col1
from t42
where col0 is null)
;
(*) Okay, there are solutions which use NVL() or COALESCE() with aggregations to do this. They work with single column projections in a single row as this question poses, but break down when the real query has more than one row and/or more than one column. Aggregations change the results.
So this looks alright ...
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual where 1=0 union all
3 select 'X' as col0, 13 as col1 from dual where 1=0 )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Y 10
SQL>
... but this not so much:
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual union all
3 select 'X' as col0, 13 as col1 from dual )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Z 13
SQL>
May be something like this is what you need
You could change WHERE clause (in this case WHERE COL > 1) similarly in both places.
WITH T(COL) AS(
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL
)
SELECT COL FROM T WHERE COL > 1
UNION ALL
SELECT 10 AS COL FROM DUAL WHERE NOT EXISTS( SELECT 1 FROM T WHERE COL > 1)
You can use aggregation. An aggregation query always returns one row:
select coalesce(max(null), '10')
from dual
where 1 = 0;
I prefer coalesce() to nvl() because coalesce() is the ANSI standard function. But, nvl() would work here just as well.

Subselect in oracle

I'm struggling with a subselect in oracle. I want to include the latest price from another table.
Here is my current attempt:
SELECT tab1.*
(select price from
old_prices
where part_no=tab1.article_no
order by valid_from desc) as old_price,
FROM articles tab1
order by article_no
The sub select returns several rows which I think is the problem. But I do not know how to limit the number of rows in Oracle.
SQL> create table articles (article_no,name)
2 as
3 select 1, 'PEN' from dual union all
4 select 2, 'PAPER' from dual
5 /
Table created.
SQL> create table old_prices (part_no,valid_from,price)
2 as
3 select 1, date '2008-01-01', 10 from dual union all
4 select 1, date '2009-01-01', 11 from dual union all
5 select 1, date '2010-01-01', 12 from dual union all
6 select 1, date '2011-01-01', 13 from dual union all
7 select 2, date '2010-01-01', 89.95 from dual union all
8 select 2, date '2011-01-01', 94.95 from dual union all
9 select 2, date '2012-01-01', 99.95 from dual
10 /
Table created.
SQL> select a.article_no
2 , max(a.name) keep (dense_rank last order by p.valid_from) name
3 , max(p.price) keep (dense_rank last order by p.valid_from) price
4 from articles a
5 , old_prices p
6 where a.article_no = p.part_no
7 group by a.article_no
8 /
ARTICLE_NO NAME PRICE
---------- ----- ----------
1 PEN 13
2 PAPER 99.95
2 rows selected.
Regards,
Rob.
If it's the latest price you're after:
SELECT tab1.*, p.price old_price
FROM articles tab1
, old_prices p
where p.part_no = tab1.article_no
and valid_from = (
select MAX(valid_from)
from old_prices p2
where p2.part_no = p.part_no
)
order by article_no
I want to include the lastest price
I presume you mean latest.
OK, well that's a bit of a problem to start with, there are several ways of doing this:
SELECT o.price
FROM old_prices o
WHERE o.part_no=&part_no
AND o.ondate=(SELECT MAX(o2.ondate)
FROM old_prices o2
WHERE o2.part_no=&part_no);
Seems the most obvious choice but its rather innefficient.
You could try....
SELECT ilv.price
FROM (SELECT o.price
FROM old_price o
WHERE o.part_no=&part_no
ORDER BY ondate DESC) ilv
WHERE rownum=1;
Or....
SELECT TO_NUMBER(
SUBSTR(
MAX(TO_CHAR(o.ondate, 'YYYYMMDDHH24MISS') || price)
, 15)
) as latest_price
FROM old_price o
WHERE o.part_no=&part_no;
To limit rows use ROWNUM < 10. This is a pseudocolumn returning the row number of each line of your resultset.
EDIT:
You need to add another subselect query (hope this is the right place for your need)
SELECT tab1.*
select (
(select price from old_prices
where part_no=tab1.article_no order by valid_from desc
) as x
where rownum = 1
) as old_price
FROM articles tab1
order by article_no
SELECT tab1.*
(select
price
from (
SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
where rn =1
and tab1.article_no = P.part_no
) as old_price
FROM articles tab1
order by article_no
more efficient would be
SELECT
tab1.*
, P.price
FROM
articles tab1
, ( SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
WHERE
P.part_no(+) = tab1.article_no
P.rn(+) = 1
;
with old_prices as(
select level * 15 price ,
mod (level ,5) part_no , --this is just to create a grouping type partno
(sysdate - level ) valid_from
from dual
connect by level < 100)
,
articles as(
select level ,
mod(level , 5 ) article_no ,
(sysdate + level) someOtherDateField
From dual
connect by level < 5
)
SELECT tab1.* ,
old_price.*
from articles tab1
left join
(
select price,
part_no ,
valid_from ,
rank() over(partition by part_no order by valid_from desc) rk
from old_prices
) old_price
on tab1.article_no = old_price.part_no
and old_price.rk = 1
order by article_no ;
Here's another way!
LEVEL ARTICLE_NO SOMEOTHERDATEFIELD PRICE PART_NO VALID_FROM RK
---------------------- ---------------------- ------------------------- ---------------------- ---------------------- ------------------------- ----------------------
1 1 25/05/11 07:30:54 15 1 23/05/11 07:30:54 1
2 2 26/05/11 07:30:54 30 2 22/05/11 07:30:54 1
3 3 27/05/11 07:30:54 45 3 21/05/11 07:30:54 1
4 4 28/05/11 07:30:54 60 4 20/05/11 07:30:54 1

Resources