Rownum for a column with lag - oracle

I have this query
SELECT CASE WHEN LAG(emp_id) OVER( ORDER BY NULL ) = emp_id THEN '-'
ELSE emp_id END "Employee ID",
row_number() over (partition by emp_id order by emp_id) as "S/N",
family_mem_id "MemID",
CASE WHEN LAG(emp_id) OVER(ORDER BY NULL ) = emp_id THEN 0
ELSE (SUM(amount_paid) OVER(PARTITION BY emp_id)) END "Total amount"
FROM Employee
ORDER BY emp_id;
And it shows me result like this: Resultset
I want to add row number (first column SN) for Employee ID for in between rows I want to set it as null, for e.g. For Employee ID -> S/N 2-> F904 (SN should be null). How can I do that?

You can use the outer query to create the SN column as follows:
SELECT CASE WHEN "S/N" = 1 THEN '<1>' END AS SN, T.* FROM -- added this
(SELECT
CASE WHEN LAG(emp_id) OVER( ORDER BY NULL ) = emp_id THEN '-'
ELSE emp_id END "Employee ID",
row_number() over (partition by emp_id order by emp_id) as "S/N",
family_mem_id "MemID",
CASE WHEN LAG(emp_id) OVER(ORDER BY NULL ) = emp_id THEN 0
ELSE (SUM(amount_paid) OVER(PARTITION BY emp_id)) END "Total amount"
FROM Employee ) T
ORDER BY "Employee ID", "S/N"; -- Added this
Note that I have also changed the ORDER BY clause (which is moved to the outer query).
Cheers!!

Use dense_rank() to enumerate rows:
select case lag(emp_id) over( order by emp_id ) when emp_id then null
else '<'||dense_rank() over (order by emp_id)||'>'
end sn,
nullif(emp_id, lag(emp_id) over( order by emp_id )) empid,
row_number() over (partition by emp_id order by emp_id) rn,
family_mem_id memid,
case lag(emp_id) over(order by emp_id) when emp_id then null
else (sum(amount_paid) over(partition by emp_id))
end total
FROM Employee order by emp_id;
dbfiddle and sample output:
SN EMPID RN MEMID TOTAL
---- ----- ------ ----- ----------
<1> 101 1 F901 200
2 F904
<2> 102 1 F901 135
2 F901
<3> 103 1 F901 185
2 F901
3 F901

Related

Oracle sort values into column

I have a table like this:
time length name
00:01:00 2 a
00:11:22 2 a
01:01:00 45 a
00:23:00 3 b
and I want to retrieve data from the table in the form:
a b
time length time length
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 2
so it is a simple task of rearranging data, atm I am doing this in a bash script, but I wonder if there is an easy way to do it in Oracle?
You can use analytical function ROW_NUMBER and full outer join as follows:
WITH CTE1 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'a'),
CTE2 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'b')
SELECT A.TIME, A.LENGTH, B.TIME, B.LENGTH
FROM CTE1 A FULL OUTER JOIN CTE2 B
ON A.RN = B.RN
Note: You need to use proper order by to order the records as per your requirement. I have used LENGTH, TIME
You can use a multi-column pivot, by adding an extra column that links the related A and B values; presumably by time order, something like:
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table;
TIME_COL LENGTH_COL N RNK
-------- ---------- - ----------
00:01:00 2 a 1
00:11:22 2 a 2
01:01:00 45 a 3
00:23:00 3 b 1
and then pivot based on that:
select *
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
pivot (
max(time_col) as time_col, max(length_col) as length_col
for name_col in ('a' as a, 'b' as b)
);
RNK A_TIME_C A_LENGTH_COL B_TIME_C B_LENGTH_COL
---------- -------- ------------ -------- ------------
1 00:01:00 2 00:23:00 3
2 00:11:22 2
3 01:01:00 45
I've left the rnk value in the output; if you don't want that you can list the columns in the select list:
select a_time_col, a_length_col, b_time_col, b_length_col
from ...
Or you could do the same thing with conditional aggregation (which is what pivot uses under the hood anyway):
select
max(case when name_col = 'a' then time_col end) as time_col_a,
max(case when name_col = 'a' then length_col end) as length_col_a,
max(case when name_col = 'b' then time_col end) as time_col_b,
max(case when name_col = 'b' then length_col end) as length_col_b
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
group by rnk
order by rnk;
TIME_COL LENGTH_COL_A TIME_COL LENGTH_COL_B
-------- ------------ -------- ------------
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 45
db<>fiddle

