need to repeat the previous transaction at place of null - sql

My store procedure:
SELECT B.ETADATE,
a.NAME,
a.CATEGORY ,
a.TYPE,
a.STOCK
FROM
(SELECT TO_CHAR(TO_DATE(FROM_DATE,'DD-MM-YYYY HH24:MI:SS')) AS "FROM_DATE",
NAME AS "NAME",
CATEGORY AS "CATEGORY",
TYPE AS "TYPE",
BALANCE AS "BALANCE"
FROM VW_NET_STOCK_POSITION
) a,
(SELECT dt + LEVEL AS ETADate
FROM
(SELECT TRUNC (TO_DATE ('01-09-2018 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), 'MM') - 1 AS dt
FROM DUAL
) D
CONNECT BY LEVEL <= sysdate - dt
) B
WHERE a.FROM_DATE(+) = B.ETADATE
ORDER BY ETADate;
and my output is:
but I want that where 'null' then there should be replaced with the previous transaction.
Like this,
want output like this:

Do not use + for outer joins! The solution to your problem is LAG(. . . IGNORE NULLS). But the query can be cleaned up.
I would suggest writing this as:
WITH dates as (
SELECT (dt + level - 1) as etadate
FROM (SELECT TRUNC(DATE '2018-09-01', 'MM') - 1 AS dt
FROM DUAL
) d
CONNECT BY LEVEL <= sysdate - dt + 1 -- to get today's date
)
SELECT d.etadate,
COALESCE(nsp.name, LAG(nsp.name IGNORE NULLS) OVER (ORDER BY d.etadate)) as name,
COALESCE(nsp.category, LAG(nsp.category IGNORE NULLS) OVER (ORDER BY d.etadate)) as category,
COALESCE(LAG(nsp.type IGNORE NULLS) OVER (ORDER BY d.etadate), type) as type,
COALESCE(LAG(nsp.stock IGNORE NULLS) OVER (ORDER BY d.etadate), stock) as stock
FROM dates d LEFT JOIN
VW_NET_STOCK_POSITION nsp
ON d.etadate = TRUNC(nsp.from_date)
ORDER BY d.ETADate;

You might use LAST_VALUE analytical function with IGNORE NULLS option to make the SQL as follows :
SELECT ETADATE,
LAST_VALUE(NAME) IGNORE NULLS OVER (ORDER BY etadate) as NAME,
LAST_VALUE(CATEGORY) IGNORE NULLS OVER (ORDER BY etadate) as CATEGORY,
LAST_VALUE(TYPE) IGNORE NULLS OVER (ORDER BY etadate ) as TYPE,
LAST_VALUE(BALANCE) IGNORE NULLS OVER (ORDER BY etadate ) as STOCK
FROM
(SELECT TO_CHAR(TO_DATE(FROM_DATE,'DD-MM-YYYY HH24:MI:SS')) AS "FROM_DATE",
NAME AS "NAME",
CATEGORY AS "CATEGORY",
TYPE AS "TYPE",
BALANCE AS "BALANCE"
FROM VW_NET_STOCK_POSITION
) a LEFT OUTER JOIN
(SELECT dt + LEVEL AS ETADate
FROM
(SELECT TRUNC (TO_DATE ('01-09-2018 00:00:00', 'DD-MM-YYYY HH24:MI:SS'), 'MM') - 2
AS dt
FROM DUAL
) D
CONNECT BY LEVEL <= trunc(sysdate) - dt
) B
ON ( a.FROM_DATE = B.ETADATE )
ORDER BY ETADate;
and prefer using ANSI JOIN standard.
SQL Fiddle Demo

Related

How to get max date among others ids for current id using BigQuery?

I need to get max date for each row over other ids. Of course I can do this with CROSS JOIN and JOIN .
Like this
WITH t AS (
SELECT 1 AS id, rep_date FROM UNNEST(GENERATE_DATE_ARRAY('2021-09-01','2021-09-09', INTERVAL 1 DAY)) rep_date
UNION ALL
SELECT 2 AS id, rep_date FROM UNNEST(GENERATE_DATE_ARRAY('2021-08-20','2021-09-03', INTERVAL 1 DAY)) rep_date
UNION ALL
SELECT 3 AS id, rep_date FROM UNNEST(GENERATE_DATE_ARRAY('2021-08-25','2021-09-05', INTERVAL 1 DAY)) rep_date
)
SELECT id, rep_date, MAX(rep_date) OVER (PARTITION BY id) max_date, max_date_over_others FROM t
JOIN (
SELECT t.id, MAX(max_date) max_date_over_others FROM t
CROSS JOIN (
SELECT id, MAX(rep_date) max_date FROM t
GROUP BY 1
) t1
WHERE t1.id <> t.id
GROUP BY 1
) USING (id)
But it's too wired for huge tables. So I'm looking for the some simpler way to do this. Any ideas?
Your version is good enough I think. But if you want to try other options - consider below approach. It might looks more verbose from first look - but should be more optimal and cheaper to compare with your version with cross join
temp as (
select id,
greatest(
ifnull(max(max_date_for_id) over preceding_ids, '1970-01-01'),
ifnull(max(max_date_for_id) over following_ids, '1970-01-01')
) as max_date_for_rest_ids
from (
select id, max(rep_date) max_date_for_id
from t
group by id
)
window
preceding_ids as (order by id rows between unbounded preceding and 1 preceding),
following_ids as (order by id rows between 1 following and unbounded following)
)
select *
from t
join temp
using (id)
Assuming your original table data just has columns id and dt - wouldn't this solve it? I'm using the fact that if an id has the max dt of everything, then it gets the second-highest over the other id values.
WITH max_dates AS
(
SELECT
id,
MAX(dt) AS max_dt
FROM
data
GROUP BY
id
),
with_top1_value AS
(
SELECT
*,
MAX(dt) OVER () AS max_overall_dt_1,
MIN(dt) OVER () AS min_overall_dt
FROM
max_dates
),
with_top2_values AS
(
SELECT
*,
MAX(CASE WHEN dt = max_overall_dt_1 THEN min_overall_dt ELSE dt END) AS max_overall_dt2
FROM
with_top1_value
),
SELECT
*,
CASE WHEN dt = max_overall_dt1 THEN max_overall_dt2 ELSE max_overall_dt1 END AS max_dt_of_others
FROM
with_top2_values

Windows functions orderen by date when some dates doesn't exist

Suppose this example query:
select
id
, date
, sum(var) over (partition by id order by date rows 30 preceding) as roll_sum
from tab
When some dates are not present on date column the window will not consider the unexistent dates. How could i make this windowns aggregation including these unexistent dates?
Many thanks!
You can join a sequence containing all dates from a desired interval.
select
*
from (
select
d.date,
q.id,
q.roll_sum
from unnest(sequence(date '2000-01-01', date '2030-12-31')) d
left join ( your_query ) q on q.date = d.date
) v
where v.date > (select min(my_date) from tab2)
and v.date < (select max(my_date) from tab2)
In standard SQL, you would typically use a window range specification, like:
select
id,
date,
sum(var) over (
partition by id
order by date
range interval '30' day preceding
) as roll_sum
from tab
However I am unsure that Presto supports this syntax. You can resort a correlated subquery instead:
select
id,
date,
(
select sum(var)
from tab t1
where
t1.id = t.id
and t1.date >= t.date - interval '30' day
and t1.date <= t.date
) roll_sum
from tab t
I don't think Presto support window functions with interval ranges. Alas. There is an old fashioned way to doing this, by counting "ins" and "outs" of values:
with t as (
select id, date, var, 1 as is_orig
from t
union all
select id, date + interval '30 day', -var, 0
from t
)
select id.*
from (select id, date, sum(var) over (partition by id order by date) as running_30,
sum(is_org) as is_orig
from t
group by id, date
) id
where is_orig > 0

SQL query to interpolate timestamp basing on adjacent records

I use Oracle and have the following table:
create table test as
select to_date('02.05.2017 00:00', 'DD.MM.YYYY HH24:MI') as DT, 203.4 as VAL from dual union all
select to_date('02.05.2017 01:00', 'DD.MM.YYYY HH24:MI') as DT, 206.7 as VAL from dual union all
select to_date('02.05.2017 02:00', 'DD.MM.YYYY HH24:MI') as DT, 208.9 as VAL from dual union all
select to_date('02.05.2017 03:00', 'DD.MM.YYYY HH24:MI') as DT, 211.8 as VAL from dual union all
select to_date('02.05.2017 04:45', 'DD.MM.YYYY HH24:MI') as DT, 212.3 as VAL from dual union all
select to_date('02.05.2017 06:15', 'DD.MM.YYYY HH24:MI') as DT, 214.5 as VAL from dual union all
select to_date('02.05.2017 08:12', 'DD.MM.YYYY HH24:MI') as DT, 215 as VAL from dual
;
DT VAL
----------------------------
02.05.2017 00:00 203.4
02.05.2017 01:00 206.7
02.05.2017 02:00 208.9
02.05.2017 03:00 211.8
02.05.2017 04:45 212.3
02.05.2017 06:15 214.5
02.05.2017 08:12 215
I need to write SQL query (or PL/SQL procedure) so as to interpolate the value (VAL) for any timestamp (DT), assuming that value is increasing constantly between two neighbour records in table (ie. linear interpolation).
Example:
When I select value for timestamp '02.05.2017 00:00', query should give me 203.4 (record with such timestamp exists in table)
When I select value for timestamp '02.05.2017 00:30', query should give me 205.05 (record with such timestamp doesn't exist in table, so we take a 'middle' between 203.4 and 206.7, because wanted timestamp is in the middle between their timestamps)
When I select value for timestamp '02.05.2017 00:15', query should give me 204.225 (a 'fourth part' between 203.4 and 206.7)
What is the simplest way to solve such task?
I think this one is even more compact, it avoids the self-join:
WITH t AS
(SELECT DT, VAL,
LEAD(DT, 1, DT) OVER (ORDER BY DT) AS FOLLOWING_DT,
LEAD(VAL, 1, VAL) OVER (ORDER BY VAL) AS FOLLOWING_VAL
FROM TEST)
SELECT VAL + (FOLLOWING_VAL - VAL) * ( (:timestamp - DT) / (FOLLOWING_DT - DT) )
FROM t
WHERE :timestamp BETWEEN DT AND FOLLOWING_DT;
I think the easiest way to accomplish this would be with a PL/SQL function similar to the following:
create or replace function get_val(dt in date) return number
is
cursor exact_cursor(dt in date) is
select t.val from t where t.dt = exact_cursor.dt;
cursor earlier_cursor(dt in date) is
select t.dt, t.val from t where t.dt < earlier_cursor.dt
order by t.dt desc;
cursor later_cursor(dt in date) is
select t.dt, t.val from t where t.dt > later_cursor.dt
order by t.dt asc;
result number;
factor number;
earlier_rec earlier_cursor%rowtype;
later_rec later_cursor%rowtype;
begin
open exact_cursor(dt);
fetch exact_cursor into result;
close exact_cursor;
if result is not null then
return result;
end if;
-- No exact match. Perform linear interpolation between values
-- from earlier and later records.
open earlier_cursor(dt);
fetch earlier_cursor into earlier_rec;
close earlier_cursor;
open later_cursor(dt);
fetch later_cursor into later_rec;
close later_cursor;
-- Return NULL unless earlier and later records found
if earlier_rec.dt is null or later_rec.dt is null then
return null;
end if;
factor := (dt - earlier_rec.dt) / (later_rec.dt - earlier_rec.dt);
result := earlier_rec.val + factor * (later_rec.val - earlier_rec.val);
return result;
end;
/
You don't need cursors for this. You need to find the two closest records, one above and one below and then take their average. Something like this:
select :timestamp,
(case when lower.val = upper.val then val
else lower.val + (upper.val - lower.val) * ( (:timestamp - lower.dt) / (upper.dt - lower.dt) )
end) as imputed_val
from (select *
from (select dt, val
from t
where dt <= :timestamp
order by dt desc
)
where rownum = 1
) lower cross join
(select *
from (select dt, val
from t
where dt >= :timestamp
order by dt asc
)
where rownum = 1
) upper;
You can easily achieve this by using Analytical functions. Hope this belo snippet helps.
SELECT *
FROM
(SELECT c.*,
((lead(c.dt) over(order by 1) -c.dt)*24*60) lead_val,
(lead(c.val) over(order by 1 )-c.val) lead_val_diff,
lead(c.val) over(order by 1 ) - (((lead(c.dt) over(order by 1)- to_date('&enter_date','DD.MM.YYYY HH24:MI'))*24*60)/((lead(c.dt) over(order by 1) -c.dt)*24*60))*(lead(c.val) over(order by 1 )-c.val)polated_val
FROM
(SELECT so_test.*,
row_number() over(order by dt) rn1
FROM SO_TEST
)c
WHERE ((c.rn1) IN
(SELECT MAX(RN)
FROM
(SELECT ROW_NUMBER() OVER(ORDER BY A.DT ) RN,
A.*
FROM SO_TEST A
WHERE A.DT <= '&enter_date'
)B
))
OR (c.rn1 IN
(SELECT MAX(RN)+1
FROM
(SELECT ROW_NUMBER() OVER(ORDER BY A.DT ) RN,
A.*
FROM SO_TEST A
WHERE A.DT <= '&enter_date'
)B
))
)d
WHERE d.polated_val IS NOT NULL;

Oracle SQL: Get the First Value of each complex Group/Partition

How can I in Oracle with SQL retrieve for a table each first Column A,B, in case column B changes the value ordered by A???
Assume I have a table with date and value:
DATE;VALUE
01-2015;1
02-2015;1
01-2016;2
01-2016;2
01-2017:1
So what I want now, is each first line once the value changes (based on certain orderning here DATE) so from this set I want:
DATE;VALUE
01-2015;1
01-2016;2
01-2017:1
Now I cannot use a simply GROUP BY VALUE, because the value can flip back again (in this case to 1 in 2015 and 2017) and MIN(DATECOL) GROUP BY VALUECOL will not report this 2017.
So I was looking into Analytical functions something like:
SELECT FIRST_VALUE(DATECOL),FIRST_VALUE(VALUECOL) OVER (PARTITION BY
VALUECOL ORDER BY DATECOL) FROM DATATABLE
But I cannot get this working!
Tabibtosan makes this easy:
with table1 as (select to_date('01/01/2015', 'dd/mm/yyyy') dt, 1 val from dual union all
select to_date('01/02/2015', 'dd/mm/yyyy') dt, 1 val from dual union all
select to_date('01/01/2016', 'dd/mm/yyyy') dt, 2 val from dual union all
select to_date('01/01/2016', 'dd/mm/yyyy') dt, 2 val from dual union all
select to_date('01/01/2017', 'dd/mm/yyyy') dt, 1 val from dual)
-- end of mimicking a table "table1" with data in it. See sql below:
select min(dt) dt,
val
from (select dt,
val,
dense_rank() over (order by dt)
- dense_rank() over (partition by val order by dt) grp
from table1)
group by val,
grp;
DT VAL
---------- ----------
01/01/2015 1
01/01/2016 2
01/01/2017 1
I think LAG() is the appropriate function, along with some other logic:
select t.*
from (select t.*, lag(value) over (order by date) as prev_value
from datatable t
) t
where prev_value is null or prev_value <> value;
The only issue with your data is that the rows are not unique. This can cause a problem, because sorting in databases is not stable (that is, two rows can be in either order). Hopefully, in your actual data, the dates are unique or you have another id you can add to the order by to make the sort stable.
One brute force way of doing this is:
with dt as (
select dt.*, rownum as rn
from datatable dt
)
select t.*
from (select dt.*, lag(value) over (order by date, rn) as prev_value
from datatable dt
) t
where prev_value is null or prev_value <> value;

Oracle SQL: fill in dates between dates

I have the following basic script, this shows me some current capacity loading in my production schedule.
select rl.duedate, rl.reservation_no resnr, qty
from gps_reservation_load rl
where rl.reservation_no in ('179459','179460','179461')
and rl.work_center_no in ('ALIN','AVD5','AVD9')
But, I want to show the DUEDATE as a date range from the sysdate to end of the year, e.g.
I have the following that gives me that range but, how can I combine the scripts to give me the result above?
select trunc(sysdate + rownum) dt
from DUAL connect by rownum < (to_date('01-JAN-2016', 'dd-mon-yyyy') - trunc(sysdate))
You just need to use outer join.
SELECT duedate_generated,reservation_no, qty, dt
from
(SELECT
trunc(sysdate + rownum) AS duedate_generated
FROM DUAL
connect by rownum < (to_date('01-JAN-2016', 'dd-mon-yyyy') - trunc(sydsate)
) d
OUTER JOIN
(select
duedate, reservation_no resnr, qty
from gps_reservation_load
where
reservation_no in ('179459','179460','179461')
and work_center_no in ('ALIN','AVD5','AVD9')
) r1
ON (d.duedate_generated = r1. due date)