Left outer join and necessary subquery with date in Oracle - sql

I'm struggling with a left outer join where the right portion, the one that could contain nulls, doesn't show me values. I think the situation is called an implicit inner join. The reason is that the subquery doesn't return nulls but rather nothing for what I'd like to have on my right part.
(This question differs from Left Outer Join Not Working? in that a subquery to find the maximum date is needed.)
Here is a simplified example:
Table Contracts:
customer_id, status
Table Values:
customer_id, value_code, value, begin_date
I want to display all customers with status = 'active' and the latest value for a certain value_code, lets say 'volume'. There are more value_codes and the values have a certain date from which they are valid. There can also be no value_code BUT then I want a NULL on the right side.
So here is what I tried to do:
SELECT * FROM CONTRACTS C
LEFT JOIN VALUES V ON C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME'
WHERE C.STATUS = 'ACTIVE'
AND V.BEGIN_DATE = (
SELECT MAX(BEGIN_DATE) FROM VALUES V2
WHERE V2.CUSTOMER_ID = V.CUSTOMER_ID
AND V2.VALUE_CODE = 'VOLUME'
)
I can't put the subquery in the join clause, Oracle won't accept that. On the other hand my subquery makes it so that all the rows that have no entry for a value with code 'volume' are omitted. What I want is to have value = NULL instead with all the customers on the left.
Thanks for helping!

