lookup and sum contracts in DB2 SQL - sql

I'm trying to compute the value of the contract amendments in this below table.
when there is an amendment to an contract the value is updated with the remainder from the last version of the contract + the value added in the amendment.
To illustrate i added the info on how its calculated in the last column.
How would I go about that in SQL for DB2, I'm hitting a wall here.
Thanks

Consider using a self join each calculating running number by Contract with ROW_NUMBER() . Specifically, have the LEFT JOIN filtered to one row number behind the first FROM table. From there columns will align for AmendmentValue calculation:
SELECT sub1.Contract, sub1.Amendment, sub1.Value, sub1.Billed, sub1.Reminder,
(CASE WHEN sub1.Amendment IS NULL THEN NULL
ELSE sub1.Billed - sub2.Remainder END) As AmendmentValue
FROM
(SELECT t.Contract, t.Amendment, t.Value, t.Billed, t.Remainder,
ROW_NUMBER() OVER (PARTITION BY t.Contract
ORDER BY CASE WHEN t.Amendment IS NULL THEN 1 ELSE t.Amendment END) rn
FROM TableName t) sub1
LEFT JOIN
(SELECT t.Contract, t.Amendment, t.Value, t.Billed, t.Remainder,
ROW_NUMBER() OVER (PARTITION BY t.Contract
ORDER BY CASE WHEN t.Amendment IS NULL THEN 1 ELSE t.Amendment END) rn
FROM TableName t) sub2
ON sub1.Contract = sub2.Contract AND sub1.rn = sub2.rn + 1

Related

Improving a SQL teradata query

I have a table like below and I want 'Y' in front of Ref 345 and 789 in the result-set on basis of count(Ref) = 1 where the amount is less than 0. I am using this query to get the desired output. My question is, is there any other (and more efficient) way to do it in Teradata?
SELECT T.Ref,T.AMOUNT, R.Refund_IND as Refund_IND
FROM Table1 t
LEFT JOIN (select 'Y' as Refund_IND, Ref from Table1 where Ref in
(select Ref from Table1 where amount < 0)
group by Ref having count(Ref) = 1) R on t.Ref = R.Ref
You can use window functions to test these conditions:
SELECT
Ref,
Amount,
CASE WHEN COUNT(*) OVER (PARTITION BY REF) = 1 AND Amount < 0 THEN 'Y' ELSE '' END AS Refund_Ind
FROM Table1

SQL Joined Tables - Multiple rows on joined table per 'on' matched field merged into one row?

I have two tables I am pulling data from. Here is a minimal recreation of what I have:
Select
Jobs.Job_Number,
Jobs.Total_Amount,
Job_Charges.Charge_Code,
Job_Charges.Charge_Amount
From
DB.Jobs
Inner Join
DB.Job_Charges
On
Jobs.Job_Number = Job_Charges.Job_Number;
So, what happens is that I end up getting a row for each different Charge_Code and Charge_Amount per Job_Number. Everything else on the row is the same. Is it possible to have it return something more like:
Job_Number - Total_Amount - Charge_Code[1] - Charge_Amount[1] - Charge_Code[2] - Charge_Amount[2]
ETC?
This way it creates one line per job number with each associated charge and amount on the same line. I have been reading through W3 but haven't been able to tell definitively if this is possible or not. Anything helps, thank you!
To pivot your resultset over a fixed number of columns, you can use row_number() and conditional aggregation:
select
job_number,
total_amount,
max(case when rn = 1 then charge_code end) charge_code1,
max(case when rn = 1 then charge_amount end) charge_amount1,
max(case when rn = 2 then charge_code end) charge_code2,
max(case when rn = 2 then charge_amount end) charge_amount2,
max(case when rn = 3 then charge_code end) charge_code3,
max(case when rn = 3 then charge_amount end) charge_amount3
from (
select
j.job_number,
j.total_amount,
c.charge_code,
c.charge_amount,
row_number() over(partition by job_number, total_amount order by c.charge_code) rn
from DB.Jobs j
inner join DB.Job_Charges c on j.job_number = c.job_number
) t
group by job_number, total_amount
The above query handes up to 3 charge codes and amounts par job number (ordered by job codes). You can expand the select clause with more max(case ...) expressions to handle more of them.

Check whether an employee is present on three consecutive days

