Conditional Analytic Function - sql

Work:
,MAX(CASE WHEN MAX(HIST)
AND workid IS NOT NULL
AND ROLE = 'red'
THEN 'ASSIGNED'
ELSE 'UNASSIGNED'
END)
OVER (PARTITION BY id) AS ASSIGNED
Criteria:
Partition By ID
Look at last entry from each ID, utilizing the PKHistid column
If Role = Red and Workid IS NOT NULL from the last entry for each ID
Then Assigned
Else Unassigned
Table:
PKHistid ID Role Entry_Date Workid
1 101 Red 1/1/17 201
2 101 Yellow 1/2/17 201
3 102 Yellow 5/1/17 (Null)
4 102 Red 6/1/17 202
5 103 Red 7/1/17 202
6 103 Red 7/5/17 202
Expected Results: (New Column Assigned_Status)
PKHistid ID Role Entry_Date Workid *Assigned_Status
1 101 Red 1/1/17 201 Unassigned
2 101 Yellow 1/2/17 201 Unassigned
3 102 Yellow 5/1/17 (Null) Assigned
4 102 Red 6/1/17 202 Assigned
5 103 Red 7/1/17 202 Assigned
6 103 Red 7/5/17 202 Assigned

Is this "instead of" your earlier question (also posted today), or is it "in addition to" it? If it is "in addition to", note that you can do both things in the same query.
Here you need a case expression to create the additional column. In the case expression, the condition uses an analytic function. I prefer the analytic version of the LAST function (which, unfortunately, many developers don't seem to know and use). Please read the Oracle documentation for it if it is not familiar to you.
Note that analytic functions can't be nested; but there is absolutely no prohibition against using analytic functions in case expressions. I often see solutions where the analytic function is called in a subquery, and then further processing (such as case expressions using the result from the analytic functions) is done in an outer query. Unnecessary layering!
with
inputs ( pkhistid, id, role, entry_date, workid) as (
select 1, 101, 'Red' , to_date('1/1/17', 'mm/dd/rr'), 201 from dual union all
select 2, 101, 'Yellow', to_date('1/2/17', 'mm/dd/rr'), 201 from dual union all
select 3, 102, 'Yellow', to_date('5/1/17', 'mm/dd/rr'), null from dual union all
select 4, 102, 'Red' , to_date('6/1/17', 'mm/dd/rr'), 202 from dual union all
select 5, 103, 'Red' , to_date('7/1/17', 'mm/dd/rr'), 202 from dual union all
select 6, 103, 'Red' , to_date('7/5/17', 'mm/dd/rr'), 202 from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select pkhistid, id, role, entry_date, workid,
case when max(role) keep (dense_rank last order by pkhistid)
over (partition by id) = 'Red'
and
max(workid) keep (dense_rank last order by pkhistid)
over (partition by id) is not null
then 'Assigned'
else 'Unassigned' end as assigned_status
from inputs
order by id, pkhistid -- If needed
;
PKHISTID ID ROLE ENTRY_DATE WORKID ASSIGNED_STATUS
---------- ---------- ------ ---------- ---------- ---------------
1 101 Red 01/01/17 201 Unassigned
2 101 Yellow 01/02/17 201 Unassigned
3 102 Yellow 05/01/17 Assigned
4 102 Red 06/01/17 202 Assigned
5 103 Red 07/01/17 202 Assigned
6 103 Red 07/05/17 202 Assigned

Related

SQL needed to find if a property exists in multiple counties

I need some SQL to determine if a property exists in multiple counties.
I have a list of distinct property ids and county ids, but I'm not sure how to find if the property exists in more than one county.
TABLE: PROPERTIES
PROPERTYID
COUNTYID
12345
1111
12345
1112
23456
1111
34567
2222
In this example, I need some sql that will only show me property 12345 since it exists in both county 1111 and 1112.
I'm sure there is some easy SQL, but I can't figure it out.
Sample data:
SQL> with properties (propertyid, countryid) as
2 (select 12345, 1111 from dual union all
3 select 12345, 1112 from dual union all
4 select 23456, 1111 from dual union all
5 select 34567, 2222 from dual
6 )
Query:
7 select propertyid
8 from properties
9 group by propertyid
10 having count(distinct countryid) > 1;
PROPERTYID
----------
12345
SQL>

incorrect Oracle order calculation logic at the query level

With online qty and str qty, I'm having trouble calculating the quantity.
I tried the data computation with one record, and it worked perfectly with online qty and str qty. However, when I attempted it with several records (same items), it did not work. I need guidance on this.
I've written the following query, so if I'm wrong on how to design the select statement or if somebody is utilising the cursor approach, please give specifics.
Query :
with final_order as (
select 121212 po,11111 item ,20 sw_need,30 order_det from dual union all
select 131313 po,22222 item ,50 sw_need,30 order_det from dual union all
select 141414 po,33333 item ,50 sw_need,40 order_det from dual union all
select 151515 po,33333 item ,50 sw_need,30 order_det from dual union all
select 161616 po,44444 item ,50 sw_need,40 order_det from dual union all
select 171717 po,44444 item ,50 sw_need,5 order_det from dual)
select item,
case
when sw_need<order_det then sw_need
when sw_need>order_det then order_det-1
else 0
end online_qty,
case
when sw_need<order_det then order_det-sw_need
when sw_need>order_det then (order_det) -(order_det-1)
else 0
end str_qty
from final_order
Query results
ITEM
ONLINE_QTY
STR_QTY
11111
20
10
22222
29
1
33333
39
1
33333
29
1
44444
39
1
44444
4
1
Expected results
PO
ITEM
SW_NEED
ORDER_DET
ONLINE_QTY
STORE_QTY
121212
11111
20
30
20
10
131313
22222
50
30
29
1
141414
33333
50
40
40
0
151515
33333
50
30
10
20
1616416
44444
50
40
40
0
171717
44444
50
5
4
1

Oracle Function LAG and order by

*Edit Question to reflect true output, see comments.
I have the below data, I need previous program.
TableA
StartDate EndDate Program Id
1/26/15 2/23/15 Red 1
2/24/15 3/31/17 Yellow 1
5/3/16 6/1/17 Silver 1
4/1/17 1/31/18 Orange 1
2/1/18 Blue 1
MyOutput(incorrect)
StartDate EndDate Program Prev_program
1/26/15 2/23/15 Red
2/24/15 3/31/17 Yellow Red
5/3/16 6/1/17 Silver Yellow
4/1/17 1/31/18 Orange Silver
2/1/18 Blue Orange
ExpectedOutput:
StartDate EndDate Program Prev_program
1/26/15 2/23/15 Red
2/24/15 3/31/17 Yellow Red
5/3/16 6/1/17 Silver Red
4/1/17 1/31/18 Orange Yellow
2/1/18 Blue Orange
I would like to take the previous program when previous program end date is not greater than current startdate.
I used Lag which is producing results that I do not want. Lag is not taking into account "program end date is not greater than current startdate."
SELECT *
,LAG (PROGRAM, 1) OVER (PARTITION BY ID ORDER BY STARTDATE) AS PREV_PROGRAM
FROM TABLEA
Here is one way to do this. Not the most elegant or efficient, but it does the job. Two indexes, one on (id, startdate) and one on (id, enddate) may help with performance (worth testing, anyway). You are missing the id column in the output, but I assume it plays a role (and you want the processing to be done separately for each id). I wrote the query to work separately for each id, even though the test data has only one id.
The with clause is not part of the query - I included it at the top instead of creating an actual table. You don't need it - start from SELECT a1.startdate...
with
table_a ( startdate, enddate, program, id ) as (
select date '2015-01-26', date '2015-02-23', 'Red' , 1 from dual union all
select date '2015-02-24', date '2017-03-31', 'Yellow', 1 from dual union all
select date '2016-03-05', date '2017-06-01', 'Silver', 1 from dual union all
select date '2017-04-01', date '2018-01-31', 'Orange', 1 from dual union all
select date '2018-02-01', null , 'Blue' , 1 from dual
)
select a1.startdate, a1.enddate, a1.program, a1.id,
min(a2.program) keep (dense_rank last order by a2.startdate) as prev_program
from table_a a1 left outer join table_a a2
on a1.id = a2.id and a1.startdate > a2.enddate
group by a1.startdate, a1.enddate, a1.program, a1.id
;
STARTDATE ENDDATE PROGRAM ID PREV_PROGRAM
---------- ---------- -------- -- ------------
1/26/2015 2/23/2015 Red 1
2/24/2015 3/31/2017 Yellow 1 Red
3/5/2016 6/1/2017 Silver 1 Red
4/1/2017 1/31/2018 Orange 1 Yellow
2/1/2018 Blue 1 Orange

Apply MAX value. Then add conditions based on the MAX Value Row

I have the below table. I need the MAX value of Date Per ID when CategoryID = 201 Per ID
TableA
ID Date CategoryID
1 1/1/17 101
1 1/2/17 201
1 1/4/17 201
1 1/5/17 301
2 1/1/17 101
2 5/1/17 201
(Work) Query:
,MAX(TABLEA.DATE)
KEEP (DENSE_RANK LAST ORDER BY TABLEA.DATE)
OVER (PARTITION BY ID)
AS most_recent_dt
I need to add a condition in the query: When CategoryId = 201 Then take the MAX Date
Expected Output:
ID Date CatergoryId Most_Recent_Dt
1 1/1/17 101 1/4/17
1 1/2/17 201 1/4/17
1 1/4/17 201 1/4/17
1 1/5/17 301 1/4/17
2 1/1/17 101 5/1/17
2 5/1/17 201 5/1/17
*Edit
Now that I have my MAX Line I need to add Conditions based on the MAX line only.
Expected Output:
In short.
**Partition by ID.
Apply Max Value when CategoryID = 201
Now apply conditions based off the MAX value ROW
When Role = Gold and HistID is not null Then "Approved"
else "Pending"
ID Date CategoryID Most_Recent_Dt Role HistId Category
1 1/1/17 101 1/4/17 Gold (Null) Approved
1 1/2/17 201 1/4/17 Bronze 201 Approved
*1 1/4/17 201 1/4/17 Gold 101 Approved
1 1/5/17 301 1/4/17 Gold 101 Approved
2 1/1/17 101 5/1/17 Gold (Null) Pending
*2 5/1/17 201 5/1/17 Bronze 101 Pending
You should not need the KEEP clause (since it is the same as the MAX) and can just do:
MAX( CASE When CategoryId = 201 THEN TABLEA.DATE END )
OVER (PARTITION BY ID)
AS most_recent_201_dt
Now that I have my MAX Line I need to add Conditions based on the MAX line only.
Case When (Role = Gold And HistId IS NOT NULL) OR () THEN 'Approved' WHEN... THEN 'NotApproved' ELSE 'Pending' END AS Category
This is when you would use the KEEP clause as you want the values for the Role and HistID columns for the latest date value.
Something like:
CASE
WHEN (
MAX( CASE Role WHEN 'Gold' THEN Role END )
KEEP ( DENSE_RANK LAST
ORDER BY CASE WHEN CategoryId = 201 THEN TABLEA.DATE END NULLS FIRST )
OVER ( PARTITION BY ID )
= Role
AND
MAX( HistID )
KEEP ( DENSE_RANK LAST
ORDER BY CASE WHEN CategoryId = 201 THEN TABLEA.DATE END NULLS FIRST,
CASE Role WHEN 'Gold' THEN Role END NULLS FIRST )
OVER ( PARTITION BY ID )
IS NOT NULL
)
OR ( ... )
THEN 'Approved'
WHEN ...
THEN 'NotApproved'
ELSE 'Pending'
END
I would do this as:
MAX(CASE WHEN CategoryId = 201 THEN TABLEA.DATE END) OVER (PARTITION BY id) as most_recent_dt
That is, don't think of this as a "first value" calculation. Think of it as a (condition) maximum over all records with the same id.

Possible recursive or analytical query. Can this be done via SQL or is a Script Required?

Not sure if this is possible or not through a standard query but here is how I would like my data to be.
START END SETTLE NEW_SETTLE CORRECTION_FACT STORED_SETTLE
1 2 120 NULL 1 120
2 3 127 119 1.0084 128.0668
3 4 NULL 125 1.0245344 NULL
Calculations are done as follows:
CORRECTION_FACT
Basically, if NEW SETTLE is null or this is Row 1 than correction factor is always 1. If row > 1 than correction factor is PREV ROW STORED_SETTLE / NEW_SETTLE eg. 120/119
STORED_SETTLE
This will always be CORRECTION_FACTOR * SETTLE. This value is not known until the next row is inserted so there will be times where it is NULL. Tricky part is that this is dependent on CORRECTION_FACT which is also a calculated value and CORRECTION_FACT is dependent on STORED_SETTLE.
In terms of values that I have, I have SETTLE, NEW_SETTLE, START and END. CORRECTION_FACT and STORED_SETTLE will always have to be calculated.
So the question here is can I do this with some sort of recursive query or analytical function or do I have to write a script to populate?
with T1 as
(select 1 strt, 2 en, 120 settle, null new_settle from dual union all
select 2 strt, 3 en, 127 settle, 119 new_settle from dual union all
select 3 strt, 4 en, null settle, 125 new_settle from dual
)
select strt
, en
, settle
, new_settle
, correction_fact
, stored_settle
from t1
model
dimension by(row_number() over (order by strt) RowNumber)
measures(strt, en, settle, new_settle
, cast(null as number) correction_fact
, cast(null as number) stored_settle)
rules automatic order
(
correction_fact[1]=1,
correction_fact[RowNumber>1] = decode(new_settle[cv()], null,
1,stored_settle[cv()-1]/new_settle[cv()]),
stored_settle[rownumber]=(correction_fact[cv()]*settle[cv()])
);
Result:
STRT EN SETTLE NEW_SETTLE CORRECTION_FACT STORED_SETTLE
---------- ---------- ---------- ---------- --------------- -------------
1 2 120 1 120
2 3 127 119 1.00840336 128.067227
3 4 125 1.02453782