Analytic Function: ROW_NUMBER( ) - sql

I have a table "Invoice"
id integer Primary key
customer_id Integer
total Number (*,2)
The query is to display all customer_id, total and running serial number to each customer with alias name as 'SNO'. And the records should be displayed in ascending order based on the customer_id and then by SNO.
Hints:
Analytic Function: ROW_NUMBER( )
Analytic Clause: query_partition_clause and order_by_clause.
I wrote the below query:
Select customer_id,
total,
ROW_NUMBER( ) OVER (PARTITION BY customer_id ORDER BY customer_id ASC) AS "SNO"
from invoice;
But the result is failing. What is that I am missing. Also what is meant "the records should be displayed in ascending order based on the customer_id and then by SNO".
The result I am getting is as below:
CUSTOMER_ID TOTAL SNO
1 70000 1
2 250000 1
2 560000 2
3 200000 1
3 45000 2
4 475000 1
5 50000 1
5 10000 2
6 600000 1
6 90000 2
Expected result is :
CUSTOMER_ID TOTAL SNO
1 70000 1
2 250000 1
2 560000 2
3 45000 1
3 200000 2
4 475000 1
5 10000 1
5 50000 2
6 600000 1
6 90000 2
TOTAL Column data is not matching.

You're close, you probably need to order the row_number by id (assuming it's ascending based on time)
Select customer_id,
total,
ROW_NUMBER( ) OVER (PARTITION BY customer_id ORDER BY id ASC) AS "SNO"
from invoice
order by customer_id, "SNO" -- should be the default anyway (but there's no guarantee)

I have not found any order by clause in your query, another issue in which order you want to generate SNO ? by using id or total that will impact on your ordering
with cte as
(
select 1 cid, 70000 total from dual
union all
select 2, 250000 from dual
union all
select 2, 560000 from dual
union all
select 3, 200000 from dual
union all
select 3, 45000 from dual
union all
select 4, 475000 from dual
union all
select 5, 50000 from dual
union all
select 5, 10000 from dual
union all
select 6, 600000 from dual
union all
select 6, 90000 from dual
)Select cid,total,ROW_NUMBER( ) OVER (PARTITION BY cid ORDER BY total ) AS "SNO" from cte order by cid,SNO

Related

How to group data based on max value of a column in oracle [duplicate]

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 4 months ago.
I have a table by the name of purchase_details which has the following data:
purchase_id
product_id
purchase_price
sale_price
3
1
15000
16000
4
1
13000
14000
3
4
500
700
2
4
400
500
I want to select data based on max purchase_id for every product like the following:
purchase_id
product_id
purchase_price
sale_price
4
1
13000
14000
3
4
500
700
One option is to use analytic function (such as row_number or rank) to "sort" data; then extract rows that rank as the highest:
Sample data:
SQL> with purchase_Details (purchase_id, product_id, purchase_price, sale_price) as
2 (select 3, 1, 15000, 16000 from dual union all
3 select 4, 1, 13000, 14000 from dual union all
4 select 3, 4, 500, 700 from dual union all
5 select 2, 4, 400, 500 from dual
6 ),
Query begins here:
7 temp as
8 (select p.*,
9 row_number() over (partition by product_id order by purchase_id desc) rn
10 from purchase_details p
11 )
12 select purchase_id, product_id, purchase_price, sale_price
13 from temp
14 where rn = 1;
PURCHASE_ID PRODUCT_ID PURCHASE_PRICE SALE_PRICE
----------- ---------- -------------- ----------
4 1 13000 14000
3 4 500 700
SQL>

GROUP BY ID and select MAX

Good Evening,
I am working on a table like this in Oracle:
ID
BALANCE
SEQ
1
102
13
1
119
15
2
50
4
3
20
11
3
15
10
3
45
9
4
90
5
5
67
20
5
12
19
6
20
1
I want to select, for each ID, the BALANCE having MAX(SEQ).
So final result would be:
ID
BALANCE
SEQ
1
119
15
2
50
4
3
20
11
4
90
5
5
67
20
6
20
1
How can I do that?
I've tried several Group by queries but with no success.
Thanks for any help
One method is aggregation using keep:
select id,
max(balance) keep (dense_rank first order by seq desc) as balance,
max(seq)
from t
group by id;
You may use normal rank()
SELECT ID, BALANCE, SEQ FROM (
select
ID, BALANCE, SEQ, RANK() OVER (PARTITION BY ID ORDER BY SEQ DESC) ranks
from t
) WHERE ranks = 1
sample demo
SELECT ID, BALANCE, SEQ FROM (
SELECT ID, BALANCE, SEQ, RANK() OVER (PARTITION BY ID ORDER BY SEQ DESC) ranks
FROM (
SELECT 1 ID, 102 BALANCE, 13 SEQ FROM dual UNION all
SELECT 1, 119, 15 FROM dual UNION all
SELECT 2, 50, 4 FROM dual UNION all
SELECT 3, 20, 11 FROM dual UNION all
SELECT 3, 15, 10 FROM dual UNION all
SELECT 3, 45, 9 FROM dual UNION all
SELECT 4, 90, 5 FROM dual UNION all
SELECT 5, 67, 20 FROM dual UNION all
SELECT 5, 12, 19 FROM dual UNION all
SELECT 6, 20, 1 FROM dual
)
) WHERE ranks = 1
you can add it in your Big query as below
SELECT ID, BALANCE, SEQ FROM (
select
ID, BALANCE, SEQ, RANK() OVER (PARTITION BY ID ORDER BY SEQ DESC) ranks
from (**YOUR BIG QUERY HERE**)
) WHERE ranks = 1

oracle grouping everytime the sum amount is below 15

I've following data:
SELECT 1 note, 1000 amt FROM dual union all
SELECT 2 note, 2000 amt FROM dual union all
SELECT 3 note, 8000 amt FROM dual union all
SELECT 4 note, 3000 amt FROM dual union all
SELECT 5 note, 1500 amt FROM dual union all
SELECT 6 note, 1600 amt FROM dual union all
SELECT 7 note, 20000 amt FROM dual union all
SELECT 8 note, 20000 amt FROM dual union all
SELECT 9 note, 2100 amt FROM dual union all
SELECT 10 note, 4500 amt FROM dual union all
SELECT 11 note, 1000 amt FROM dual union all
SELECT 12 note, 16000 amt FROM dual
and I need sum the amount, but for every sum <= 15000, they will be grouped together. If the amount is > 15000, they will be on their own group like this:
NOTE
AMT
group
1
1000
1
11
1000
1
5
1500
1
6
1600
1
2
2000
1
9
2100
1
4
3000
1
10
4500
2
3
8000
2
12
16000
3
7
20000
4
8
20000
5
I need the solution in oracle sql, is it possible? I'm using oracle 11g
One method is a recursive subquery:
with tt(note, amt, seqnum) as (
select t.note, t.amt, row_number() over (order by amt) as seqnum
from t
),
cte(note, amt, seqnum, grp, running_amt) as (
select note, amt, seqnum, 1, amt
from tt
where seqnum = 1
union all
select tt.note, tt.amt, tt.seqnum,
(case when tt.amt + cte.running_amt > 15000 then cte.grp + 1 else cte.grp end),
(case when tt.amt + cte.running_amt > 15000 then tt.amt else tt.amt + cte.running_amt end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte;
Here is a db<>fiddle.
Note: This is ordering by amt -- as in your sample data. You can as easily order by note (which also makes sense) just by adjusting seqnum in tt.
You can use the SQL for Pattern Matching:
WITH t AS (
SELECT 1 note, 1000 amt FROM DUAL UNION ALL
SELECT 2 note, 2000 amt FROM DUAL UNION ALL
SELECT 3 note, 8000 amt FROM DUAL UNION ALL
SELECT 4 note, 3000 amt FROM DUAL UNION ALL
SELECT 5 note, 1500 amt FROM DUAL UNION ALL
SELECT 6 note, 1600 amt FROM DUAL UNION ALL
SELECT 7 note, 20000 amt FROM DUAL UNION ALL
SELECT 8 note, 20000 amt FROM DUAL UNION ALL
SELECT 9 note, 2100 amt FROM DUAL UNION ALL
SELECT 10 note, 4500 amt FROM DUAL UNION ALL
SELECT 11 note, 1000 amt FROM DUAL UNION ALL
SELECT 12 note, 16000 amt FROM DUAL)
SELECT note, amt, amt_group, sum_amt
FROM t
MATCH_RECOGNIZE (
ORDER BY amt
MEASURES
MATCH_NUMBER() AS amt_group,
NVL(SUM(amt), amt) AS sum_amt
ALL ROWS PER MATCH
PATTERN (s*)
DEFINE
s AS SUM(amt) <= 15000
) mr
ORDER BY amt
NOTE
AMT
AMT_GROUP
SUM_AMT
1
1000
1
1000
11
1000
1
2000
5
1500
1
3500
6
1600
1
5100
2
2000
1
7100
9
2100
1
9200
4
3000
1
12200
10
4500
2
4500
3
8000
2
12500
12
16000
3
16000
7
20000
4
20000
8
20000
5
20000

How to use distinct and sum both together in oracle?

For example my table contains the following data:
ID price
-------------
1 10
1 10
1 20
2 20
2 20
3 30
3 30
4 5
4 5
4 15
So given the example above,
ID price
-------------
1 30
2 20
3 30
4 20
-----------
ID 100
How to write query in oracle? first sum(distinct price) group by id then sum(all price).
I would be very careful with a data structure like this. First, check that all ids have exactly one price:
select id
from table t
group by id
having count(distinct price) > 1;
I think the safest method is to extract a particular price for each id (say the maximum) and then do the aggregation:
select sum(price)
from (select id, max(price) as price
from table t
group by id
) t;
Then, go fix your data so you don't have a repeated additive dimension. There should be a table with one row per id and price (or perhaps with duplicates but controlled by effective and end dates).
The data is messed up; you should not assume that the price is the same on all rows for a given id. You need to check that every time you use the fields, until you fix the data.
first sum(distinct price) group by id then sum(all price)
Looking at your desired output, it seems you also need the final sum(similar to ROLLUP), however, ROLLUP won't directly work in your case.
If you want to format your output in exactly the way you have posted your desired output, i.e. with a header for the last row of total sum, then you could set the PAGESIZE in SQL*Plus.
Using UNION ALL
For example,
SQL> set pagesize 7
SQL> WITH DATA AS(
2 SELECT ID, SUM(DISTINCT price) AS price
3 FROM t
4 GROUP BY id
5 )
6 SELECT to_char(ID) id, price FROM DATA
7 UNION ALL
8 SELECT 'ID' id, sum(price) FROM DATA
9 ORDER BY ID
10 /
ID PRICE
--- ----------
1 30
2 20
3 30
4 20
ID PRICE
--- ----------
ID 100
SQL>
So, you have an additional row in the end with the total SUM of price.
Using ROLLUP
Alternatively, you could use ROLLUP to get the total sum as follows:
SQL> set pagesize 7
SQL> WITH DATA AS
2 ( SELECT ID, SUM(DISTINCT price) AS price FROM t GROUP BY id
3 )
4 SELECT ID, SUM(price) price
5 FROM DATA
6 GROUP BY ROLLUP(id);
ID PRICE
---------- ----------
1 30
2 20
3 30
4 20
ID PRICE
---------- ----------
100
SQL>
First do the DISTINCT and then a ROLLUP
SELECT ID, SUM(price) -- sum of the distinct prices
FROM
(
SELECT DISTINCT ID, price -- distinct prices per ID
FROM tab
) dt
GROUP BY ROLLUP(ID) -- two levels of aggregation, per ID and total sum
SELECT ID,SUM(price) as price
FROM
(SELECT ID,price
FROM TableName
GROUP BY ID,price) as T
GROUP BY ID
Explanation:
The inner query will select different prices for each ids.
i.e.,
ID price
-------------
1 10
1 20
2 20
3 30
4 5
4 15
Then the outer query will select SUM of those prices for each id.
Final Result :
ID price
----------
1 30
2 20
3 30
4 20
Result in SQL Fiddle.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE MYTABLE ( ID, price ) AS
SELECT 1, 10 FROM DUAL
UNION ALL SELECT 1, 10 FROM DUAL
UNION ALL SELECT 1, 20 FROM DUAL
UNION ALL SELECT 2, 20 FROM DUAL
UNION ALL SELECT 2, 20 FROM DUAL
UNION ALL SELECT 3, 30 FROM DUAL
UNION ALL SELECT 3, 30 FROM DUAL
UNION ALL SELECT 4, 5 FROM DUAL
UNION ALL SELECT 4, 5 FROM DUAL
UNION ALL SELECT 4, 15 FROM DUAL;
Query 1:
SELECT COALESCE( TO_CHAR(ID), 'ID' ) AS ID,
SUM( PRICE ) AS PRICE
FROM ( SELECT DISTINCT ID, PRICE FROM MYTABLE )
GROUP BY ROLLUP ( ID )
ORDER BY ID
Results:
| ID | PRICE |
|----|-------|
| 1 | 30 |
| 2 | 20 |
| 3 | 30 |
| 4 | 20 |
| ID | 100 |

Oracle SQL - order table

I am new to Oracle SQL and have a table below.
TABLE NAME : ORDERS
CNUM AMT SNUM
1001 1000 2001
1002 2000 2002
1001 1500 2001
1001 500 2001
need to get only those data where cnum (customer number) is serviced by more than 3 snum(sales person) from this above table
thank you
Asit
Something like that?
select cnum, count(*), sum(amount)
from orders
group by cnum
having count(*) > 3
Not sure what you need - aggregate result or every single row. If you need every row then try this one:
select * from (
select a.*, count(*) over(partition by cnum) cnt
from orders a
)
where cnt > 3
You could use the analytic function ROW_NUMBER.
For example,
SQL> WITH DATA AS(
2 SELECT t.*, row_number() OVER(PARTITION BY cnum ORDER BY snum) rn FROM t
3 )
4 SELECT cnum, amt, snum FROM DATA
5 WHERE rn >=3;
CNUM AMT SNUM
---------- ---------- ----------
1001 1500 2001
SQL>
Based on your sample data, the above gives the result including 3 snum for cnum, if you want more than 3, then replace >= with >.
You can group and count distinct SNUMs:
select CNUM
from ORDERS
group by CNUM
having count(distinct SNUM) > 3
WITH tab
AS (SELECT 1001 CNUM, 1000 AMT, 2001 SNUM FROM DUAL
UNION ALL
SELECT 1002, 2000, 2002 FROM DUAL
UNION ALL
SELECT 1001, 1500, 2001 FROM DUAL
UNION ALL
SELECT 1001, 500, 2001 FROM DUAL)
SELECT cnum
FROM tab
GROUP BY cnum
HAVING COUNT (*) >= 3;
If you only need the cnum and the snum you could do the following witch is partially taken from Rusty's answer:
WITH tab
AS (SELECT 1001 CNUM, 1000 AMT, 2001 SNUM FROM DUAL
UNION ALL
SELECT 1002, 2000, 2002 FROM DUAL
UNION ALL
SELECT 1001, 1500, 2001 FROM DUAL
UNION ALL
SELECT 1001, 500, 2001 FROM DUAL)
SELECT DISTINCT cnum, snum
FROM (SELECT cnum, snum, COUNT (*) OVER (PARTITION BY cnum) RANK FROM tab)
WHERE RANK >= 3;