Oracle query where one column with multiple values must be different - oracle

I'm having trouble trying to come up with a query that retrieves COLUMN_A where:
COLUMN_C is having count > 1
And COLUMN_B's values are different.
Table contents
COLUMN_A COLUMN_B COLUMN_C
-------------------------------- ---------------- ---------
000111222 AAAAAAAAA 100
000111222 BBBBBBBBB 100
000111222 CCCCCCCCC 300
000111222 CCCCCCCCC 300
000888999 DDDDDDDDD 300
000888999 DDDDDDDDD 300
777666555 EEEEEEEEE 100
777666555 FFFFFFFFF 100
234567890 GGGGGGGGG 100
234567890 GGGGGGGGG 100
444333111 HHHHHHHHH 100
Expected result
COLUMN_A COLUMN_B COLUMN_C
-------------------------------- ---------------- ---------
000111222 AAAAAAAAA 100
000111222 BBBBBBBBB 100
777666555 EEEEEEEEE 100
777666555 FFFFFFFFF 100
This is where I'm stuck at:
select COLUMN_A, count(*) from SOME_TABLE where COLUMN_C = '100' group by COLUMN having count(*) > '1';
But this query retrieves:
COLUMN_A COLUMN_B COLUMN_C
-------------------------------- ---------------- ---------
000111222 AAAAAAAAA 100
000111222 BBBBBBBBB 100
777666555 EEEEEEEEE 100
777666555 FFFFFFFFF 100
234567890 GGGGGGGGG 100
234567890 GGGGGGGGG 100
I strongly suspect the query lacks a distinct or a count somewhere but, for the life of me, nothing comes to mind.
Any info would be greatly appreciated.

We can try to use two count window functions in the subquery for your grouping logic.
SELECT t1.COLUMN_A ,t1.COLUMN_B ,t1.COLUMN_C
FROM (
select t1.*,
count(*) OVER(PARTITION BY COLUMN_A,COLUMN_B) cnt1,
COUNT(*) OVER(PARTITION BY COLUMN_A,COLUMN_C) cnt2
from SOME_TABLE t1
) t1
WHERE cnt1 = 1 AND cnt2 > 1
sqlfiddle

You can use:
SELECT column_a, column_b, column_c
FROM (
SELECT t.*,
COUNT(column_b) OVER (PARTITION BY column_a, column_c) AS cnt_all,
COUNT(DISTINCT column_b) OVER (PARTITION BY column_a, column_c)
AS cnt_dst
FROM table_name t
)
WHERE cnt_all = cnt_dst
AND cnt_dst > 1
Which, for the sample data:
CREATE TABLE table_name (COLUMN_A, COLUMN_B, COLUMN_C) AS
SELECT '000111222', 'AAAAAAAAA', 100 FROM DUAL UNION ALL
SELECT '000111222', 'BBBBBBBBB', 100 FROM DUAL UNION ALL
SELECT '000111222', 'CCCCCCCCC', 300 FROM DUAL UNION ALL
SELECT '000111222', 'CCCCCCCCC', 300 FROM DUAL UNION ALL
SELECT '000888999', 'DDDDDDDDD', 300 FROM DUAL UNION ALL
SELECT '000888999', 'DDDDDDDDD', 300 FROM DUAL UNION ALL
SELECT '777666555', 'EEEEEEEEE', 100 FROM DUAL UNION ALL
SELECT '777666555', 'FFFFFFFFF', 100 FROM DUAL UNION ALL
SELECT '234567890', 'GGGGGGGGG', 100 FROM DUAL UNION ALL
SELECT '234567890', 'GGGGGGGGG', 100 FROM DUAL UNION ALL
SELECT '444333111', 'HHHHHHHHH', 100 FROM DUAL;
Outputs:
COLUMN_A
COLUMN_B
COLUMN_C
000111222
AAAAAAAAA
100
000111222
BBBBBBBBB
100
777666555
EEEEEEEEE
100
777666555
FFFFFFFFF
100
db<>fiddle here

Related

percentage analysis in quartiles

