dense rank duplicate values oracle - sql

So I am really happy being able to rank results based on effective dates, but currently I'm having an issue where one data element repeats (POD) while another changes based on EFFDT (DEPT).
I only want to rank unique values for Pod, and later Dept. However Pod is based on Dept, which changes more frequently. The below code gives me:
EENBR PodRank POD DeptRank DeptNbr DeptEffdt
100 1 73 1 12420 4/11/2005
100 2 73 2 12560 5/22/2005
100 3 73 3 12501 6/24/2007
200 1 12 1 50768 3/14/2005
200 2 13 2 10949 9/9/2012
300 1 73 1 12450 3/21/2005
300 2 73 2 12471 12/25/2005
300 3 73 3 12581 12/21/2008
300 4 73 4 12585 6/6/2010
300 5 73 5 12432 5/19/2013
SELECT DISTINCT
AL4.FULL_NAME,
AL4.EMPLOYEE_NUMBER,
dense_rank() over (partition by AL4.EMPLOYEE_NUMBER
order by AL3.EFFECTIVE_START_DATE) as POD_RANKING,
AL7.POD_NBR as POD,
row_number() over (partition by AL4.EMPLOYEE_NUMBER
order by AL3.EFFECTIVE_START_DATE) as DEPT_RANKING,
AL3.RECORDVALUE AS DEPT_NUMBER,
AL3.EFFECTIVE_START_DATE AS "DEPT EFFECTIVE DATE"
FROM T1 AL3,
T2 AL4,
T3 AL7
WHERE AL4.PERSON_ID = AL3.PERSON_ID
AND AL4.EMPLOYEE_NUMBER = AL3.EMPLOYEE_NUMBER
AND AL3.RECORDTYPE = 'DEPARTMENT_NUMBER'
AND AL7.DEPT_NBR = AL3.RECORDVALUE
Order By AL4.Employee_Number;
Is there a function that only ranks unique values?

The function you are looking for is the analytic function dense_rank():
dense_rank() over (partition by eenbr order by pod) as ranking
This is the simplest way to get what you want. You can just add it in the select clause of your query.

There's no function for this, but you can get the result when you use nested window functions:
SELECT dt.*,
SUM(flag) OVER (PARTITION BY EMPLOYEE_NUMBER
ORDER BY "DEPT EFFECTIVE DATE") AS POD_RANKING
FROM
(
SELECT
AL4.FULL_NAME,
AL4.EMPLOYEE_NUMBER,
AL7.POD_NBR AS POD,
ROW_NUMBER() OVER (PARTITION BY AL4.EMPLOYEE_NUMBER
ORDER BY AL3.EFFECTIVE_START_DATE) AS DEPT_RANKING,
AL3.RECORDVALUE AS DEPT_NUMBER,
AL3.EFFECTIVE_START_DATE AS "DEPT EFFECTIVE DATE",
CASE WHEN ROW_NUMBER()
OVER (PARTITION BY AL4.EMPLOYEE_NUMBER,AL7.POD_NBR
ORDER BY AL3.EFFECTIVE_START_DATE) = 1 THEN 1 ELSE 0 END AS flag
FROM T1 AL3,
T2 AL4,
T3 AL7
WHERE AL4.PERSON_ID = AL3.PERSON_ID
AND AL4.EMPLOYEE_NUMBER = AL3.EMPLOYEE_NUMBER
AND AL3.RECORDTYPE = 'DEPARTMENT_NUMBER'
AND AL7.DEPT_NBR = AL3.RECORDVALUE
) dt
ORDER BY AL4.Employee_Number;
Edit:
Ok, I noticed this is a overly complex version of a simple DENSE_RANK with different order, shortly before Gordon posted his answer :-)
dense_rank() over (partition by AL4.EMPLOYEE_NUMBER order by AL7.POD_NBR)

Related

Oracle SQL fetch previous rows based

