ORACLE SQL - multiple JOINs from same table - sql

I have data related material transactions in one table and log history header data related to materials is in another table and detailed log history data in third table. I'm trying to get different status update dates matched to material table but I get duplicate rows for one material transaction
Original material transaction table:
ORDER_NO
MATERIAL
QTY
0001
MAT01
2
0002
MAT02  
5
Original Log History Header transaction table:
ORDER_NO
LOG_ID
0001
1001
0001
1002
Status code 1 refers to Opened and code 2 to Closed
Detailed Log History table:
LOG_ID
STATUS_CODE
DATE
1001
1
11/12/2021
1002
2  
15/12/2021
With following SQL query:
SELECT
TO_CHAR (m.order_no) order_no,
m.material,
a.date opened_date,
ab.closed_date
FROM MATERIAL_TRANSACTIONS m
INNER JOIN HISTORY_LOG t
ON m.ORDER_NO = t.ORDER_NO
INNER JOIN HISTORY_LOG_DETAILED a
ON t.LOG_ID = a.LOG_ID
AND a.STATUS_CODE = '1'
INNER JOIN HISTORY_LOG_DETAILED ab
ON t.LOG_ID = ab.LOG_ID
AND ab.STATUS_CODE = '2'
I get following result:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
0001
MAT01  
2
15/12/2021
And I would like to get the status dates to the same row as below:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
15/12/2021
I would appreciate all the help I can get and am very sorry if there already is topic for similar issue.

Your problem occurs because you join the history table, which holds 2 records for the order. You could flatten this if you use 2 inline tables that hold exactly 1 record.
with opened_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '1'
), closed_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '2'
)
select to_char (m.order_no) order_no,
m.material,
o.date opened_date,
c.date closed_date
from material_transactions m
join opened_dates o on m.order_no = o.order_no
join closed_dates c on m.order_no = c.order_no
;

Just an idea :
I joined HISTORY_LOG and HISTORY_LOG_DETAILED tables to get dates for specific status, and set as OPENED_DATE and CLOSED_DATE (if status 1 , then opened date is DATE column, otherwise set it as 01.01.0001)
After that grouped those records by ORDER_NO and maxed the date values to get actual OPENED_DATE and CLOSED_DATE .
Finally joined this subquery with MATERIAL_TRANSACTIONS table :
SELECT
TO_CHAR (M.ORDER_NO) ORDER_NO,
M.MATERIAL,
QTY,
L_T.OPENED_DATE,
L_T.CLOSED_DATE
FROM MATERIAL_TRANSACTIONS M
INNER JOIN
(
SELECT L.ORDER_NO ,
MAX( CASE WHEN LD.STATUS_CODE = 1 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) OPENED_DATE
MAX( CASE WHEN LD.STATUS_CODE = 2 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) CLOSED_DATE
FROM
HISTORY_LOG L
INNER JOIN HISTORY_LOG_DETAILED LD ON LD.LOG_ID = L.LOG_ID
GROUP BY L.ORDER_NO
) L_T on L_T.ORDER_NO = M.ORDER_NO
Note: I didnt test it. So there can be small syntax errors. Please check it and for better help add a fiddle so i can test my query

Related

How to get the price per unit based on the price of an item at the highest rate