Update the top row of each group - Oracle

The need is to update only the top row of each group of a table from the data of other table.
I need to update table A with details from table B
Table A
---------
ID Name Date PCNO
1 abc 1/1/12 123
2 def 1/1/12 234
3 fgh 1/2/12 222
4 asd 1/2/12 234
TABLE B
-----------
ID Name Date PCNO
1 adsf 1/1/12 4343
2 sdf 1/2/12 9347
For each top record of table A grouped by "Date" and ordered by PCNO desc, I would like to update the values from table B.
Do i use rank for this purpose.?
You can use rank or dense-rank (or even row-number) to get identify the 'top' row, though you may need t consider what to do if ties are possible in your real data:
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by date_col order by pcno desc) as rnk
from table_a a;
ID NAME DATE_COL PCNO RNK
---------- ---- ---------- ---------- ----------
2 def 2012-01-01 234 1
1 abc 2012-01-01 123 2
4 asd 2012-01-02 234 1
3 fgh 2012-01-02 222 2
And you can join to table B to get the new values for the top-ranked:
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by a.date_col order by a.pcno desc) as rnk,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col;
ID NAME DATE_COL PCNO RNK NEW_ NEW_PCNO
---------- ---- ---------- ---------- ---------- ---- ----------
2 def 2012-01-01 234 1 adsf 4343
1 abc 2012-01-01 123 2 abc 123
4 asd 2012-01-02 234 1 sdf 9347
3 fgh 2012-01-02 222 2 fgh 222
and you can then use that in a merge statement:
merge into table_a target
using (
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by a.date_col order by a.pcno desc) as rnk,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col
) source
on (source.id = target.id)
when matched then update
set target.name = source.new_name, target.pcno = source.new_pcno
where source.rnk = 1;
or maybe
merge into table_a target
using (
select a.id, a.name, a.date_col, a.pcno,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col
) source
on (source.id = target.id)
when matched then update
set target.name = source.new_name, target.pcno = source.new_pcno
where target.name != source.new_name or target.pcno != source.new_pcno;
either of which reports 2 rows merged, and then:
select * from table_a;
ID NAME DATE_COL PCNO
---------- ---- ---------- ----------
1 abc 2012-01-01 123
2 adsf 2012-01-01 4343
3 fgh 2012-01-02 222
4 sdf 2012-01-02 9347
You may need to adjust it if there isn't always going to be a match for a date, though the inner join ought to take care of that.
db<>fiddle demo

Issue with HIVE with row_number() over() syntax

I have HIVE table ( details below):
hive> select * from abcd ;
OK
a 1 1
b 2 2
a 3 3
Time taken: 0.261 seconds, Fetched: 3 row(s)
hive> desc abcd;
OK
val001 string
val002 int
val003 int
Time taken: 0.084 seconds, Fetched: 3 row(s)
I am writing following query but receiving below error :
select max(rnk) rnk, max(val) val, sum(cnt) cnt from (select val, count(*) cnt, row_number() over (order by case val when null then 0 else count(*) end desc, val) rnk from (select VAL001 val from abcd ) group by val) group by case when rnk <= 100 or val is null then rnk else 100 + 1 end;
FAILED: ParseException line 3:55 missing ) at 'by' near 'by'
line 3:58 missing EOF at 'val' near 'by'
I am looking for following result from above query :
RNK VAL CNT
--- ------------------------------ ---
1 a 2
2 b 1
I was able to achieve the same from Oracle database having similar kind of table. Only difference was instead of order by case I used order by decode in Oracle DB but since decode is not supported in HIVe I can not do the same.
Please find ORacle DB SQL query which is working :
SQL> select max(rnk) rnk, max(val) val, sum(cnt) cnt from
(select val, count(*) cnt, row_number() over (order by
decode(val,null,0,count(*)) desc, val) rnk from (select VAL001 val from
table_name ) group by val)
group by case when rnk <= 100 or val is null then rnk else 100 + 1 end;
RNK VAL CNT
--- ------------------------------ ---
1 a 2
2 b 1
Can anyone please help me fixing HIVE query. Let me know if you need any more details.
This is your query. I suspect there is a simpler way to get what you want:
select max(rnk) as rnk, max(val) as val, sum(cnt) as cnt
from (select val, count(*) as cnt,
row_number() over (order by case val when null then 0 else count(*) end desc, val) as rnk
from (select VAL001 val from abcd )
group by val
)
group by case when rnk <= 100 or val is null then rnk else 100 + 1 end;
I think you just need table aliases for the subqueries in the from clause:
select max(rnk) as rnk, max(val) as val, sum(cnt) as cnt
from (select val, count(*) as cnt,
row_number() over (order by case val when null then 0 else count(*) end desc, val) as rnk
from (select VAL001 val from abcd
) x
group by val
) x
group by case when rnk <= 100 or val is null then rnk else 100 + 1 end;
This is not technically simpler solution, but possible easier to read:
The first subquery performs the count and ranking,
the second subquery the categorisation in the top 1 - top 100 and the special categories for other (top) and unknown.
The final query makes the grouping.
with cnt as (
select VAL001 val,
count(*) as cnt,
row_number() over (order by decode(VAL001,null,0,count(*)) desc, VAL001) as rnk
from abcd
group by VAL001),
ctg as (
select
val, cnt, rnk,
case when val is NULL then 'unknown'
when rnk <= 100 then 'top '||rnk
else 'other' end as category_code
from cnt)
select
max(rnk) as rnk, max(val) as val, sum(cnt) as cnt
from ctg
group by category_code
order by 1

