How to use GREATEST function with Over Partition by in Oracle - oracle

In the below code I want to select customer_name, location, gender and address along with customerid, aread_code.
select
customerid, aread_code, GREATEST(MAX(productid), MAX(itemid))
from
CUSTOMER C
inner join
ORDER O ON c.custid = o.custid
where
c.custtype = 'EXECUTIVE'
group
customerid, by aread_code;
I tried GREATEST function along with OVER PARTITION BY to display required columns. It's throwing an error.
Could you please help me to select the required columns.
Thank you.

DISCLAIMER:
When working with more than one table, qualify the columns with their table name. You haven't done so, so we don't know what of the two tables the aread_code resides in. In my answer here I assume it is the customer's area. If it isn't then you need a different answer.
ANSWER:
You group by customer_id and area code. This gives you one row per customer. And you want the maximum product/item ID from the orders table. (I suppose they are drawn from the same sequence, so you can use this ID somehow to go on from there.)
The easiest approach for this is to get the maximum ID in a subquery. Either directly in the select clause or in the from clause.
Here is how to do this in the SELECT clause:
select
c.*,
(
select greatest(max(productid), max(itemid))
from orders o
where o.custid = c.custid
) as max_id
from customer c
where c.custtype = 'EXECUTIVE';
Here is one way to do this in the FROM clause:
select
c.*,
agg.max_id
from customer c
outer apply
(
select greatest(max(productid), max(itemid)) as max_id
from orders o
where o.custid = c.custid
) agg
where c.custtype = 'EXECUTIVE';
And here is another way to do this in the FROM clause:
select
c.*,
agg.max_id
from customer c
left outer join
(
select
custid,
greatest(max(productid), max(itemid)) as max_id
from orders
group by custid
) agg on agg.custid = c.custid
where c.custtype = 'EXECUTIVE';
If you only want customers with at least one order, then I recommend the approach with the FROM clause. You'd have to turn the OUTER APPLY into a CROSS APPLY resp. the LEFT OUTER JOIN into an INNER JOIN for this.

There are several mistakes in your code. The main confusion is not using table alias prefix for columns. There is a group by mistake and a problem with your table name ORDER - if it is a name of a table. ORDER is a reserved word in Oracle and if it is the name of the table then you should use something like "YOUR_OWNER_NAME"."ORDER".... Here is the corected code with some sample data and result:
WITH
customers (CUSTID, PRODUCTID, AREAD_CODE, CUSTOMER_NAME, LOCATION, GENDER, ADDRESS, CUSTTYPE) AS
(
Select 1, 1, 63, 'Name 1', 'Location 1', 'M', 'Address 1', 'EXECUTIVE' From Dual Union All
Select 2, 1, 63, 'Name 1', 'Location 1', 'M', 'Address 1', 'EXECUTIVE' From Dual Union All
Select 3, 3, 63, 'Name 1', 'Location 1', 'M', 'Address 1', 'EXECUTIVE' From Dual Union All
Select 4, 7, 63, 'Name 1', 'Location 1', 'M', 'Address 1', 'EXECUTIVE' From Dual
),
orders (ORDER_ID, CUSTID, ITEMID, SOME_COLUMN) AS
(
Select 1, 1, 1, 'Some other data' From Dual Union All
Select 2, 2, 1, 'Some other data' From Dual Union All
Select 3, 3, 1, 'Some other data' From Dual Union All
Select 4, 3, 3, 'Some other data' From Dual Union All
Select 5, 4, 1, 'Some other data' From Dual Union All
Select 6, 4, 8, 'Some other data' From Dual
)
select
c.custid, c.aread_code, GREATEST(MAX(c.productid), MAX(o.itemid)) "MAX_ID"
from
CUSTOMERS C
inner join
ORDERS O ON c.custid = o.custid
where
c.custtype = 'EXECUTIVE'
group by
c.custid, c.aread_code
CUSTID AREAD_CODE MAX_ID
---------- ---------- ----------
1 63 1
4 63 8
3 63 3
2 63 1
There are different options to get the rest of the columns depending on your actual data you could use some or all of them.
Option 1 - select and group by as suggested in Beefstu's comment below
select Distinct
c.custid, c.customer_name, c.location, c.address, c.gender, c. custtype, c.aread_code,
GREATEST(MAX(c.productid), MAX(o.itemid)) "MAX_ID"
from
CUSTOMERS C
inner join
ORDERS O ON c.custid = o.custid
where
c.custtype = 'EXECUTIVE'
group by
c.custid, c.customer_name, c.location, c.address, c.gender, c. custtype, c.aread_code
order by c.custid
CUSTID CUSTOMER_NAME LOCATION ADDRESS GENDER CUSTTYPE AREAD_CODE MAX_ID
---------- ------------- ---------- --------- ------ --------- ---------- ----------
1 Name 1 Location 1 Address 1 M EXECUTIVE 63 1
2 Name 1 Location 1 Address 1 M EXECUTIVE 63 1
3 Name 1 Location 1 Address 1 M EXECUTIVE 63 3
4 Name 1 Location 1 Address 1 M EXECUTIVE 63 8
Option 2. - using analytic functions MAX() OVER() with Distinct keyword (could be performance costly with big datasets) - result is the same as above
select Distinct
c.custid, c.customer_name, c.location, c.address, c.gender, c. custtype, c.aread_code,
GREATEST(MAX(c.productid) OVER(Partition By c.custid), MAX(o.itemid) OVER(Partition By c.custid)) "MAX_ID"
from
CUSTOMERS C
inner join
ORDERS O ON c.custid = o.custid
where
c.custtype = 'EXECUTIVE'
order by c.custid
Option 3 - using left join to a subquery - see the solution offered by Thorsten Kettner

