Calculate a field in Sub Query using fields of Main Query - sql

I'm trying to calculate a lapse in products with a SQL:
SELECT DISTINCT ID_NO, START_DT, END_DT, TERM_NO
FROM DB1.TABLE1
ORDER BY ID_NO, START_DT;
I want to calculate the LAPSE when a ID has a second term. It would be the number of days between the END_DT of Term 1 and START_DT of Term 2.
I can do this easily in excel. But I'm new writing any advanced SQL. Can I get some direction or any sample to achieve this? I tried to google, but I'm having hard time trying to come up with correct search phrases.

You can use lag():
select t1.*,
nullif((start_dt - lag(end_dt) over (partition by id_no order by start_dt)), 0) as lapse
from table1 t1;

You can do it with a simple left self join:
select
t.*, t.start_dt - tt.end_dt as lapse
from tablename t left join tablename tt
on tt.id_no = t.id_no and tt.term_no = 1 and t.term_no = 2
You may change the ON clause to:
on tt.id_no = t.id_no and t.term_no - tt.term_no = 1
if there are other values also in the column termo_no like 1, 2, 3, 4....
See the demo.
Results:
ID_NO | START_DT | END_DT | TERM_NO | LAPSE
:------- | :-------- | :-------- | ------: | ----:
48965787 | 13-DEC-17 | 13-DEC-18 | 1 |
48965787 | 30-DEC-18 | 13-DEC-19 | 2 | 17
57896248 | 17-JAN-18 | 17-JAN-19 | 1 |
57896248 | 17-JAN-19 | 17-JAN-20 | 2 | 0
78515698 | 16-JUN-18 | 16-JUN-19 | 1 |
78515698 | 01-AUG-19 | 16-JUN-20 | 2 | 46

Related

SQL SERVER How to select the latest record in each group? [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 2 years ago.
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:00:20 | 0 |
| 1 | 0:00:40 | 1 |
| 1 | 0:01:00 | 1 |
| 2 | 0:01:20 | 1 |
| 2 | 0:01:40 | 0 |
| 2 | 0:02:00 | 1 |
| 3 | 0:02:20 | 1 |
| 3 | 0:02:40 | 1 |
| 3 | 0:03:00 | 0 |
I have this and I would like to turn it into
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:01:00 | 1 |
| 2 | 0:02:00 | 1 |
| 3 | 0:03:00 | 0 |
Please advise, thank you!
A correlated subquery is often the fastest method:
select t.*
from t
where t.timestamp = (select max(t2.timestamp)
from t t2
where t2.id = t.id
);
For this, you want an index on (id, timestamp).
You can also use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by timestamp desc) as seqnum
from t
) t
where seqnum = 1;
This is typically a wee bit slower because it needs to assign the row number to every row, even those not being returned.
You need to group by id, and filter out through timestamp values descending in order to have all the records returning as first(with value 1) in the subquery with contribution of an analytic function :
SELECT *
FROM
(
SELECT *,
DENSE_RANK() OVER (PARTITION BY ID ORDER BY TimeStamp DESC) AS dr
FROM t
) t
WHERE t.dr = 1
where DENSE_RANK() analytic function is used in order to include records with ties also.

SQL group by but order is important

