Change order by in partition by clause based on the value - sql

I have data with columns contractID, typecode, typecodedate. Typecode can be A1,A2,B1,B2 or empty and one contractID can have multiple typecodes. If contract has empty typecode, I need my query to select that regardless of the typecodedate else. If contract does not have empty typecode it should select typecode with latest typecodedate.
contractID
typecode
typecodedate
20135
20221020
20135
A1
20221021
20136
A2
20221022
20136
B1
20221023
Expected:
contractID
typecode
typecodedate
20135
20221020
20136
B1
20221023
I've tried to use
with a as (select
contractID,
typecode,
typecodedate,
case when typecode = '' then row_number() over (partition by contractID order by typecode asc)
else row_number() over (partition by contractID order by typecodedate desc) end rank
from table)
select * from a where rank=1
This works with contractID that don't have '' typecode, but if contractID has '' and value typecode then it returns rank=1 for both rows

You could move the case expression to the window function to explicitly control the date used for ordering:
with a as (
select
contractID,
typecode,
typecodedate,
row_number() over (
partition by contractID
order by case when typecode = '' then '20990101' else typecodedate end desc
) rank
from table
)
select * from a
where rank = 1;

Related

How to join to a statement with a row_number() function in SQL?

I a SQL with a row_number() function, and I would like to join on additional tables to get the fields below. How would I accomplish this?
Desired fields:
EMPLOYEE.EMPLID
EMPLOYEE.JOBTITLE
NAME.FIRST_NAME
NAME.LAST_NAME
LOCATION.ADDRESS
PROFESSIONAL_NAME.PROF_NAME
Beginning SQL:
SELECT COUNT(*)
FROM
(
SELECT EMPLOYEE.*, ROW_NUMBER() OVER (PARTITION BY EMPLID ORDER BY
PRIM_ROLE_IND DESC, EMPL_RCD ASC) as RN
FROM EMPLOYEE
WHERE JOB_INDICATOR = 'P'
) dt
WHERE RN = 1
When I try to add a left join at the end, I get an error that says "EMPLOYEE"."EMLID" invalid identifier.
What I'm trying:
SELECT
EMPLOYEE.EMPLID,
EMPLOYEE.JOBTITLE,
NAME.FIRST_NAME,
NAME.LAST_NAME,
LOCATION.ADDRESS,
PROFESSIONAL_NAME.PROF_NAME
FROM
(
SELECT EMPLOYEE.*, ROW_NUMBER() OVER (PARTITION BY EMPLID ORDER BY
PRIM_ROLE_IND DESC, EMPL_RCD ASC) as RN
FROM EMPLOYEE
WHERE JOB_INDICATOR = 'P'
)
LEFT JOIN NAME ON EMPLOYEE.EMPLID = NAME.EMPLID
WHERE
RN = 1
AND
NAME.EFFDT = (
SELECT
MAX (NAME2.EFFDT)
FROM
NAME NAME2
WHERE
NAME2.EMPLID = NAME.EMPLID
AND NAME.NAME_TYPE = 'PRI'
)
AND EMPLOYEE.JOB_INDICATOR = 'P'
You just need to alias your table
...
(
SELECT EMPLOYEE.*, ROW_NUMBER() OVER (PARTITION BY EMPLID ORDER BY
PRIM_ROLE_IND DESC, EMPL_RCD ASC) as RN
FROM EMPLOYEE
WHERE JOB_INDICATOR = 'P'
) temp_employee --add this
LEFT JOIN NAME ON temp_employee.EMPLID = NAME.EMPLID
...
When you create your new table with row_number() in an inner select you essentially create a new table. You need to alias or name this table and then refer to that alias. In the above your from is the inner select, not the EMPLOYEE table. See below for simplified example.
select newtable.field from (select field from mytable) newtable

I need to create a field that is the second date to a maxdate