How to get only the one employee name from each department if the max salary is same from more than one employee

I am using below query:
SELECT rownum, job_id, employee_id, first_name, last_name, phone_number, salary
FROM employees OUTER
WHERE salary =
(
SELECT MAX(salary)
FROM employees
WHERE job_id = OUTER.job_id
GROUP BY job_id
)
AND ROWNUM < 6;
And getting below result:
1 AD_PRES 100 Steven King 515.123.4567 24000
2 AD_VP 101 Neena Kochhar 515.123.4568 17000
3 AD_VP 102 Lex De Haan 515.123.4569 17000
4 IT_PROG 103 Alexander Hunold 590.423.4567 9000
5 FI_MGR 108 Nancy Greenberg 515.124.4569 12008
But the problem is I want only one name for each JOB_ID. And that should be decided by alphabetical preference in FIRST_NAME.
One option would be to use a subquery which contains the job_id for the first name you want to retain. I wrapped your original query in a common table expression to make it more readable.
WITH t AS
(
SELECT rownum, job_id, employee_id, first_name, last_name,
phone_number, salary
FROM employees OUTER
WHERE salary =
(
SELECT MAX(salary)
FROM employees
WHERE job_id = OUTER.job_id
GROUP BY job_id
)
AND ROWNUM < 6;
)
SELECT t1.rownum, t1.job_id, t1.employee_id, t1.first_name, t1.last_name,
t1.phone_number, t1.salary
FROM t t1
INNER JOIN
(
SELECT job_id, MAX(first_name) AS max_name
FROM t
GROUP BY job_id
) t2
ON t1.job_id = t2.job_id AND t1.first_name = t2.max_name
Use analytic functions:
select * from (
SELECT job_id, employee_id, first_name, last_name, phone_number, salary,
RANK() over (
job_id
order by
salary desc,
first_name,
employee_id -- adding employe_id breaks ties in the ordering
) as rnk
FROM employees
) where rnk = 1;
This will probably also perform better then the subselect.
All this is written without a database at hand, so it might/will contain typos

Query to exclude row based on another row's filter

I'm using Oracle 10g.
Question: How can I write query to return just ID only if ALL the codes for that ID end in 6? I don't want ID=1 because not all its codes end in 6.
TABLE_A
ID Code
===============
1 100
1 106
2 206
3 316
3 326
4 444
Desired Result:
ID
==
2
3
You simply want each ID where the count of rows for that id is the same as the count of rows where the third digit is six.
SELECT ID
FROM TABLE_A
GROUP BY ID
HAVING COUNT(*) = COUNT(CASE WHEN SUBSTR(code,3,1) = '6' THEN 1 END)
Try this:
SELECT DISTINCT b.id
FROM (
SELECT id,
COUNT(1) cnt
FROM table_a
GROUP BY id
) a,
(
SELECT id,
COUNT(1) cnt
FROM table_a
WHERE CODE LIKE '%6'
GROUP BY id
)b
WHERE a.id = b.id
AND a.cnt = b.cnt
Alternative using ANALYTIC functions:
SELECT DISTINCT id
FROM
(
SELECT id,
COUNT(1) OVER(PARTITION BY id) cnt,
SUM(CASE WHEN code LIKE '%6' THEN 1 ELSE 0 END) OVER(PARTITION BY id) sm
FROM table_a
)
WHERE cnt = sm

Resources