Subtracting from different rows - sql

I am trying to subtract the startdate from the enddate on different rows, but only for the same code.
For example:
I want to do startdate in row 2 for C002 (2012-07-01) minus enddate in row 1 for C002 (2012-06-30).
The result should be 1 (day) for row 2. No data should be in row 1.
Row 4 should show 1 (day) as well.
How can I go about doing this?
row code startdate enddate
1 C002 2011-07-01 00:00:00.000 2012-06-30 00:00:00.000
2 C002 2012-07-01 00:00:00.000 2013-06-30 00:00:00.000
3 C003 2011-07-01 00:00:00.000 2012-06-30 00:00:00.000
4 C003 2012-07-01 00:00:00.000 2013-06-30 00:00:00.000

select max(row),code,datediff(day,max(startdate),min(enddate)) as ouputtt
from table
group by
code

Try this-
Select x.code, y.startdate-x.enddate
From table1 x left outer join table1 y on
X.code=Y.code
Where
X.enddate<y.startdate

You could use this query, which adds the requested value as an additional column:
select row, code, startdate, enddate,
datediff('d', lag(enddate) over (partition by code order by row1), startdate) df
from mytable

Related

create a row index column beginning at -1

I need to create a row index column that begins at -1 so i can query the previous day's balance. My current query:
select TRANSDATE, sum(convert(float,AMOUNTMST-SETTLEAMOUNTMST)) as Balance
from [AX2cTestStage].[dbo].[CUSTTRANS_V]
group by TRANSDATE
order by TRANSDATE asc
TRANSDATE Balance
2019-04-12 00:00:00.000 -22591.47
2019-04-15 00:00:00.000 -394.95
2019-04-25 00:00:00.000 -1776
2019-04-26 00:00:00.000 -11973.84
2019-04-29 00:00:00.000 -24230.16
2019-05-02 00:00:00.000 -10695.39
This is what i need:
TRANSDATE Balance Row Index
2019-04-12 00:00:00.000 -22591.47 -1
2019-04-15 00:00:00.000 -394.95 0
2019-04-25 00:00:00.000 -1776 1
2019-04-26 00:00:00.000 -11973.84 2
2019-04-29 00:00:00.000 -24230.16 3
2019-05-02 00:00:00.000 -10695.39 4
I have tried to declare a variable as the row index
declare #row_num as int = -1
select TRANSDATE, sum(convert(float,AMOUNTMST-SETTLEAMOUNTMST)) as Balance, #row_num += 1 as Row Index
from [AX2cTestStage].[dbo].[CUSTTRANS_V]
group by TRANSDATE
i receive this error:
A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations.
after declaring a variable for each field I still receives errors. Is there an easier way to accomplish this? thanks
You can use ROW_NUMBER(). For example:
select
TRANSDATE,
sum(convert(float,AMOUNTMST-SETTLEAMOUNTMST)) as Balance,
row_number() over(order by TRANSDATE) - 2 as Row Index
from [AX2cTestStage].[dbo].[CUSTTRANS_V]
group by TRANSDATE

Select next subsequent change of certain column in a new column