I am stuck on how to fetch the previous row after joining multiple tables - Below is the data set after joining with multiple tables
CARRCD FLTNBR IND DEPDATETIME
---- -------- ----- --------
AB 123 0 2020-10-29T14:00:00
AB 124 0 2020-10-29T10:00:00
AB 119 0 2020-10-29T09:00:00
AB 100 0 2020-10-29T08:00:00
AB 105 1 2020-10-29T07:00:00 ---------> Match
AB 99 1 2020-10-29T06:00:00
AB 135 1 2020-10-29T04:00:00
AB 178 1 2020-10-29T02:00:00
Now once I get the above dataset after joining multiple tables, I have to find the first record whose IND matches with 1 and then return the previous record. So in the above data set
the first record which matches IND=1 is "AB 105" and then I have to return the previous record
AB 100 0 2020-10-29T08:00:00
Please help
If you want the first time this happens, then:
select t.*
from (select t.*,
lead(ind) over (order by depdatetime desc) as next_ind
from t
) t
where t.ind = 0 and t.next_ind = 1
order by depdatetime
fetch first 1 row only;
However, I suspect that you want this per carcd. If so, you need partition by and some more logic:
select t.*
from (select t.*,
row_number() over (partition by carcd order by depdatetime) as seqnum
from (select t.*,
lead(ind) over (partition by carcd order by depdatetime desc) as next_ind
from t
) t
where t.ind = 0 and t.next_ind = 1
) t
where seqnum = 1;
Note that the above is quite general. In particular, it works:
When ind might have more than two values.
When ind can return to 0 after 1.
When the first row is 1 (which is rejected as a candidate).
If the problem is more constrained, there are likely other solutions.
Based on additional info you provided in comment, you can locate requested row as the one which has ind=0 and is followed by row with ind=1. This is done using lead analytical function.
Assuming t is your relation, this blindly typed SQL should work:
select CARRCD, FLTNBR, IND, DEPDATETIME
from (
select t.*, lead(IND) over (order by depdatetime desc) next_ind
from t
) x
where x.ind = 0 and x.next_ind = 1

SQL Server Adding Order with CTE

I want to achieve something easy in an while loop but it's too slow to run so I'm sure I can do this with an CTE table but I don't know how..
I have two table with a join one to many and i'm trying to put an order in my detail here's my data:
for each IDRV you have many IDRVOBJET. I want to put an order number 1 to n for each IDRVOBJET in IDRV. I don't know if i'm clear but here's what I should have as result:
Thanks for taking the time to help me!
EDIT
Here's the formated text:
IDRV IDRVOBJET ORDER
12700 76357 1
12700 76358 2
12701 76363 1
12701 76364 2
12701 76365 3
12702 76359 1
12702 76360 2
12703 76120 1
12703 76121 2
12703 76122 3
12705 19375 1
12705 19376 2
12706 19410 1
12707 19408 1
12707 19409 2
12709 22473 1
12709 22474 2
12711 40352 1
12711 40353 1
Just use a window function.
SELECT
IDRV,
IDRVOBJECT,
ROW_NUMBER() OVER (PARTITION BY IDRV ORDER BY IDRVOBJECT) as ORDRE
FROM YourTable
You can use row_number and partition by
select *, Ordr = Row_Number() over (partition by IDRV order by IDRVOBJET) from your table
SELECT IDRV,IDRVOBJECT,Row_Number()Over(Partition by IDRV Order by IDRVOBJECT) AS ORDRE
FROM <yourtable>

SQL count issues

Sorry if this is a basic question.
I have a table
store-ProdCode
13p I10x
13p I20x
13p I30x
14a K38z
17a K38y
my data set has nearly 100,000 records.
What I'm trying to do is, for every store find the top 10 prodCode.
I am unsure of how to do this but what I tried was:
select s_code as store, prod_code,count (prod_code)
from top10_secondary
where prod_code is not null
group by store,prod_code
order by count(prod_code) desc limit 10
this is giving me something completely different and i'm unsure on how I go about achieving my final result.
The expected output should be: for every store(s_code) display the top 10 prodcode (top 10 calculated by the count)
so:
store--prodcode--result
1a abc 5
1a abd 4
1a xxx 7
--- this will be done until top 10 prodcodes for 1a are done--
2a dgf 1
2a ldk 6
--process completes until end of data is reached and top 10 prodcodes are displayed for each store
All help is appreciated. What is the best way to do this?
Thanks
One method uses row_number(), something like this:
select s.*
from (select s_code as store, prod_code, count(prod_code),
row_number() over (partition by s_code order by count(prod_code) desc) as seqnum
from top10_secondary
where prod_code is not null
group by s_code, prod_code
) s
where seqnum <= 10;
You can use window functions directly in an aggregation query. The subquery is needed only to reference the sequence number for filtering.