is there any option to group by items but order of grouping is important?
Let's assume I have table with hardware and it's assigned to some users. And this hardware has some states like broken, ok, service. I want to group this table to have information, how long user had this item, but state is not important.
What I have:
+----+-------+--------+------------+------------+
| id | owner | state | from | to |
+----+-------+--------+------------+------------+
| 1 | ow1 | ok | 01.02.2019 | 04.06.2019 |
| 2 | ow1 | broken | 04.06.2019 | 12.06.2019 |
| 3 | srvc | fixing | 12.06.2019 | 17.06.2019 |
| 4 | ow1 | ok | 17.06.2019 | null | -- null - still has
+----+-------+--------+------------+------------+
But I want to have:
+-------+------------+------------+
| owner | from | to |
+-------+------------+------------+
| ow1 | 01.02.2019 | 12.06.2019 | -- here we have min and max dates before state changed
| srvc | 12.06.2019 | 17.06.2019 |
| ow1 | 17.06.2019 | null | -- null - still has
+-------+------------+------------+
How to write query to achieve this result?
This looks like a gaps and islands problem. One solution is follows:
Mark rows where owner changes (different from previous row) with a value 1
Group all 1s and subsequent 0s together
I usually do this:
WITH cte1 AS (
SELECT *
, CASE WHEN owner = LAG(owner) OVER (PARTITION BY hardware_id ORDER BY [from]) THEN 0 ELSE 1 END AS chg
FROM t
), cte2 AS (
SELECT *
, SUM(chg) OVER (PARTITION BY hardware_id ORDER BY [from]) AS grp
FROM cte1
)
SELECT owner
, hardware_id
, grp
, MIN([from])
, MAX([to])
FROM cte2
GROUP BY owner, hardware_id, grp
I have assumed that you want separate results per every piece of hardware, remove the hardware column if that is not the case.
Demo on db<>fiddle
Try this below option with union all.
SELECT owner,from,to
FROM your_table
WHERE to IS NULL
UNION ALL
SELECT owner,MIN(from),MAX(to)
FROM your_table
WHERE to IS NOT NULL
GROUP BY owner

Oracle SQL [1..*] Relation | Select all rows from [1...] + top-row from [...*] after order by in [...*]

I want to select * from Table TRAINEE + the first REPORT.DATE within the last 2 months.
TRAINEE
+----+----------+
| ID | NAME |
+----+----------+
| 1 | John Doe |
+----+----------+
| 2 | Jane Doe |
+----+----------+
REPORT
+------------+------------+---------------+
| TRAINEE_ID | DATE | REPORT |
+------------+------------+---------------+
| 1 | 01.07.2018 | Not Important |
+------------+------------+---------------+
| 1 | 02.07.2018 | Not Important |
+------------+------------+---------------+
| 1 | 03.07.2018 | Not Important |
+------------+------------+---------------+
| 2 | 02.07.2018 | Not Important |
+------------+------------+---------------+
| 2 | 02.07.2018 | Not Important |
+------------+------------+---------------+
| 2 | 03.07.2018 | Not Important |
+------------+------------+---------------+
Wanted results
+----+----------+--------------+
| ID | NAME | FIRST_REPORT |
+----+----------+--------------+
| 1 | John Doe | 01.07.2018 |
+----+----------+--------------+
| 2 | Jane Doe | 02.07.2018 |
+----+----------+--------------+
I have tried...
This way gave me only one row with a date, rest filled with NULL, because the join only returns one row. Removing the ROWNUM filter will make my query return one row of TRAINEE for each row found in REPORT. So this wouldn't work as well. Where do I have to put the ROWNUM filter?
SELECT
TRAINEE.*,
OUTER_ORDER_DATE.DATE
FROM
Trainee
LEFT JOIN
(
SELECT
DATE,
ID,
ROWNUM as rnum
FROM
(
SELECT
DATE,
ID,
FROM
REPORT INNER_ORDER_DATE
WHERE
INNER_ORDER_DATE.DATE >= add_months(sysdate,-2)
ORDER BY
INNER_ORDER_DATE.DATE ASC
)
WHERE
rnum < 2
) ON OUTER_ORDER_DATE ON OUTER_ORDER_DATE.ID = a.ID
I then tried the following query, that has some wrong syntax; the inner query cannot access the outer query's Trainee.ID.
SELECT
Trainee.*,
(SELECT
DATE
FROM (
SELECT
DATE
FROM
REPORT
WHERE
ID = TRAINEE.ID
AND
DATE >= add_months(sysdate,-2)
ORDER BY
DATE ASC
)
WHERE
ROWNUM < 2
) as DATE
FROM
TRAINEE
What does my query has to look like, to get the wanted result above?
If this question was answered already please link it for me. I have no idea how I could search for this scenario. Thank you.
You should try using this code:
SELECT trainee.id, trainee.name, report.report_date
FROM trainee
JOIN ( SELECT trainee_id, MIN (report_date) AS report_date
FROM report
WHERE report_date >= ADD_MONTHS (SYSDATE, -2)
GROUP BY trainee_id) report
ON (report.trainee_id = trainee.id)
Try this:
select t.id, t.name, min(r.date) firstdate
from trainee t, report r
where t.id = r.id
and r.date >= add_months(sysdate,-2)