I have a table with a unique index on Contracts of Customers that live in Houses. I want to know the days per house how long it takes when someone moves out (Contract end date) and a new contracts starts. For that I want to know what the first next contract will be in that house, but on the same row as the old contract for a (potentially different) customer.
This how the table currently looks like, I select the top 10 here:
SELECT TOP 10
PMCCONTRACT.ACCOUNTNUM --Customer
,PMCCONTRACT.RENTALOBJECTID --House
,PMCCONTRACT.CONTRACTID --Contract & Unique index of the table
,PMCCONTRACT.VALIDFROM --Contract Start Date
,PMCCONTRACT.VALIDTO --Contract End Date
FROM PMCCONTRACT
Then this rolls out:
ACCOUNTNUM RENTALOBJECTID CONTRACTID VALIDFROM VALIDTO
101852 2488 HC000001 1994-03-01 00:00:00.000 NULL
101136 2489 HC000002 1920-01-01 00:00:00.000 NULL
101352 2491 HC000003 1996-09-16 00:00:00.000 NULL
100687 2492 HC000004 1984-11-01 00:00:00.000 NULL
105160 2499 HC000005 1975-05-02 00:00:00.000 2018-01-31 00:00:00.000
102821 2501 HC000006 1997-09-16 00:00:00.000 NULL
100731 2506 HC000007 1920-01-01 00:00:00.000 2018-11-15 00:00:00.000
102797 2508 HC000008 1998-02-01 00:00:00.000 NULL
102155 2512 HC000009 1981-09-01 00:00:00.000 NULL
102563 2515 HC000010 1965-10-17 00:00:00.000 2017-06-30 00:00:00.000
And what I want is that based on the RENTALOBJECTID it will show what the First Next contract on that house was (so it is important that the CONTRACTID remains unique in this table).
Below is the code I use to get it, however, it shows all the following contract changes for that specific RENTALOBJECTID (House).
SELECT --TOP 1000
PMCCONTRACT.CONTRACTID
,PMCCONTRACT.RENTALOBJECTID
,PMCCONTRACT.VALIDFROM
,PMCCONTRACT.VALIDTO
,P2.CONTRACTID AS 'FirstNextContractId'
,P2.VALIDFROM
,P2.VALIDTO
FROM PMCCONTRACT
LEFT JOIN PMCCONTRACT P2
ON PMCCONTRACT.RENTALOBJECTID = P2.RENTALOBJECTID
LEFT JOIN
(SELECT
RENTALOBJECTID,
MAX(CONTRACTID) AS CONTRACTID
FROM PMCCONTRACT
GROUP BY RENTALOBJECTID) X ON X.CONTRACTID = P2.CONTRACTID
WHERE P2.VALIDFROM > PMCCONTRACT.VALIDTO
This is what I get when I select only ContractID HC000028, it shows 2 rows, while I want it to show only the first row.
CONTRACTID RENTALOBJECTID VALIDFROM VALIDTO FirstNextContractId VALIDFROM2 VALIDTO2
HC000028 75 1995-01-01 00:00:00.000 2016-04-30 00:00:00.000 HC009990 2016-05-01 00:00:00.000 2018-11-25 00:00:00.000 --<< Only row I want to show
HC000028 75 1995-01-01 00:00:00.000 2016-04-30 00:00:00.000 HC025218 2018-11-26 00:00:00.000 1900-01-01 00:00:00.000 --Too far in the future
Kind regards,
Igor
It looks like a simple LEAD window function is enough. It returns the next row, as defined by partitioning and ordering clauses.
SELECT TOP 10
PMCCONTRACT.ACCOUNTNUM --Customer
,PMCCONTRACT.RENTALOBJECTID --House
,PMCCONTRACT.CONTRACTID --Contract & Unique index of the table
,PMCCONTRACT.VALIDFROM --Contract Start Date
,PMCCONTRACT.VALIDTO --Contract End Date
,LEAD(CONTRACTID) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextContractID
,LEAD(VALIDFROM) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextVALIDFROM
,LEAD(VALIDTO) OVER (PARTITION BY RENTALOBJECTID ORDER BY VALIDFROM) AS NextVALIDTO
FROM PMCCONTRACT
;

sql-query that change all validTo dates to the next validFrom date minus one Day