Related

I need 2 count columns in the same query in ORACLE

I'm trying to get the unique number of invoices a company has received and sent out using 2 count() functions. In invoices table there are two columns that are references to the same company id (one is id of a company that is sending an invoice and the other one is id of a company that is receiving an invoice)
This is the code I tried using:
SELECT K.ID,K.NAME,K.CITY, COUNT(*) AS NUM_OF_INVOICES_SENT, COUNT(*) AS NUM_OF_INVOICES_RECEIVED
FROM COMPANY K LEFT JOIN INVOICE F ON F.COMP_SNEDING = K.ID
GROUP BY K.NAME,K.ID,K.CITY
This is for a school project so I am in no means well versed in sql/oracle
actual data invoices:
actual data company:
desired outcome with given actual data:
Here's one option; it doesn't use count, but sum with case expression.
Sample data:
SQL> with
2 invoice (id, amount, comp_sending, comp_receiving) as
3 (select 1, 2000 , 1, 2 from dual union all
4 select 2, 28250, 3, 2 from dual union all
5 select 3, 8700 , 4, 1 from dual union all
6 select 4, 20200, 5, 3 from dual union all
7 select 5, 21500, 3, 4 from dual
8 ),
9 company (id, name, city, state) as
10 (select 1, 'Microsoft', 'Redmond' , 'Washington' from dual union all
11 select 2, 'Ubisoft' , 'Paris' , 'France' from dual union all
12 select 4, 'Starbucks', 'Seattle' , 'Washington' from dual union all
13 select 5, 'Apple' , 'Cupertino', 'California' from dual union all
14 select 3, 'Nvidia' , 'Cupertino', 'California' from dual
15 )
Query begins here:
16 select c.id, c.name,
17 sum(case when c.id = i.comp_sending then 1 else 0 end) cnt_sent,
18 sum(case when c.id = i.comp_receiving then 1 else 0 end) cnt_received
19 from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
20 group by c.id, c.name
21 order by c.id;
ID NAME CNT_SENT CNT_RECEIVED
---------- --------- ---------- ------------
1 Microsoft 1 1
2 Ubisoft 0 2
3 Nvidia 2 1
4 Starbucks 1 1
5 Apple 1 0
SQL>
You can use COUNT if you replace the 0 in the CASE expressions with NULL. So #Littlefoot's query becomes
select c.id, c.name,
COUNT(case when c.id = i.comp_sending then 1 else NULL end) cnt_sent,
COUNT(case when c.id = i.comp_receiving then 1 else NULL end) cnt_received
from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
group by c.id, c.name
order by c.id;
This works because COUNT counts only those rows which have a non-NULL value in the expression which is being counted.
db<>fiddle here

oracle- JOIN 2 tables with 2 ID's in common