Longest item in each group

I am trying to find which activity took the longest (1) by facility (giving me 6 different activities) and (2) by facility and department (giving me 11 different activities).
This code only gives my one response when
SELECT NOC.FCILTY_ID, NAC.ACTIVITY_ID, NAC.ELAPSED_SECONDS
FROM NAC, NOC
WHERE NAC.OBS_ID=NOC.OBS_ID
AND NAC.ELAPSED_SECONDS IN (SELECT MAX(NAC.ELAPSED_SECONDS) FROM NAC, NOC
GROUP BY NOC.FCILTY_ID)
ORDER BY NOC.FCILTY_ID;
An example of some of the data and the code to retrieve some of the data is given below.
SELECT NAC.OBS_ID, NOC.FCILTY_ID, NOC.DEPT_NO, NAC.ACTIVITY_ID, NAC.ACTIVE_SECONDS, NAC.CAT
FROM NAC, NOC
WHERE NAC.OBS_ID = NOC.OBS_ID;
OBS_ID FCILTY_ID DEPT_NO ACTIVITY_ID ACTIVE_SECONDS CAT
1 A a 132 73.9999584 Motion
2 A a 133 92.000016 Operations
3 A a 134 198.0000288 Operations
4 A a 135 54.9999936 Error/Defect
5 A a 136 79.0000128 Error/Defect
6 A a 137 57.9999744 Operations
Use a CTE to add a ROW_NUMBER for each desired grouping,rnf for facility and rnfd for facility and department
WITH CTE AS
(SELECT NAC.OBS_ID, NOC.FCILTY_ID, NOC.DEPT_NO, NAC.ACTIVITY_ID, NAC.ACTIVE_SECONDS, NAC.CAT,
ROW_NUMBER() OVER(PARTITION BY NOC.FCILTY_ID ORDER BY ACTIVE_SECONDS DESC) as rnf,
ROW_NUMBER() OVER(PARTITION BY NOC.FCILTY_ID,NOC.DEPT_NO ORDER BY ACTIVE_SECONDS DESC) as rnfd
FROM NAC, NOC
WHERE NAC.OBS_ID = NOC.OBS_ID)
SELECT NAC.OBS_ID, NOC.FCILTY_ID, NOC.DEPT_NO, NAC.ACTIVITY_ID, NAC.ACTIVE_SECONDS, NAC.CAT FROM CTE
WHERE rnf=1 OR rnfd =1
EDIT
For 2 separate queries
..WHERE rnf=1
..WHERE rnfd =1
You need to join to a subquery. Here is one way.
with maxInterval as
(select cat theCat, max(active_seconds) longestTime
from etc
group by cat
)
select whatever
from yourTables join maxInterval on cat = theCat
and active_seconds = longestTime

In SQL, I need to generate a ranking (1st, 2nd, 3rd) column, getting stuck on "ties"

I have a query that calculates points based on multiple criteria, and then orders the result set based on those points.
SELECT * FROM (
SELECT
dbo.afunctionthatcalculates(Something, Something) AS Points1
,dbo.anotherone(Something, Something) AS Points2
,dbo.anotherone(Something, Something) AS Points3
,[TotalPoints] = dbo.function(something) + dbo.function(something)
) AS MyData
ORDER BY MyData.TotalPoints
So my first stab at adding placement, rankings.. was this:
SELECT ROW_NUMBER() OVER(MyData.TotalPoints) AS Ranking, * FROM (
SELECT same as above
) AS MyData
ORDER BY MyData.TotalPoints
This adds the Rankings column, but doesn't work when the points are tied.
Rank | TotalPoints
--------------------
1 100
2 90
3 90
4 80
Should be:
Rank | TotalPoints
--------------------
1 100
2 90
2 90
3 80
Not really sure about how to resolve this.
Thank you for your help.
You should use the DENSE_RANK() function which takes the ties into account, as described here: http://msdn.microsoft.com/en-us/library/ms173825.aspx
DENSE_RANK() instead of ROW_NUMBER()