I have to modify a big pricelist table so that there is only one valid price for every article.
Sometimes the sales employees insert new prices and forgot to change the old infinite validTo dates.
So I have to write a sql-query to change all validTo dates to the next validFrom date minus one day, when the validTo date has infinite validity (9999-12-31).
But I have no idea how can i reach this with only SQL (Oracle 12).
anr price validFrom validTo
1 447.1 2015-06-01 9999-12-31 <
1 447.2 2015-06-16 2015-06-16
1 447.3 2015-06-17 2015-06-17
1 447.4 2015-06-22 2015-06-22
1 447.5 2015-07-06 9999-12-31 <
1 395.0 2015-07-20 2015-07-20
1 447.6 2015-08-03 9999-12-31 <
1 447.7 2015-08-17 9999-12-31 <
1 447.8 2015-08-24 9999-12-31 <
1 395.0 2015-09-07 2015-09-07
1 450.9 2015-11-15 9999-12-31 < no change because it is the last entry
after updating the the table, the result should look like
anr price validFrom validTo
1 447.1 2015-06-01 2015-06-15 <
1 447.2 2015-06-16 2015-06-16
1 447.3 2015-06-17 2015-06-17
1 447.4 2015-06-22 2015-06-22
1 447.5 2015-07-06 2015-07-19 <
1 395.0 2015-07-20 2015-07-20
1 447.6 2015-08-03 2015-08-16 <
1 447.7 2015-08-17 2015-08-23 <
1 447.8 2015-08-24 2015-09-06 <
1 395.0 2015-09-07 2015-09-07
1 450.9 2015-11-15 9999-12-31 <
In order to update an end date you can simply select the minimum of all higher start dates.
update mytable upd
set enddate = coalesce(
(
select min(startdate) - 1
from mytable later
where later.startdate > upd.startdate
and later.anr = upd.anr -- same product
), date'9999-12-31') -- coalesce for the case there is no later record
where enddate = date'9999-12-31';
I have taken anr to be the product id. If it isn't then change the statement accordingly.
Oracle provides an analytic function LEAD that references the current-plus-n-th record given a sort criterion. This function may serve the purpose of selecting the proper date value in an update statement as follows ( let test_prices be the table name, ppk its PK ):
update test_prices p
set p.validTo = (
select ps.vtn
from (
select lead ( p1.validFrom, 1 ) over ( order by p1.validFrom ) - 1 vtn
, ppk
from test_prices p1
) ps
where ps.ppk = p.ppk
)
where to_char(p.validTo, 'YYYY') = '9999'
and p.validFrom != ( select max(validFrom) from test_prices )
;
UPDATE VALID_DATES v
SET validTo = (
SELECT validTo
FROM (
SELECT anr,
validFrom,
COALESCE(
LEAD( validFrom - 1, 1 ) OVER ( PARTITION BY anr ORDER BY validFrom ),
validTo
) AS validTo
FROM valid_dates
) u
WHERE v.anr = u.anr
AND v.validFrom = u.validFrom
)
WHERE validTo = DATE '9999-12-31';
There are two possibilities:
1. Explicit time spans
price validFrom validTo
90.99 2016-01-01 9999-12-31
80.00 2016-01-16 2016-01-17
The first price would be valid both before January 16 and after January 17, whereas the second price was only valid on two days in January.
It would then be a very bad idea to change the first validTo.
2. Implicit time spans
price validFrom
90.99 2016-01-01
80.00 2016-01-16
90.99 2016-01-18
This data represents the same as in the explicit time spans example. The first price is valid before January 16, then the second price is valid until January 17, and afterwards the next price (which equals the first price again) is valid. Here you don't need an EndDate, because it's implicit. Of course the first price is only valid until January 15, because from January 16 there is another price valid (record #2).
So: Either remove the EndDate column completely or let it untouched. Don't simply update it, as you have intended. If you updated your records to next date minus one, you would actually hold data redundantly, which might lead to problems later.

Subtition of cursor for combining tables with time periods

I have to combine two tables into one but I have to take validation dates into consideriation. For instance having two tables:
Address
ID AddressValue ValidFrom ValidTo
----------- --------------- ----------------------- -----------------------
1 Pink Street 2010-01-01 00:00:00.000 2010-01-20 00:00:00.000
2 Yellow Street 2010-01-20 00:00:00.000 2010-02-28 00:00:00.000
Phone
ID PhoneValue ValidFrom ValidTo
----------- ------------ ----------------------- -----------------------
1 123456789 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000
2 987654321 2010-01-16 00:00:00.000 2010-01-31 00:00:00.000
I need to do combine them into new one:
NewSystem
ID NewPhone NewAddress ValidFrom ValidTo Version
----------- ----------- --------------- ----------------------- ----------------------- -------
1 123456789 Pink Street 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000 4
2 NULL Pink Street 2010-01-15 00:00:00.000 2010-01-16 00:00:00.000 3
3 987654321 Pink Street 2010-01-16 00:00:00.000 2010-01-20 00:00:00.000 2
4 987654321 Yellow Street 2010-01-20 00:00:00.000 2010-01-31 00:00:00.000 1
5 NULL Yellow Street 2010-01-31 00:00:00.000 2010-02-28 00:00:00.000 0
The idea is quite simple. I create periods based on dates and then query each table in subqueries. I pasted my solution here: http://pastebin.com/cdKePA9X.
Right now I am trying to get rid of the cursor but I failed. I tried to use CTE but without success. Maybe someone of you faced similar problem or know how to combine these tables into one without using cursor. I pasted the 'create table' scripts here: http://pastebin.com/BeRspb6K.
Thank you in advanced.
First, construct new date ranges by merging the date ranges from the source tables. Second, for each new date range, lookup the valid data in the source tables.
WITH
old_ranges(d1,d2) AS (
SELECT ValidFrom,ValidTo FROM #Address UNION
SELECT ValidFrom,ValidTo FROM #Phone
),
new_ranges(d1,d2) AS (
SELECT d,LEAD(d) OVER(ORDER BY d)
FROM (
SELECT DISTINCT d
FROM old_ranges
UNPIVOT(d FOR dx IN (d1,d2)) p
) t
)
SELECT
ROW_NUMBER() OVER (ORDER BY d1) AS ID,
NewPhone,
NewAddress,
d1 AS ValidFrom,
d2 AS ValidTo
FROM new_ranges
OUTER APPLY (
SELECT PhoneValue AS NewPhone
FROM #Phone
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x1
OUTER APPLY (
SELECT AddressValue AS NewAddress
FROM #Address
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x2
WHERE d2 IS NOT NULL

Select dates in ranges list

I have table with records, each row contains DATETIME column which describes when row was loaded into table. And I have CTE which creates ranges (count is vary) like one below.
first_day_of_month last_day_of_moth
-------------------------------------------------------
2013-12-01 00:00:00.000 2013-12-31 23:59:59.000
2013-11-01 00:00:00.000 2013-12-31 23:59:59.000
2013-10-01 00:00:00.000 2013-12-31 23:59:59.000
2013-09-01 00:00:00.000 2013-12-31 23:59:59.000
2013-08-01 00:00:00.000 2013-12-31 23:59:59.000
Question: Now I want to select minimal DATETIME value from first table for each range created in CTE. I am absolutely have no idea how to do it. Any ideas/links are appreciated.
For example, it should looks like:
2013-12-10
2013-11-20
2013-10-05
2013-09-13
2013-08-06
UPD: Date or datetime - it is no matter
UPD2: I found that I can join my tables using condition like:
INNER JOIN source_monthes_dates ON
(load_timestamp >= first_day_of_month AND load_timestamp <= last_day_of_moth)
but actually I do not know how to acquire only first date of period.
You can use this query which uses ROW_NUMBER() to get the minimum. ranges is the result of your CTE, table1 is the other table where you have dates.
select x.somedate
from
(select t.somedate,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
where x.rownumber = 1
SQL Fiddle demo
If you want to get all the ranges and include only those days that match ranges and display null for others, you can join ranges once more:
select ranges.first_day_of_month, ranges.last_day_of_moth, x.somedate
from
ranges
left join
(select t.somedate, r.first_day_of_month, r.last_day_of_moth,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
on x.first_day_of_month = ranges.first_day_of_month and x.last_day_of_moth = ranges.last_day_of_moth
where isnull(x.rownumber, 1) = 1
SQL Fiddle demo