I have multiple tables join together to get the latest Transaction Date, Stock Code, Stock Name, UOM Code, Rate, UOM Price, Current Balance for an item.
The problem is to get the UOM Price for the highest rate is easy, but what if I want to get the UOM Price Per Unit based on the highest rate which is by doing this formula UOM Price / Rate = Price Per Unit
In my mind, I want to do like this :
If an item have multiple UOM Codes, then check if there are multiple rates.
If Yes, then do the formula and put the value to the lowest rate as it is the price per unit for the item.
If No, then no need to do the formula.
Here is my code
SELECT *
FROM(
SELECT
TransactionDate, -- from StockTransactions Table
DocumentCode, -- from StockTransactions Table
StockCode, -- from Stocks Table
StockName, -- from Stocks Table
UOMCode, -- from UOMs Table
Rate, -- from UOMs Table
st.UOMPriceExcLandingCost as UOMPrice, -- from StockTransactions Table
FORMAT(CurrentBalance, 'N4') AS CurrentBalance, --from stocks table
ROW_NUMBER() OVER(PARTITION BY StockCode, UOMCode ORDER BY TransactionDate DESC) RN -- sort from latest to oldest stock transactions based on stockcode and UOMCode
FROM UOMs us
LEFT JOIN Stocks s ON s.Id = us.StockId -- join Stocks Table
LEFT JOIN StockTransactions st ON st.StockId = us.StockId -- join StockTransactions Table
LEFT JOIN StockPurchasePriceHistory spph on spph.Stock = us.StockId
WHERE
st.TransactionDate BETWEEN '2000-01-01' AND GETDATE() --get result from specific transaction date in StockTransations Table
AND st.Qty > 0
AND st.DocumentCode NOT LIKE 'SA%' --get all documentcode except documentcode that start with SA in StockTransations Table
OR st.DocumentCode LIKE 'SA1503%' --get all documentcode that start with SA1503 in StockTransations Table
AND st.DocumentCode NOT LIKE 'CN%' --get all documentcode except documentcode that start with CN in StockTransations Table
GROUP BY
--group columns that might have multple records with different stock transactions date
TransactionDate,
DocumentCode,
StockCode,
StockName,
UOMCode,
Rate,
st.UOMPriceExcLandingCost,
CurrentBalance
) StockInquiry
WHERE
RN = 1 -- get latest date in stockpurchasepricehistory table
AND StockCode = 'WRC3MM' -- for checking purpose only
ORDER BY StockCode ASC -- sort result by stockcode in ascending order
Here is my output
Here is the sample of the Database : SQLFiddle
Here is the sample of the Database extract using ApexSQL = Google Drive
The output should be :
If METER then it should be 230 / 305 = 0.7540 Price Per Unit
If ROLL/305 then it should be 230
If you look at the UOM Price right now, it is getting the highest price for the highest rate.
It is possible to solve this even if an item have different UOM Code?
I think you need to modify the join to [StockTransactions] so that you include the UOMid in the join logic PLUS you can also include the row_number() calculation in as a subquery so that you only join to the latest rows of that table:
/* modified */
SELECT
TransactionDate, -- from StockTransactions Table
DocumentCode, -- from StockTransactions Table
StockCode, -- from Stocks Table
StockName, -- from Stocks Table
UOMCode, -- from UOMs Table
Rate, -- from UOMs Table
st.UOMPriceExcLandingCost as UOMPrice, -- from StockTransactions Table
FORMAT(CurrentBalance, 'N4') AS CurrentBalance, --from stocks table
st.RN
FROM UOMs us
INNER JOIN Stocks s ON s.Id = us.StockId -- join Stocks Table
INNER JOIN (
select *
, row_number() over(partition by StockId, UOMid order by TransactionDate DESC) as rn
from StockTransactions
) st ON st.StockId = us.StockId AND st.UOMid = us.id AND rn = 1
WHERE
st.TransactionDate BETWEEN '2000-01-01' AND GETDATE() --get result from specific transaction date in StockTransations Table
AND st.Qty > 0
AND (st.DocumentCode NOT LIKE 'SA%' --get all documentcode except documentcode that start with SA in StockTransations Table
OR st.DocumentCode LIKE 'SA1503%') --get all documentcode that start with SA1503 in StockTransations Table
AND st.DocumentCode NOT LIKE 'CN%' --get all documentcode except documentcode that start with CN in StockTransations Table
;
result:
+-----------------+--------------+-----------+--------------------------------+-----------+------+----------+----------------+----+
| TransactionDate | DocumentCode | StockCode | StockName | UOMCode | Rate | UOMPrice | CurrentBalance | RN |
+-----------------+--------------+-----------+--------------------------------+-----------+------+----------+----------------+----+
| 2015-03-01 | SA1503/388 | WRC3MM | WIRE ROPE GI C/W COVER 3MMX5MM | METER | 1 | 0.75 | 22.3000 | 1 |
| 2016-05-14 | BIL1605/065 | WRC3MM | WIRE ROPE GI C/W COVER 3MMX5MM | ROLL/305M | 305 | 230 | 22.3000 | 1 |
+-----------------+--------------+-----------+--------------------------------+-----------+------+----------+----------------+----+
demo
Also, I think you need parentheses in the where clause to control the OR, I don't know if I have introduced these correctly however.
I have solved my problem thanks to Paul Maxwell solution
Here is the final code, I'm not sure if it is the correct way to do it but it works
SELECT
DocDate,
DocCode,
StockCode,
FORMAT((Price / us.Rate), 'N4') AS PricePerUnit,
UOMCode AS LastPurchasePriceUOM,
FORMAT(Price, 'N4') AS LastPurchasePrice,
FORMAT(CurrentBalance, 'N4') AS Balance,
st.RN
FROM StockPurchasePriceHistory spph
INNER JOIN Stocks s ON spph.Stock = s.Id
INNER JOIN (
SELECT
Id,
Rate,
UOMCode
FROM UOMs
WHERE
UOMs.Description IS NULL
) us ON spph.UOM = us.Id
INNER JOIN (
SELECT
StockId,
UOMId,
TransactionDate,
DocumentCode,
ROW_NUMBER() OVER(PARTITION BY StockId, UOMId ORDER BY TransactionDate DESC) AS RN
FROM StockTransactions
) st ON st.StockId = spph.Stock AND st.UOMId = us.Id AND RN = 1
WHERE
st.TransactionDate BETWEEN '2000-01-01' AND GETDATE()
AND (st.DocumentCode NOT LIKE 'SA%' OR st.DocumentCode LIKE 'SA1503%')
AND st.DocumentCode NOT LIKE 'CN%'
ORDER BY StockCode ASC
Sample here : SQL Fiddle
Output is like this :
DocDate
DocCode
StockCode
PricePerUnit
LastPurchasePriceUOM
LastPurchasePrice
Balance
RN
2016-05-13
BIL1605/065
WRC3MM
0.7541
ROLL/305M
230.0000
22.3000
1