I am trying to get the maximum, minimum of the difference between t3 and t3 in each quartile. But when I change the percentage # to 50,75.. I see the output listed below. I am not sure what I am doing wrong here.
SELECT MIN(SPREAD),MAX(SPREAD) FROM
(SELECT COUNT(*)*1.0 AS TOTALLINES FROM pa_fcs)A,
(SELECT SPREAD,ROWNUM *1.0 AS LINENUM FROM (
select BOARD_RATE_T3,BOARD_RATE_T1 ,(BOARD_RATE_T3-BOARD_RATE_T1) AS SPREAD
from pa_fcs ))B
WHERE LINENUM/TOTALLINES <= .25;
OUTPUT: .25
Min(Spread) Max(Spread)
7.47 5160.24
Output :.50
Min(Spread) Max(Spread)
7.47 5160.24
Output: .75
Min(Spread) Max(Spread)
0 5160.24
Use NTILE analytic function to calculate the quartiles.
Then is MIN, MAX trivial exersize
create table tab as
select rownum spread from dual connect by level <= 100;
with ntile as (
select
spread,
NTILE(4) OVER (ORDER BY spread DESC) qtile
from tab)
select qtile, min(spread), max(spread)
from ntile
group by qtile order by 1
;
QTILE MIN(SPREAD) MAX(SPREAD)
---------- ----------- -----------
1 76 100
2 51 75
3 26 50
4 1 25
For your table it would be something like
with spr as (
select (BOARD_RATE_T3-BOARD_RATE_T1) AS SPREAD
from pa_fcs),
ntile as (
select
spread,
NTILE(4) OVER (ORDER BY spread DESC) qtile
from spr)
select qtile, min(spread), max(spread)
from ntile
group by qtile order by 1

Grouping and aggregation based on a specific condition

My result set from a query looks like this
trackingnumber type price
------------------------------------------
12799467 AVRM 674.0536
12799467 AVRM 860.7415
12799467 PRICESTD 200.00
12799468 PRICESTD 590.85
12799469 PRICESTD 800
12799470 PRICESTD 640
12799471 PRICESTD 160
12799472 PRICESTD 2080
12799473 PRICESTD 354.3779
I want to group this by the trackingnumber and in cases where the count of grouped result set is greater than 1 return the SUM of all the price which has type as AVRM else return the individual price as it is. If the count
is more that zero and the none of them has type AVRM then it's total price would be null
The expected result would be this
trackingnumber Total Price
-----------------------------------------
12799467 1534.7951 --sum of price excluding 200
12799468 590.85
12799469 800
12799470 640
12799471 160
12799472 2080
12799473 354.3779
I couldn't think of a way to get this done except for grouping by trackingnumber and checking for type by using case statement in the select part but that I believe would not work since we do not group by type
I'm not sure if this can be achieved using a single query.
Yes, it can be done in a single query.
with sample_data ( tracking_Number, "TYPE", price ) as
(
SELECT 12799467,'AVRM',674.0536 FROM DUAL UNION ALL
SELECT 12799467,'AVRM',860.7415 FROM DUAL UNION ALL
SELECT 12799467,'PRICESTD',200.00 FROM DUAL UNION ALL
SELECT 12799468,'PRICESTD',590.85 FROM DUAL UNION ALL
SELECT 12799469,'PRICESTD',800 FROM DUAL UNION ALL
SELECT 12799470,'PRICESTD',640 FROM DUAL UNION ALL
SELECT 12799471,'PRICESTD',160 FROM DUAL UNION ALL
SELECT 12799472,'PRICESTD',2080 FROM DUAL UNION ALL
SELECT 12799473,'PRICESTD',354.3779 FROM DUAL )
SELECT tracking_number,
case when count(*) > 1 THEN
sum(decode("TYPE",'AVRM',price,null)) ELSE
sum(price) END price
from sample_data
group by tracking_number
order by tracking_Number;
For example next solution. I add condition to exlude rows with type not equla 'AVRM' if rows with 'AVRM' exests
with s (trackingnumber ,type ,price)
as (
select 12799467,'AVRM',674.0536 from dual union all
select 12799467 ,'AVRM', 860.7415 from dual union all
select 12799467 ,'PRICESTD', 200.00 from dual union all
select 12799468 ,'PRICESTD', 590.85 from dual union all
select 12799469 ,'PRICESTD', 800 from dual union all
select 12799470 ,'PRICESTD', 640 from dual union all
select 12799471 ,'PRICESTD', 160 from dual union all
select 12799472 ,'PRICESTD', 2080 from dual union all
select 12799473 ,'PRICESTD', 354.3779 from dual )
select trackingnumber,
sum(price)
from (select s.*,rownum as rn from s
where not exists (select null
from s subs
where s.trackingnumber = subs.trackingnumber
and s.type != 'AVRM'
and subs.type = 'AVRM')
)
group by trackingnumber,
case when type = 'AVRM' then 0 else rn end;

Count with Case Select in Oracle