My SQL Server database includes all contracts. my query finds the contract value for the latest date, I need another column that shows the contract value for the second last contract.
Example:
ContractID ordd_mn_end_date ord_mn_billed_amt
-------------------------------------------------
8198 10-31-2021 2574.43
8198 10-31-2020 833.15
What I would like to happen is for the ExpiringAmt 2 to show 833.15
Code
SELECT
det.ordd_ContractItemID,
det.ordd_mn_end_date,
det.ordd_mn_billed_amt AS Expiring_Amt,
det.ordd_mn_billed_amt AS Expiring_Amt2
FROM
ccmast_restore_20201021.dbo.lti_orddet det
WHERE
det.ordd_mn_end_date IN (SELECT MAX(det.ordd_mn_end_date)
FROM ccmast_restore_20201021.dbo.lti_orddet det
GROUP BY det.ordd_ContractItemID)
AND det.ordd_ContractItemID IN (8198)
ORDER BY
det.ordd_mn_end_date DESC
You can do it with window functions LAG() and ROW_NUMBER():
SELECT ContractID, ordd_mn_end_date, Expiring_Amt, Expiring_Amt2
FROM (
SELECT ContractID,
ordd_mn_end_date,
ord_mn_billed_amt Expiring_Amt,
LAG(ord_mn_billed_amt) OVER (PARTITION BY ContractID ORDER BY ordd_mn_end_date) Expiring_Amt2,
ROW_NUMBER() OVER (PARTITION BY ContractID ORDER BY ordd_mn_end_date DESC) rn
FROM lti_orddet
WHERE ContractID IN (8198)
) t
WHERE rn = 1
If you are interested only in 1 ContractID, like your query, then you can remove PARTITION BY ContractID from both window functions.
See the demo.
Results:
ContractID
ordd_mn_end_date
Expiring_Amt
Expiring_Amt2
8198
2021-10-31
2574.43
833.15
I would use LEAD() or LAG() to append the next or previous value of ordd_mn_billed_amt , and then use ROW_NUMBER() to always pick the first row...
WITH
lti_orddet_augmented AS
(
SELECT
lti_orddet.*,
LEAD(ord_mn_billed_amt)
OVER (PARTITION BY ordd_ContractItemID
ORDER BY ordd_mn_end_date DESC
)
AS ord_mn_billed_amt_previous,
ROW_NUMBER()
OVER (PARTITION BY ordd_ContractItemID
ORDER BY ordd_mn_end_date DESC
)
AS ordd_ContractItemID_RowNum
FROM
ccmast_restore_20201021.dbo.lti_orddet
)
SELECT
*
FROM
lti_orddet_augmented
WHERE
ordd_ContractItemID_RowNum = 1
AND ordd_ContractItemID = 8198
In this case I used LEAD() so that the window for both functions is in the same order.

How to get records from table based on conditions

Select only latest amount, if null then before that.
table a
customer|amount|date
001|2 |20201101
001|null|20201102
001|3 |20201103
002|8.9 |20201101
002|7 |20201008
002|null|20201106
Result
001|null|20201101
001|null|20201102
001|3 |20201103
002|null|20201101
002|null|20201008
002|7 |20201106
amount data should be taken latest as per date , other record will be null, if amount is null for the latest date it should take the previous not null value.
My current attempt:
select top 1 [amount]
from table
where [amount] is not null
order by date desc
If you want to set all but the most recent value to NULL:
select customer_code, date,
(case when seqnum = 1 then amount end) as amount
from (select t.*,
row_number() over (partition by customer_code order by (amount is not null) desc, date desc) as seqnum
from table t
) t
where customer_code = '001'
order by date desc
Probably what you are looking for is a window function:
SELECT *
FROM (SELECT *,
row_number() over
(partition by customer
order by amount desc, date desc) as rn
FROM your_table
WHERE amount is not null)
WHERE rn = 1
You can use row_number or dense_rank depending on your needs
Create a view that returns all inserted values in descending order. Then select the first or second row according to the condition.

SQL Selecting from Sub Query