SQL join to and from dates - return most recent if no match found

I have two tables that I need to join. I have:
LEFT JOIN AutoBAF on (GETDATE() BETWEEN AutoBAF.FromDate and AutoBAF.ToDate)
and I get the expected result. Now if no matching record is found between the two dates (AutoBAF.FromDate and AutoBAF.ToDate) I would like to join the most recent matching record instead.
Can anyone point me in the right direction.
I am using a MS SQL database hosted in Azure.
Small example:
a small example of what I am trying to achieve:
Table Product:
Product | Description
A | Product A
Table Price
Product | FromDate | ToDate | Price
A | 01-01-20 | 31-01-20 | 100
A | 01-02-20 | 28-02-20 | 110
I need a query that will return the price according to the date returned by GETDATE().
If I run the query 15-01-20 I should get:
Product | Description | Price
A | Product A | 100
If I run the query 15-02-20 I should get:
Product | Description | Price
A | Product A | 110
and finally if I run the query 15-03-20 I will have no price in the Price table. Instead of returning null I would like to "fall back" to the most recent known price instead which in this example is 110
This is not the fastest query cause it joins products with all records with future dates. But if your tables are small, it works.
SELECT product.product, product.description, isnull(pr_curr.price, pr_fut.price) as price
FROM product
left join PRICE pr_curr on product.product=pr_curr.product
and GETDATE() BETWEEN pr_curr.FromDate and pr_curr.ToDate
left join PRICE pr_fut on product.product=pr_fut.product
and GETDATE() < pr_fut.FromDate
where pr_fut.FromDate = (
select min(FromDate) from PRICE dates
where dates.product=pr_fut.product and dates.FromDate>GETDATE()
) or pr_fut.FromDate is null
This looks like SQL Server code, which supports lateral joins via the apply keyword. Assuming you want only one match:
from product p outer apply
(select top (1) ab.*
from autobaf ab
where ab.product = p.product and
getdate() <= ab.todate
order by ab.todate desc
) ab
Note that this correlates on the product, which is not part of your question.
If that is not necessary, then you can use:
from t left join
(select top (1) ab.*
from autobaf ab
where getdate() <= ab.todate
order by ab.todate desc
) ab
on 1 = 1
If you know that there is some record in the past, then you can use cross join instead of left join and dispense with the on clause.
SELECT product.product, product.description, isnull(pr_curr.price, pr_fut.price) as price
FROM product
left join PRICE pr_curr on product.product=pr_curr.product
and GETDATE() BETWEEN pr_curr.FromDate and pr_curr.ToDate
left join PRICE pr_fut on product.product=pr_fut.product
and GETDATE() > pr_fut.FromDate
where pr_fut.FromDate = (
select max(FromDate) from PRICE dates
where dates.product=pr_fut.product and dates.FromDate<GETDATE()
) or pr_fut.FromDate is null

Self join query using SQL - demo query attached