Filter the VALUE rows first and then LEFT JOIN:
SELECT *
FROM CONTRACTS C
LEFT JOIN
( SELECT *
FROM (
SELECT V.*,
ROW_NUMBER() OVER ( PARTITION BY CUSTOMER_ID ORDER BY BEGIN_DATE DESC )
AS rn
FROM VALUES V
)
WHERE rn = 1
) V
ON ( C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME' )
WHERE C.STATUS = 'ACTIVE'

As MT0 suggested using partitioning and sorting by row number helped. Only had to include value_code in the partitioning for my purpose.
So this query finally did what I wanted:
SELECT *
FROM CONTRACTS C
LEFT JOIN
( SELECT *
FROM (
SELECT V.*,
ROW_NUMBER() OVER ( PARTITION BY CUSTOMER_ID, VALUE_CODE ORDER BY BEGIN_DATE DESC )
AS rn
FROM VALUES V
)
WHERE rn = 1
) V
ON ( C.CUSTOMER_ID = V.CUSTOMER_ID
AND VALUE_CODE = 'VOLUME' )
WHERE C.STATUS = 'ACTIVE'

Related

Count with row_number function SQL CTE

I have the below CTEs that work perfectly, but I want to count the "cl.memb_dim_id" by "cl.post_date" but I am not sure how to do that? When adding in the count function I get an error that highlights the ' row number' so I am assuming I cant have both order and group together ????
WITH
DATES AS
(
select to_date('01-jan-2017') as startdate,to_date('02-jan-2017') as enddate
from dual
),
Claims as (select distinct
cl.memb_dim_id,
row_number () over (partition by cl.Claim_number order by cl.post_date desc) as uniquerow,
cl.Claim_number,
cl.post_date,
ct.claim_type,
ap.claim_status_desc,
dc.company_desc,
dff.io_flag_desc,
pr.product_desc,
cl.prov_dim_id,
cl.prov_type_dim_id
from dw.fact_claim cl
inner join dates d
on 1=1
and cl.post_date >= d.startdate
and cl.post_date <= d.enddate
and cl.provider_par_dim_id in ('2')
and cl.processing_status_dim_id = '1'
and cl.company_dim_id in ('581','585','586','589','590','591','588','592','594','601','602','603','606','596','598','597','579','599','578','577','573','574','576','575')
left join dw.DIM_CLAIM_STATUS ap
on cl.claim_status_dim_id = ap.claim_status_dim_id
left join dw.dim_claim_type ct
on cl.claim_type_dim_id = ct.claim_type_dim_id
and cl.claim_type_dim_id in ('1','2','6','7')
left join dw.DIM_COMPANY dc
on cl.company_dim_id = dc.company_dim_id
left join dw.DIM_IO_FLAG dff
on cl.io_flag_dim_id = dff.io_flag_dim_id
left join dw.dim_product pr
on cl.product_dim_id = pr.product_dim_id
)
Select * from claims where uniquerow ='1'
First, does this work?
count(cl.memb_dim_id) over (partition by cl.Claim_number, cl.post_date) as cnt,
Second, it is strange to be using analytic functions with select distinct.

Error when adding order by

Goodday, Please check my query first.
SELECT *
FROM
(
SELECT DISTINCT row, a.tanggal, b.OutletCode, c.Nilai, a.Nip, b.Fullname,
a.KodePenilaian, f.Description AS posisilama, d.ShortDesc AS posisibaru
FROM penilaian_header a
LEFT JOIN Employee b
ON a.Nip = b.Nip
LEFT JOIN Position f
ON b.PositionCode = f.PositionCode
LEFT JOIN Position d
ON a.PositionCode = d.PositionCode
LEFT JOIN arealeader g
ON g.OutletCode = b.OutletCode
LEFT JOIN
(
SELECT ROW_NUMBER() OVER (PARTITION BY KodePenilaianH
ORDER BY idPenilaiand DESC) AS Row,
Nilai, KodePenilaianH
FROM penilaian_Detail
) c
ON a.KodePenilaian = c.KodePenilaianH
WHERE a.Outlet LIKE '%%' AND Periode LIKE '%%'
ORDER BY b.OutletCode ASC
) nilai PIVOT (SUM(nilai) FOR ROW IN ([1],[2],[3],[4],[5])) piv;
My problem is when i'm add Order by my query error. Here is the error :
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.
Without Order By my query working fine.
I believe that ORDER BY b.OutletCode ASC is the cause of the error, rather than the ORDER BY inside the partition, which is necessary and should be allowed.
If you want to return all records, you can use TOP with a large number, e.g.
SELECT *
FROM
(
SELECT DISTINCT TOP 2147483647 row, a.tanggal, b.OutletCode, c.Nilai, a.Nip,
b.Fullname, a.KodePenilaian, f.Description AS posisilama, d.ShortDesc AS posisibaru
FROM penilaian_header a
LEFT JOIN Employee b
ON a.Nip = b.Nip
...
ORDER BY b.OutletCode ASC
) nilai PIVOT (SUM(nilai) FOR ROW IN ([1],[2],[3],[4],[5])) piv;

How do I find the oldest date in Group

I have a table where I need to get the oldest date from a group and be able to return all rows. I'm finding it difficult since I need to return the system_id field.
AssignedProfsHistory MatterID EffectiveDate
1 33434-3344 08/22/2005
2 33434-3344 07/12/2004
3 33434-3344 07/12/2004
4 21122-323 12/05/2007
5 43332-986 10/18/2014
6 43332-986 03/23/2013
So in this example, the rows for systemid 2 & 3 should return because they are tied for earliest date. The row for systemid 4 should return and systemid 6 should be returned.
This is what I have so far. Because I need to include the systemid(AssignedProfHistory) I'm not getting the results I need.
SELECT aph.AssignedProfsHistory,
m.MatterID,
Min(aph.EffectiveDate) as 'EffectiveDate'
from AssignedProfsHistory aph
INNER JOIN Matters m
ON aph.Matters = m.Matters
WHERE aph.AssignedType = 'Originating'
Group by m.matters,m.matterid,aph.assignedprofshistory
order by m.MatterID
Any idea how to get the results I need?
Thank you in advance.
select AssignedProfsHistory, MatterID, EffectiveDate
from (
SELECT
aph.AssignedProfsHistory,
m.MatterID,
aph.EffectiveDate,
row_number() over(partition by m.MatterID order by aph.EffectiveDate) as rn
from AssignedProfsHistory aph
INNER JOIN Matters m ON aph.Matters = m.Matters
WHERE aph.AssignedType = 'Originating'
) t
where rn = 1;
You can use the row_number window function to assign row numbers to dates for each matterid. Because the ordering is based on the ascending EffectiveDate, rows with the oldest date get assigned 1 and you select those.
If a matterid can have multiple rows with the oldest dates, you can use rank or dense_rank to get all the rows for the oldest date.
Since you want to keep the ties, I'd do it like this:
SELECT t2.AssignedProfsHistory, m.MatterID, t2.EffectiveDate
FROM (
SELECT MatterID, MIN(EffectiveDate) med
FROM AssignedProfsHistory
WHERE AssignedType = 'Originating'
GROUP BY MatterID
) t1
INNER JOIN AssignedProfsHistory t2 ON t2.MatterID = t1.MatterID
and t2.EffectiveDate = t1.med and t2.AssignedType = 'Originating'
INNER JOIN Matters m on m.Matters = t2.Matters
ORDER BY m.MatterId
Here is an SQLFiddle without the Matters table that demonstrates it can work, no windowing functions or CTE required, though a CTE would allow you to avoid repeating the AssignedType='Originating' condition.
this should get you what you need
with cte as (
SELECT aph.AssignedProfsHistory,
m.MatterID,
aph.EffectiveDate as 'EffectiveDate'
from AssignedProfsHistory aph
INNER JOIN Matters m
ON aph.Matters = m.Matters
WHERE aph.AssignedType = 'Originating'
Group by m.matters,m.matterid,aph.assignedprofshistory
)
select
AssignedProfsHistory,
MatterID,
EffectiveDate
from
cte
join (
select
min(EffectiveDate) min_effectivedate,
MatterID
from
cte
group by
MatterID
) b on cte.EffectiveDate = b.min_effectivedate and
cte.MatterID = b.MatterID
order by AssignedProfsHistory
First take the older date and then join it with your table.
WITH OlderAPH AS (
SELECT
AssignedProfsHistory,
Matters,
MIN(EffectiveDate) OlderDate
FROM AssignedProfsHistory
WHERE AssignedType = 'Originating'
GROUP BY Matters, AssignedProfsHistory )
SELECT
O.AssignedProfsHistory, M.MatterID, O.OlderDate
FROM OlderAPH O
INNER JOIN Matters M ON O.Matters = M.Matters
ORDER BY M.MatterID

inner join with two selects sql

I am trying to implement an inner join to compare values of two tables, however failing for some reason and the query is returning zero columns.
I have two tables security and security_his and trying to join them on columns SECURITY_ID and INVESTMENT_OBJECTIVE. Query is as follows
SELECT *
FROM SECURITY origin
INNER JOIN (
SELECT *
FROM SECURITY_HIS t2
WHERE DATED = (
SELECT MAX(DATED)
FROM SECURITY_HIS t1
WHERE t1.SECURITY_ID = t2.SECURITY_ID
)
) history ON origin.SECURITY_ID = history.SECURITY_ID
AND origin.INVESTMENT_OBJECTIVE = history.INVESTMENT_OBJECTIVE;
WITH cte as (
SELECT S.*,
row_number() over
(partition by S.SECURITY_ID ORDER BY SH.DATED DESC)
FROM SECURITY S
JOIN SECURITY_HIS SH
ON S.SECURITY_ID = SH.SECURITY_ID
AND S.INVESTMENT_OBJECTIVE = SH.INVESTMENT_OBJECTIVE
)
SELECT *
FROM cte
WHERE rn = 1
You have no GROUP BY on the innermost query, so only a single value, maxed over the entire table, is returned. However your query can also be simplified for easier understanding:
SELECT origin.*, history.Dated
FROM SECURITY origin
INNER JOIN (
SELECT
SECURITY_ID,
INVESTMENT_OBJECTIVE,
MaxDated = MAX(DATED)
FROM SECURITY_HIS t2
GROUP BY
SECURITY_ID,
INVESTMENT_OBJECTIVE
) history ON origin.SECURITY_ID = history.SECURITY_ID
AND origin.INVESTMENT_OBJECTIVE = history.INVESTMENT_OBJECTIVE

SQL Query to return last value from a number of tags

I hope you might be able to help. I'm a novice at SQL so this one is starting to bug me.
Currently I am collecting data every day for a Meter Name. This data is currently being logged in a table with the columns TimeStamp, Name, Value. However I would like to create a query which will only return the most recent (last) value recorded against each Name in the table.
I've built this query so far but the Top 1 syntax doesn't seem to be what I need.
SELECT Top 1 (DataLog.Timestamp), MeterTags.Name, DataLog.Value
FROM Meters
INNER JOIN MeterTags
ON Meters.MeterId = MeterTags.MeterId
INNER JOIN DataLog
ON MeterTags.MeterTagId = DataLog.MeterTagId
WHERE Meters.MeterTypeId = 8
GROUP By MeterTags.Name, DataLog.Timestamp
Any advice you could give would be appreciated.
Thanks in advance.
You can use ROW_NUMBER to give each record a rownumber (resetting to 0 for each MeterTags.Name) then just select the first for each name:
WITH CTE AS
( SELECT DataLog.Timestamp,
MeterTags.Name,
DataLog.Value,
RowNumber = ROW_NUMBER() OVER(PARTITION BY MeterTags.Name
ORDER BY DataLog.TimeStamp DESC)
FROM Meters
INNER JOIN MeterTags
ON Meters.MeterId = MeterTags.MeterId
INNER JOIN DataLog
ON MeterTags.MeterTagId = DataLog.MeterTagId
WHERE Meters.MeterTypeId = 8
)
SELECT CTE.Timestamp,
CTE.Name,
CTE.Value
FROM CTE
WHERE CTE.RowNumber = 1;
Another solution is to use the TOP 1 inside an APPLY:
SELECT DataLog.Timestamp,
MeterTags.Name,
DataLog.Value
FROM Meters
INNER JOIN MeterTags
ON Meters.MeterId = MeterTags.MeterId
CROSS APPLY
( SELECT TOP 1 TimeStamp, Value
FROM DataLog
WHERE MeterTags.MeterTagId = DataLog.MeterTagId
ORDER BY TimeStamp DESC
) DataLog
WHERE Meters.MeterTypeId = 8;
Try below query
select Timestamp,Name,Value
from
(
SELECT (DataLog.Timestamp), MeterTags.Name, DataLog.Value,rownum,ROW_NUMBER() OVER
(PARTITION BY MeterTags.Name ORDER BY DataLog.Timestamp desc) AS rownum FROM Meters
INNER JOIN MeterTags
ON Meters.MeterId = MeterTags.MeterId
INNER JOIN DataLog
ON MeterTags.MeterTagId = DataLog.MeterTagId
)data
where rownum=1