I have a table called tbl_A with the following schema:
After insert, I have the following data in tbl_A:
Now the question is how to write a query for the following scenario:
Put (1) in front of any employee who was present three days consecutively
Put (0) in front of employee who was not present three days consecutively
The output screen shoot:
I think we should use case statement, but I am not able to check three consecutive days from date. I hope I am helped in this
Thank you
select name, case when max(cons_days) >= 3 then 1 else 0 end as presence
from (
select name, count(*) as cons_days
from tbl_A, (values (0),(1),(2)) as a(dd)
group by name, adate + dd
)x
group by name
With a self-join on name and available = 'Y', we create an inner table with different combinations of dates for a given name and take a count of those entries in which the dates of the two instances of the table are less than 2 units apart i.e. for each value of a date adate, it will check for entries with its own value adate as well as adate + 1 and adate + 2. If all 3 entries are present, the count will be 3 and you will have a flag with value 1 for such names(this is done in the outer query). Try the below query:
SELECT Z.NAME,
CASE WHEN Z.CONSEQ_AVAIL >= 3 THEN 1 ELSE 0 END AS YOUR_FLAG
FROM
(
SELECT A.NAME,
SUM(CASE WHEN B.ADATE >= A.ADATE AND B.ADATE <= A.ADATE + 2 THEN 1 ELSE 0 END) AS CONSEQ_AVAIL
FROM
TABL_A A INNER JOIN TABL_A B
ON A.NAME = B.NAME AND A.AVAILABLE = 'Y' AND B.AVAILABLE = 'Y'
GROUP BY A.NAME
) Z;
Due to the complexity of the problem, I have not been able to test it out. If something is really wrong, please let me know and I will be happy to take down my answer.
--Below is My Approch
select Name,
Case WHen Max_Count>=3 Then 1 else 0 end as Presence
from
(
Select Name,MAx(Coun) as Max_Count
from
(
select Name, (count(*) over (partition by Name,Ref_Date)) as Coun from
(
select Name,adate + row_number() over (partition by Name order by Adate desc) as Ref_Date
from temp
where available='Y'
)
) group by Name
);
select name as employee , case when sum(diff) > =3 then 1 else 0 end as presence
from
(select id, name, Available,Adate, lead(Adate,1) over(order by name) as lead,
case when datediff(day, Adate,lead(Adate,1) over(order by name)) = 1 then 1 else 0 end as diff
from table_A
where Available = 'Y') A
group by name;

SQL fill gaps with hold