I have a scenario where I need my query to pull out data based on Code value.
Example: The table #temp1 has the combination of Person ID, Program assignments and dataset. For any person '1001', I want to pull out the admission date of first program and discharge date and dataset of the last program under similar code 'PS'.
So, my desired output is:
Demo code:
https://rextester.com/ADDL95491
Any help?!
It seems to me you need below query
with cte1 as
(select cid,code,admissiondate,dischargedate,program
from #temp1 t1 where
t1.row_number = (select min(row_number) from #temp1)
) , cte2 as
(select * from #temp1 where dataset is not null
)
select cte1.cid,cte1.code,cte1.admissiondate,
cte2.dischargedate,cte2.dataset
from cte1 left join cte2 on cte1.code=cte2.code
https://rextester.com/BNVK71028
cid code admissiondate dischargedate dataset
1 1001 PR 01/01/2011 5/1/2011 discharge data
2 1001 PS 06/01/2011 7/1/2011 discharge data
3 1001 PQ 08/01/2011

SQL Server 2008 - need help on a antithetical query

I want to find out meter reading for given transaction day. In some cases there won’t be any meter reading and would like to see a meter reading for previous day.
Sample data set follows. I am using SQL Server 2008
declare #meter table (UnitID int, reading_Date date,reading int)
declare #Transactions table (Transactions_ID int,UnitID int,Transactions_date date)
insert into #meter (UnitID,reading_Date,reading ) values
(1,'1/1/2014',1000),
(1,'2/1/2014',1010),
(1,'3/1/2014',1020),
(2,'1/1/2014',1001),
(3,'1/1/2014',1002);
insert into #Transactions(Transactions_ID,UnitID,Transactions_date) values
(1,1,'1/1/2014'),
(2,1,'2/1/2014'),
(3,1,'3/1/2014'),
(4,1,'4/1/2014'),
(5,2,'1/1/2014'),
(6,2,'3/1/2014'),
(7,3,'4/1/2014');
select * from #meter;
select * from #Transactions;
I expect to get following output
Transactions
Transactions_ID UnitID Transactions_date reading
1 1 1/1/2014 1000
2 1 2/1/2014 1010
3 1 3/1/2014 1020
4 1 4/1/2014 1020
5 2 1/1/2014 1001
6 2 3/1/2014 1001
7 3 4/1/2014 1002
Your SQL Query to get your desired out put will as following:
SELECT Transactions_ID, T.UnitID, Transactions_date
, (CASE WHEN ISNULL(M.reading,'') = '' THEN
(
SELECT MAX(Reading) FROM #meter AS A
JOIN #Transactions AS B ON A.UnitID=B.UnitID AND A.UnitID=T.UnitID
)
ELSE M.reading END) AS Reading
FROM #meter AS M
RIGHT OUTER JOIN #Transactions AS T ON T.UnitID=M.UnitID
AND T.Transactions_date=M.reading_Date
I can think of two ways to approach this - neither of them are ideal.
The first (and slightly better) way would be to create a SQL Function that took the Transactions_date as a parameter and returned the reading for Max(Reading_date) where reading_date <= transactions_date. You could then use this function in a select statement against the Transactions table.
The other approach would be to use a cursor to iterate through the transactions table and use the same logic as above where you return the reading for Max(Reading_date) where reading_date <= transactions_date.
Try the below query:
Please find the result of the same in SQLFiddle
select a.Transactions_ID, a.UnitID, a.Transactions_date,
case when b.reading IS NULL then c.rd else b.reading end as reading
from
Transactions a
left outer join
meter b
on a.UnitID = b.UnitID
and a.Transactions_date = b.reading_Date
inner join
(
select UnitID,max(reading) as rd
from meter
group by UnitID
) as C
on a.UnitID = c.UnitID

Sql data retrieval issue

I have below tables:
Order
Order_id orde_number Order_name
1 12345 iphone
2 67891 samsung
order_event
order_event_no status
1 D
1 C
2 C
I wrote below query to retrieve status not in ('D') like below ,But it gave me 2 records ,
But query should not return because order_no 1 already as status D, even though it has second record C it should not include.
select o.order_number,o.order_name
from order o
join order_event oe
on (o.order_id=oe.order_event_no) where oe.status not in ('D')
Regards,
Chaitu
This will accomplish what you want with your given schema / data...
SELECT order_number, order_name
FROM order
WHERE order_id NOT IN (SELECT order_event_no FROM order_event WHERE status = 'D')
If you want to exclude any order who has a status like 'D' you need a subquery.
select o.order_number,o.order_name
from order o
where oe.order_event_no
NOT IN
(SELECT order_event_no FROM order_event_no WHERE status = 'D')
This is equivalent. Some RDBMs will execute it faster:
Select
o.order_number,
o.order_name
from
order o
where
not exists (
select
'x'
from
order_event oe
where
oe.order_event_no = o.order_id And
oe.status = 'D'
);