I have a table with contract ids that have multiple values.
SELECT contractid
,milestoneid
,DATE
,type
,RANK() OVER (PARTITION BY contractid ORDER BY Milestoneid ASC) AS RankNbr
FROM [TSWDATA].[dbo].t_milestone
WHERE contractid = 1056229
contractid milestoneid date type RankNbr
1056 43269 10/10/15 Full 1
1056 43449 10/26/15 GB 2
1056 43456 10/26/15 Submit for Funding 3
1056 43463 10/26/15 Cleared 4
I need to join to the main contract table and pull the contract only when the value 'GB' is the maximum milestoneid.
Can I do this in the where clause?
If you need the records from your contracts table, then you can join to your ranked query. Just change your order by to DESC so the RankNbr 1 will be the maximum milestoneid
SELECT *
FROM [TSWDATA].[dbo].t_contract c
JOIN (
SELECT contractid
,type
,RANK() OVER (PARTITION BY contractid ORDER BY Milestoneid DESC) AS RankNbr
FROM [TSWDATA].[dbo].t_milestone
) ms ON ms.contractid = c.contractid
WHERE
--contractid = 1056229 AND
ms.RankNbr = 1 AND ms.type = 'GB'
Does this work for you?
SELECT contractid
,milestoneid
,DATE
,type
,RANK() OVER (PARTITION BY contractid ORDER BY Milestoneid ASC) AS RankNbr
FROM [TSWDATA].[dbo].t_milestone
WHERE EXISTS (SELECT TOP (1) *
FROM SubTable AS s
WHERE s.type = 'GB'
AND s.contractID = [TSWDATA].[dbo].t_milestone.contractid
ORDER BY s.milestoneID DESC
)
AND contractid = 1056229

ROW_NUMBER() over (Partition by....) to return specific row

My query looks like this:
with T1 as (
Select
Dept_No,
Product_No,
Order_No,
Order_Type
Row_number() over (partition by Product_ID order by Order_No desc) as "COUNT"
From Orders_Table)
Select * from T1
where ("COUNT" = '1' and "Order_Type" <> 'Cancel')
or ("COUNT" = '2' AND "Order_Type" <> 'Cancel'
So I'm trying to pull the most recent order that was not canceled. Essentially my ROW_number() over (partition by...) function labels the orders in sequential order with 1 being the most recent order and 2 being the second most recent order. The issue is that with this query it pulls both the most recent, and 2nd most recent order. I am trying to write this to where if it only gives one or the other. So if COUNT = 1 and the order_type is not cancel, only show me that record. If not then show me the 2nd.
Thanks so much for the help in advance. This is being done in Toad for Oracle 9.5 using SQL tab.
But you have an or.
If both or are satisfied you will get two rows.
with T1 as (
Select
Dept_No,
Product_No,
Order_No,
Order_Type
Row_number() over (partition by Product_ID order by Order_No desc) as "COUNT"
From Orders_Table
where "Order_Type" <> 'Cancel')
Select * from T1
where "COUNT" = '1'
Use a case expression to control which rows get a result from row_number(), here we avoid numbering any rows that have been cancelled:
WITH t1 AS (
SELECT
Dept_No
, Product_No
, Order_No
, Order_Type
/* the most recent order that was not canceled */
, case when order_type <> 'Cancel'
then ROW_NUMBER() OVER (PARTITION BY Product_ID
ORDER BY Order_No DESC)
end AS is_recent
FROM Orders_Table
)
SELECT
*
FROM t1
WHERE is_recent = 1
Or, perhaps the easiest way is to simply exclude cancelled orders e.g.
WITH t1 AS (
SELECT
Dept_No
, Product_No
, Order_No
, Order_Type
, ROW_NUMBER() OVER (PARTITION BY Product_ID
ORDER BY Order_No DESC)
AS is_recent
FROM Orders_Table
WHERE order_type <> 'Cancel'
)
SELECT
*
FROM t1
WHERE is_recent = 1
nb: row_number() returns an integer, so don't compare that column to a string