table "team1" :
id country
1 India
2 Pakistan
3 srilanka
4 England
table "team2" :
id name name2
1 2 4
2 1 3
i have to combine two tables
another table when retrieve the data that time in place of 2 , 4 Pakistan,England
It is about the self join of team1 table (lines #15 and 16):
SQL> with
2 team1 (id, country) as
3 (select 1, 'India' from dual union all
4 select 2, 'Pakistan' from dual union all
5 select 3, 'Sri Lanka' from dual union all
6 select 4, 'England' from dual
7 ),
8 team2 (id, name, name2) as
9 (select 1, 2, 4 from dual union all
10 select 2, 1, 3 from dual
11 )
12 select b.id,
13 t1.country,
14 t2.country
15 from team2 b join team1 t1 on t1.id = b.name
16 join team1 t2 on t2.id = b.name2
17 order by b.id;
ID COUNTRY COUNTRY
---------- --------- ---------
1 Pakistan England
2 India Sri Lanka
SQL>
Just showing another way of writing the same query with aggregate functions and grouping.
with
team1 (id, country) as
(select 1, 'India' from dual union all
select 2, 'Pakistan' from dual union all
select 3, 'Sri Lanka' from dual union all
select 4, 'England' from dual
),
team2 (id, name, name2) as
(select 1, 2, 4 from dual union all
select 2, 1, 3 from dual
)
SELECT
T2.ID,
MAX(CASE
WHEN T2.NAME = T1.ID THEN T1.COUNTRY
END) AS TEAM1,
MAX(CASE
WHEN T2.NAME2 = T1.ID THEN T1.COUNTRY
END) AS TEAM2
FROM
TEAM2 T2
JOIN TEAM1 T1 ON T1.ID IN (
T2.NAME,
T2.NAME2
)
GROUP BY
T2.ID
ORDER BY
T2.ID;
Output:
ID TEAM1 TEAM2
---------- --------- ---------
1 Pakistan England
2 India Sri Lanka
Cheers!!

Count(*) in Group By - Returns 1, instead of '0'

I am using the below oracle query to get the count of rows.
SELECT T.ID,T.NAME,COUNT(*) AS NO_OF_STUDENTS FROM STUDENT S RIGHT JOIN
TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID,T.NAME ORDER BY T.ID
Actual Result Should be:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 0 STUDENT
The Result what i am getting is:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 1 STUDENT
Since TEACHER 3 is not having any student, the result should be 0 Student. But i am getting the result as 1 Student.
You need to count a specific column (not use *, which includes nulls) in the table being outer joined to - since that is the one that might not have matching data. So:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
The only difference is COUNT(S.ID) instead of COUNT(*).
Simple demo with made-up data provided via CTEs:
with teacher (id, name) as (
select 1, 'Teacher 1' from dual
union all select 2, 'Teacher 2' from dual
union all select 3, 'Teacher 3' from dual
),
student (id, teacher_id) as (
select level, 1 from dual connect by level <= 10
union all
select level + 10, 2 from dual connect by level <= 5
)
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID;
ID NAME NO_OF_STUDENTS
---------- --------- --------------
1 Teacher 1 10
2 Teacher 2 5
3 Teacher 3 0
You could also do this as a left join, which I find more intuitive:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM TEACHER T
LEFT JOIN STUDENT S ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
which gets the same result.

Oracle Shuffle columns in rows

Based in the solution if this issue - Shuffle a column between rows - I need to apply to this, a new condition: the names must guarantee the gender: male, female or unknown.
So, my table have a column named gender. I need to shuffle the columns whit the same gender.
I've tried this, but in some cases I cannot guarantee the gender
merge into original_table o
using (
with
helper ( id, gender, rn, rand_rn ) as (
select id,
gender,
row_number() over (order by id),
row_number() over (order by dbms_random.value())
from original_table
)
select ot.name, ot.gender, h2.id
from original_table ot inner join helper h1 on (ot.id = h1.id and ot.gender = h1.gender)
inner join helper h2 on h1.rand_rn = h2.rn
) p
on (o.id = p.id)
when matched then update set o.name = p.name
;
And in cases that gender is not available (is null value), for example company's name, this merge not update anything.
I can split this query in 3 different merge statement. One for each gender that I have. But I'm looking for a better and simply statement. Because I need to apply the same solution in a different context and with different conditions.
Thanks.
EDIT: Sample data
ID GENDER NAME
3721 M MARK
3722 M JUSTIN
3723 F RUTH
3724 F MARY
3725 F ANNE
4639 CAMPANY SA
4640 M JOHN
4641 M LUCAS
4642 COMPANY HOLDER SA
One possible solution:
ID GENDER NAME
3721 M LUCAS
3722 M JOHN
3723 F MARY
3724 F ANNE
3725 F RUTH
4639 CAMPANY HOLDER SA
4640 M MARK
4641 M JUSTIN
4642 COMPANY SA
Include gender in the PARTITION BY clause in the analytic functions and in the JOIN clause. If you don't have a primary key to match on then you could use the ROWID pseudo-column.
Oracle Setup:
CREATE TABLE original_table ( id, gender, name, company ) AS
SELECT 1, 'F', 'Alice', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 2, 'M', 'Bobby', 'ACME' FROM DUAL UNION ALL
SELECT 3, 'F', 'Carol', 'XYZ' FROM DUAL UNION ALL
SELECT 4, 'M', 'David', NULL FROM DUAL UNION ALL
SELECT 5, 'M', 'Errol', 'ACME' FROM DUAL UNION ALL
SELECT 6, 'F', 'Fiona', 'XYZ' FROM DUAL;
Update:
MERGE INTO original_table dst
USING (
WITH rnd ( rid, rn, rnd_rn, gender, name ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY id ),
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY DBMS_RANDOM.VALUE ),
gender,
name
FROM original_table
)
SELECT o.rid, r.name
FROM rnd o INNER JOIN rnd r
ON ( ( o.gender = r.gender OR ( o.gender IS NULL AND r.gender IS NULL ) )
AND o.rn = r.rnd_rn )
)
ON ( dst.ROWID = src.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Output:
SELECT * FROM original_table;
ID G NAME COMPANY
-- - ----- -------
1 F Fiona
2 M David ACME
3 F Alice XYZ
4 M Bobby
5 M Errol ACME
6 F Carol XYZ

coalesce with subquery in a where clause in Oracle

Hy,
I have the next sql query in Oracle:
Imagine I have a table "items" with "id" and "name" fields and other table "prices_items" which have three fields named "id, itemId, category". The category may have three values: "1,2,3". So the query I need to do is get the price of an item from the table "prices_items" but the item can have until three prices because of the category field. So, in priotiry order I need to get the price of an item which has category 1, if the item doesnt have this category I have to find the price for category 2 and so on.
from items
left join prices_items on prices_items.itemId = items.itemId
where prices_items.id = coalesce(select id
from prices_items
where itemId= items.itemId and category=1,
select id
from prices_items
where itemId= items.itemId and category=2,
select id
from prices_items
where itemId= items.itemId and category=3)
The query I am using is like this but I dont know how its working because coalesce is being executed on each join?. How is this being executed?
Thanks
The coalesce() is going to keep the first prices_items.id found in order of the categories listed. Instead of individual subqueries you could write it this way and it will probably give a better plan.
select ...
from items inner join prices_items on prices_items.itemId = items.itemId
where prices_items.category = (
select min(pi2.category) from prices_items pi2
where pi2.itemId = items.itemId
);
If the priority of categories doesn't happen to follow an ascending sequence you could handle it with a case expression:
select ...
from items inner join prices_items on prices_items.itemId = items.itemId
where
case prices_items.category
when 2 then 1
when 3 then 2
when 1 then 3
end = (
select
min(case pi2.category
when 2 then 1
when 3 then 2
when 1 then 3
end)
from prices_items pi2
where pi2.itemId = items.itemId
);
As far as how your current query is actually running it may or may not be materializing all the subquery results. From an end results perspective all you really need to know is that only the first non-null value from the coalesce() arguments is the one kept. The reality is that it is probably more efficient to re-write the query so you don't need them.
There are other ways to write this. The one that's most common these days seems to be the row_number() approach:
with data as (
select *,
row_number() over (partition by pi.itemId order by pi.category) as rn
from items inner join prices_items pi on pi.itemId = items.itemId
)
select ...
from data
where rn = 1;
Here's another Oracle-specific solution:
select *
from
items inner join
(
select itemId, min(price) keep (dense_rank first order by category) as price
from prices_items
group by itemId
) pi on pi.itemId = items.itemId;
Oracle Setup:
CREATE TABLE items (
itemid NUMBER PRIMARY KEY,
name VARCHAR2(20)
);
CREATE TABLE prices_items (
itemId NUMBER REFERENCES items ( itemid ),
category INT,
price NUMBER,
CHECK ( category IN ( 1, 2, 3 ) ),
PRIMARY KEY ( itemid, category )
);
INSERT INTO items
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'B' FROM DUAL UNION ALL
SELECT 3, 'C' FROM DUAL;
INSERT INTO prices_items
SELECT 1, 1, 32.5 FROM DUAL UNION ALL
SELECT 1, 2, 23.9 FROM DUAL UNION ALL
SELECT 1, 3, 19.99 FROM DUAL UNION ALL
SELECT 2, 1, 42.42 FROM DUAL UNION ALL
SELECT 2, 3, 99.99 FROM DUAL UNION ALL
SELECT 3, 2, 0.02 FROM DUAL UNION ALL
SELECT 3, 3, 10 FROM DUAL;
Query:
SELECT i.itemid,
name,
category,
price
FROM items i
INNER JOIN
( SELECT itemid,
MIN( category ) AS category,
MAX( price ) KEEP ( DENSE_RANK FIRST ORDER BY category ) AS price
FROM prices_items
GROUP BY itemid
) p
ON ( i.itemid = p.itemid );
Output:
ID NAME CATEGORY PRICE
-- ---- -------- -----
1 A 1 32.50
2 B 1 42.42
3 C 2 0.02

Resources