I have the following query:
SELECT t.range,
Count(*)
FROM (
SELECT CASE
WHEN creditscore BETWEEN 300
AND 499
THEN '[300, 499]'
WHEN creditscore BETWEEN 500
AND 699
THEN '[500, 699]'
WHEN creditscore BETWEEN 700
AND 850
THEN '[700, 850]'
END AS range
FROM customer
) T
GROUP BY t.range;
Which gives me the following :
CS range COUNT(*)
---------- ----------
[700, 850] 7
[500, 699] 13
I want the following output:
CS range COUNT(*)
---------- ----------
[300, 499] 0
[500, 699] 13
[700, 850] 7
Can anyone help me out?
UPDATE
I executed the code the Justin provided. It gives me the following:
DESCRIPTIO COUNT(*)
---------- ----------
[300, 499] 1
[700, 850] 7
[500, 699] 13
SQL> SELECT COUNT(*) FROM customer where creditscore BETWEEN 300 AND 499;
COUNT(*)
----------
0
One option would be to do something like
WITH ranges AS (
SELECT '[300, 499]' description, 300 min_val, 499 max_val FROM dual
UNION ALL
SELECT '[500, 699]' description, 500 min_val, 699 max_val FROM dual
UNION ALL
SELECT '[700, 850]' description, 700 min_val, 850 max_val FROM dual
)
SELECT r.description, count(creditscore)
FROM ranges r
LEFT OUTER JOIN customer c
ON (c.creditscore BETWEEN r.min_val AND r.max_val)
GROUP BY r.description
Normally, you'd want to set up a permanent table with your ranges in case someone wants to add or modify them in the future. But you can hard-code the ranges in your query like I do here.
You can use unpivot in this case.
Your answer-
Select * From
(
Select Sum(Col1) "[300, 499]", Sum(Col2) "[500, 699]", Sum(Col3) "[700, 850]"
From (
Select Case
When Creditscore Between 300
And 499
Then 1 Else 0 End As Col1,
Case When Creditscore Between 500
And 699
Then 1 Else 0 End Col2,
Case When Creditscore Between 700
And 850
Then 1 Else 0 End Col3
From Customer
)
)
Unpivot
(
"Count"
For
"Description" In ("[300, 499]", "[500, 699]", "[700, 850]")
);
Result -
Description Count
[300, 499] 0
[500, 699] 4
[700, 850] 3

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

Combining column values from different tables

I stuck. I have 2 tables - look at image no.1 Table columns And i would like to build query, that will give me the result - it is showed on image no 2. the result of query.
I have 2 queries and I would like to mix them up, to obtain the list from image no.2. Please help me, how to build a query.
Query no1: SELECT department_name, department_id FROM DEPARTMENTS WHERE department_id between 90 AND 110;
Query no 2: SELECT last_name, department_id from employees WHERE department_id between 90 AND 110;
Query:
with departments (department_id, department_name) as (
select 90, 'Executive' from dual union all
select 100, 'Finance' from dual union all
select 110, 'Accounting' from dual
),
employees (employee_id, last_name, department_id) as (
select 1003, 'King' , 90 from dual union all
select 1005, 'De Hann' , 90 from dual union all
select 1009, 'Gietz' , 110 from dual union all
select 1013, 'Popp' , 100 from dual union all
select 1014, 'Chen' , 100 from dual union all
select 1015, 'Higgins' , 110 from dual union all
select 1029, 'Greenberg', 100 from dual union all
select 1040, 'Kochar' , 90 from dual union all
select 1043, 'Faviet' , 100 from dual union all
select 1045, 'Urman' , 100 from dual union all
select 1049, 'Sciarra' , 100 from dual
)
-- end input data; begin actual query --
select c_name, department_id from
( select department_name as c_name, department_id, 0 as categ from departments
union all
select ' ' || last_name as c_name, department_id, 1 from employees
order by department_id, categ, c_name
);
Result:
C_NAME DEPARTMENT_ID
------------- -------------
Executive 90
De Hann 90
King 90
Kochar 90
Finance 100
Chen 100
Faviet 100
Greenberg 100
Popp 100
Sciarra 100
Urman 100
Accounting 110
Gietz 110
Higgins 110
You don't need the "with ..." part; just use the query that begins at the SELECT statement after the two factored subqueries (after the "input data"). I even ordered by last name within each department for you; if that is not needed, just delete "c_name" from the ORDER BY clause.
I called the first column c_name; you may call it whatever you want, but calling it department_name when it also holds employee last names didn't make much sense to me. To call it whatever you want, change the SELECT statement from SELECT c_name, department_id to SELECT c_name AS whatever, department_id...
SELECT c.last_name,
d.department_id,
d.department_name
FROM employee c
JOIN deptartment d ON d.department_id=c.department_id
WHERE d.department_id BETWEEN 90 AN 110
OUTPUT from my sample table
+-------+----+------------+
| KING | 10 | ACCOUNTING |
| BLAKE | 30 | SALES |
| CLARK | 10 | ACCOUNTING |
| JONES | 20 | RESEARCH |
| SCOTT | 20 | RESEARCH |
+-------+----+------------+

Resources