I've encountered a problem I cannot solve with my knowledge and I haven't found any solutions I understood good enough to solve my problem.
So here is what I try to achieve.
I have a database with the following structure:
node_id, source_time, value
1 , 10:13:15 , 1
2 , 10:13:15 , 1
2 , 10:13:16 , 2
1 , 10:13:19 , 2
1 , 10:13:25 , 3
2 , 10:13:28 , 3
I want to have a sql query to get the following output
time , value1, value2
10:13:15, 1 , 1
10:13:16, 1 , 2
10:13:19, 2 , 2
10:13:25, 3 , 2
10:13:28, 3 , 3
You see, the times are all times that occur from both nodes.
But the values have to be filled in the gaps since node1 has no value for the time :16 and :28.
I got it to the point where I get the 2 columns from one table. That was not the hard part.
SELECT T1.[value], T2.[value]
FROM [db1].[t_value_history] T1, [db1].[t_value_history] T2
WHERE ( T1.node_id = 1 AND T2.node_id = 2)
But the result doesn't look like the way I want it to be.
I found something with COALESCE and another table which holds the previous value. But that looked quiet complicated for such a easy thing.
I guess there is an easy sql solution but I haven't had much time to get into the materia.
I would be happy to get any idea which function to use.
Thanks so far.
Edit: Changed the database, made a mistake on the last line.
Edit2: I am using SQL Server. Sorry for not clarifying this. Also the values are not neccessarily increasing. I just used increasing numbers in this example here.
This works in SQL Server. If you are certain that there is a value for both nodes for the minimum time then you could change the OUTER APPLY to a CROSS APPLY, which would perform better.
WITH times
AS ( SELECT DISTINCT
source_time
FROM dbo.t_value_history
)
SELECT t.source_time ,
n1.value ,
n2.value
FROM times AS t
OUTER APPLY ( SELECT TOP 1
h.value
FROM dbo.t_value_history AS h
WHERE h.node_id = 1
AND h.source_time <= t.source_time
ORDER BY h.source_time DESC
) AS n1
OUTER APPLY ( SELECT TOP 1
h.value
FROM dbo.t_value_history AS h
WHERE h.node_id = 2
AND h.source_time <= t.source_time
ORDER BY h.source_time DESC
) AS n2;
You could use conditional aggregation to get the right set of rows:
select vh.source_time,
max(case when vh.node_id = 1 then value end) as value_1,
max(case when vh.node_id = 2 then value end) as value_2
from db1.t_value_history vh
group by vh.source_time;
If you want to fill in the values, then the best solution is lag() with ignore nulls. Supported by ANSI, but not by SQL Server (which I'm guessing you are using). Your values appear to be increasing. If that is the case, you can use a cumulative max:
select vh.source_time,
max(max(case when vh.node_id = 1 then value end)) over (order by vh.source_time) as value_1,
max(max(case when vh.node_id = 2 then value end) over (order by vh.source_time) as value_2
from db1.t_value_history vh
group by vh.source_time;
In your data, value is increasing, so this works for the data in your example. If that is not the case, a more complex query is needed to fill in the gaps.
This will do it in SQL Server. It is not 'nice' though:
SELECT DISTINCT
T1.source_time,
CASE WHEN T1.node_id = 1 THEN T1.[value] ELSE ISNULL(T2.[value], T3.[value]) END,
CASE WHEN T1.node_id = 1 THEN ISNULL(T2.[value], T3.[Value]) ELSE T1.[value] END
FROM
[db1].[t_value_history] T1
LEFT OUTER JOIN [db1].[t_value_history] T2 ON T2.source_time = T1.source_time
AND T2.node_id <> T1.node_id -- This join looks for a value for the other node at the same time.
LEFT OUTER JOIN [db1].[t_value_history] T3 ON T3.source_time < T1.source_time
AND T3.node_id <> T1.node_id -- If the previous join is empty, this looks for values for the other node at previous times
LEFT OUTER JOIN [db1].[t_value_history] T4 ON T4.source_time > T3.source_time
AND T4.source_time < T1.source_time
AND T4.node_id <> T1.node_id -- This join makes sure there aren't any more recent values
WHERE
T4.node_id IS NULL

ORACLE SQL Returning most recently added record detail

Edit: Solved with a lot of help from Dems, I will post a truncated version of the query which is now working very well!
SELECT
*
FROM
O_PERSONS
/*The below left join returns the most recently added ethnicity classification*/
LEFT JOIN
(SELECT
ROW_NUMBER() OVER (PARTITION BY O_CLASSIFICATIONS.CLA_SUBJECT_ID ORDER BY O_CLASSIFICATIONS.CLA_DATE_NOTIFIED DESC) AS Sequence_ID,
O_CLASSIFICATIONS.CLA_CAT_ID,
O_CLASSIFICATIONS.CLA_SUBJECT_ID
FROM
O_CLASSIFICATIONS
WHERE
O_CLASSIFICATIONS.CLA_SUBJECT_IND = 'P'
AND
O_CLASSIFICATIONS.CLA_TOP_CAT_ID = 'ETHNIC'
AND
O_CLASSIFICATIONS.CLA_CAT_ID <> 'DECLINED'
) ETHNIC
ON ETHNIC.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND ETHNIC.Sequence_ID = 1
/*The below left join returns the most recently added PCG classification*/
LEFT JOIN
(SELECT
ROW_NUMBER() OVER (PARTITION BY O_CLASSIFICATIONS.CLA_SUBJECT_ID ORDER BY O_CLASSIFICATIONS.CLA_DATE_NOTIFIED DESC) AS Sequence_ID,
O_CLASSIFICATIONS.CLA_CAT_ID,
O_CLASSIFICATIONS.CLA_SUBJECT_ID
FROM
O_CLASSIFICATIONS
WHERE
O_CLASSIFICATIONS.CLA_SUBJECT_IND = 'P'
AND
O_CLASSIFICATIONS.CLA_TOP_CAT_ID = 'PRIMARY'
AND
O_CLASSIFICATIONS.CLA_CAT_ID <> 'DECLINED'
) PCG
ON PCG.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND PCG.Sequence_ID = 1
WHERE
O_PERSONS.PER_ID LIKE 'P%'
I am currently trying to write a query which will return certain details for clients on our system. Whilst a client will only have one date of birth, gender, and P ID, they may have multiple ethnicities (as a result of data quality) and also client groups (this is more legitimate as a client's needs may change). However, for these particular items, I am only interested in the most recently added ethnicities or client groups. Below I have written a query which assigns a row number to the ethnicity and to the client group with a plan to return only those which equal 1, as these generally will be the most recent. However, I have run into 2 issues, one of which I can resolve, the other of which I'm not so sure.
First off, some clients will not have an ethnicity or a client group recorded. When running the below query, they return with very odd row numbers (often in the thousands). However, I know in my 'where' that I could specify that a row is returned if client group or ethnicity was empty (as these will be data quality cases which need to be addressed).
The second issue is slightly trickier which I will try to represent with a table below. Please note I have abbreviated the table for ease of input and understanding.
P ID Ethnicity PCG RN E RN P
P1 WB OV 1 2
P1 WI OV 2 1
The above client had two ethnicities, though WB is the most recent (therefore the 1 in Row Number E is correct). However, the client has only had one PCG recorded but the row number returns a 2 in the first row (arguably the row I want to return). I'm not sure why though I guess because ETHNIC.CLA_SUBJECT_ID is joined on O_PERSONS.PER_ID and PER_ID occurs twice, that is why it thinks there is two rows for that particular field. However, even if that is the case, is there a way to force just 1's to appear in one row? Or could I do this a completely different way? Hopefully this query makes some sense, apologies if parts are unclear. Thanks,
SELECT
O_PERSONS.PER_ID as "P ID",
olm_bo.get_per_name(O_PERSONS.PER_ID) as "Full Name",
O_PERSONS.PER_BIRTH_DATE as "Date of Birth",
case
when O_PERSONS.PER_DECEASED_DATE is null then FLOOR(MONTHS_BETWEEN(sysdate,O_PERSONS.PER_BIRTH_DATE)/12)
else FLOOR(MONTHS_BETWEEN(O_PERSONS.PER_DECEASED_DATE,O_PERSONS.PER_BIRTH_DATE)/12)
end as "Age",
O_PERSONS.PER_DECEASED_DATE as "Date Deceased",
olm_bo.get_gender_desc('P', O_PERSONS.PER_GENDER) as "Gender",
CASE
WHEN ETHNIC.CLA_CAT_ID IN ('C1','C2','C3','C4','ABAN','AIND','AOTH','APKN') THEN 'Asian or Asian British'
ELSE NULL
End as "Ethnicity - Top" ,
CASE
WHEN ETHNIC.CLA_CAT_ID IN ('BAFR','D2') THEN 'African'
ELSE NULL
End as "Ethnicity - Detail" ,
CASE
WHEN PCG.CLA_CAT_ID IN ('ASYLUM','REFUGEE') THEN 'Asylum Seeker/Refugee'
ELSE NULL
End as "PCG",
CASE
WHEN PCG.CLA_CAT_ID IN ('ASYLUM','REFUGEE') THEN 'Asylum Seeker/Refugee'
ELSE NULL
End as "PCG - Top",
CASE
WHEN PCG.CLA_CAT_ID IN ('ASYLUM','REFUGEE') THEN 'Asylum Seeker/Refugee'
ELSE NULL
End as "PCG - DETAIL",
to_char(row_number() over(PARTITION BY ETHNIC.CLA_SUBJECT_ID ORDER BY abs(sysdate - ETHNIC.CLA_DATE_NOTIFIED) asc)) as "Row Number E",
to_char(row_number() over(PARTITION BY PCG.CLA_SUBJECT_ID ORDER BY abs(sysdate - PCG.CLA_DATE_NOTIFIED) asc))as "Row Number P"
FROM
O_PERSONS
LEFT JOIN O_CLASSIFICATIONS ETHNIC ON ETHNIC.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND ETHNIC.CLA_SUBJECT_IND = 'P'
AND ETHNIC.CLA_TOP_CAT_ID = 'ETHNIC'
AND ETHNIC.CLA_CAT_ID <> 'DECLINED'
LEFT JOIN O_CLASSIFICATIONS PCG ON PCG.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND PCG.CLA_SUBJECT_IND = 'P'
AND PCG.CLA_TOP_CAT_ID = 'PRIMARY'
AND PCG.CLA_CAT_ID <> 'DECLINED'
WHERE
/*Following line excludes any clients whose is less than 18)*/
O_PERSONS.PER_BIRTH_DATE > trunc(add_months(O_PERSONS.PER_BIRTH_DATE,-216))
AND O_PERSONS.PER_ID LIKE 'P%'
You should apply ROW_NUMBER() before you do the joins.
SELECT
* -- Your calculations here, '*' used for brevity
FROM
O_PERSONS
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY CLA_SUBJECT_ID ORDER BY CLA_DATE_NOTIFIED DESC) AS sequence_id,
*
FROM
O_CLASSIFICATIONS
WHERE
CLA_SUBJECT_IND = 'P'
AND CLA_TOP_CAT_ID = 'ETHNIC'
AND CLA_CAT_ID <> 'DECLINED'
)
ETHNIC
ON ETHNIC.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND ETHNIC.sequence_id = 1
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY CLA_SUBJECT_ID ORDER BY CLA_DATE_NOTIFIED DESC) AS sequence_id,
*
FROM
O_CLASSIFICATIONS
WHERE
CLA_SUBJECT_IND = 'P'
AND CLA_TOP_CAT_ID = 'PRIMARY'
AND CLA_CAT_ID <> 'DECLINED'
)
PCG
ON PCG.CLA_SUBJECT_ID = O_PERSONS.PER_ID
AND PCG.sequence_id = 1
WHERE
/*Following line excludes any clients whose is less than 18)*/
O_PERSONS.PER_BIRTH_DATE > trunc(add_months(O_PERSONS.PER_BIRTH_DATE,-216))
AND O_PERSONS.PER_ID LIKE 'P%'