SELECT based on multiple fields in MS-SQL

I have a table with 4 columns:
AcctNumb | PeriodEndingDate | WaterConsumption | ReadingType
There are multiple records for each AcctNumb, with the date that each record was recorded.
What I want to do is grab the most recent date, consumption reading, and reading type for each account.
I have tried using MAX(PeriodEndingDate) and GROUP BY AcctNumb, but I would need to aggregate all the other values, and none of the aggregate functions help me for the WaterConsumption, etc.
Can anyone point me in the right direction?
Thanks
EDIT
Here is a sample table
+----------+------------------+------------------+-------------+
| AcctNumb | PeriodEndingDate | WaterConsumption | ReadingType |
+----------+------------------+------------------+-------------+
| 1000 | 2018-03-31 | 122230 | A |
| 1001 | 2018-03-31 | 24850 | A |
| 1002 | 2018-03-31 | 88540 | A |
| 1000 | 2017-12-31 | 123800 | A |
| 1001 | 2017-12-31 | 3000 | E |
+----------+------------------+------------------+-------------+
The ReadingType is whether it's an actual (A) reading, or an estimate (E).
Try this
SELECT
AcctNumb,
PeriodEndingDate,
WaterConsumption,
ReadingType
FROM (SELECT
AcctNumb,
PeriodEndingDate,
WaterConsumption,
ReadingType,
ROW_NUMBER() OVER (PARTITION BY AcctNumb ORDER BY PeriodEndingDate DESC) AS MostrecentRecord
FROM <TableName>) dt
WHERE MostrecentRecord= 1
This can be done using ROW_NUMBER. It has been asked an answered thousands of times but the query is easier to write than find a duplicate.
select *
from
(
select *
, RowNum = ROW_NUMBER() over(partition by AcctNumb order by PeriodEndingDate)
from YourTable
) x
where x.RowNum = 1
SELECT DQ.* FROM
(SELECT *,
Row_Number() OVER (PARTITION BY AcctNumb ORDER BY PeriodEndingDate DESC) AS RN
FROM YourTable
) AS DQ
WHERE DQ.RN = 1

Oracle SQL - Making a one to many join one to one based on logic

Sorry for the broad title, I had a hard time coming up with a brief way of describing what I am looking to do. I have two tables (examples below) that I want to join but under a certain condition.
The main table has a field called "DateVal", the second table has a field called "Day". After joining on field "JoinField" I only want to keep rows where the day value in "DateVal" is less than the value of "Day". However, if this criteria is met for multiple values of "Day" I only want it to keep the first instance.
In the second table below, for JoinField "A" there are three rows, for the first I only want it to return times when the day of the month is between 1-10, the second only with the day of the month is between 11-20, and the last 20-31.
A left or inner join will bring back all values, the only way I can think of to get around this is to do a complete join and only return for min("Day"). Can anyone think of a more efficient way?
Thanks in advance.
Table 1
-------------------------------
| ID | JoinField | DateVal |
-------------------------------
| 1 | A | 01/01/2014 |
| 2 | A | 01/16/2014 |
| 3 | B | 05/20/2013 |
-------------------------------
Table 2
--------------------------------
| JoinField | Day | FieldToAdd |
--------------------------------
| A | 10 | A |
| A | 20 | AA |
| A | 31 | AAA |
| B | 15 | B |
| B | 31 | BB |
--------------------------------
Desired Results
--------------------------------------------
| ID | JoinField | DateVal | FieldToAdd |
--------------------------------------------
| 1 | A | 01/01/2014 | A |
| 2 | A | 01/16/2014 | AA |
| 3 | B | 05/20/2014 | BB |
--------------------------------------------
You can do this in a variety of ways. I think a correlated subquery is the easiest way to express it, but unfortunately, the following doesn't work in Oracle:
select t1.*,
(select *
from (select t2.*
from table2 t2
where t2.day < extract(day from t1.dateval)
order by t2.day desc
) t
where rownum = 1
)
from table1 t1;
You can instead do this with join fancy window functions:
select *
from (select t1.*,
row_number() over (partition by t1.id order by t2.day desc) as seqnum
from table1 t1 left outer join
table2 t2
on t2.day < extract(day from t1.dateval)
) t
where seqnum = 1;