For loop in Microsoft SQL Server - sql

I have a data which has 3 different numbers of "Equipement" and each "Equipement" has different contract date ( start_date and end_date).
Screen Data:
I want to write a script which I can say that for every "Equipement" If the first line of "end_date" match the second line of "start_date" in days, so I should do ("start_date" - 1 day) in the second line AS a new_end_date for the first line.
I've made an attempt, but for just the two first lines ( not generalized):
SELECT[Ref]
,[Equipement]
,[start_date]
,[end_date]
,CASE WHEN DATEDIFF(day, (SELECT [end_date] FROM [DWDiagnostics].[dbo].[Test1] WHERE [Ref] = 1290), (SELECT [start_date] FROM [DWDiagnostics].[dbo].[Test1] WHERE [Ref] = 1380)) < 0 THEN DATEADD(dd, -1, [start_date]) ELSE [end_date]
END AS [new_end_date]
FROM [DWDiagnostics].[dbo].[Test1]
Here's a screen of the result I want
SQL code for the Data ==>
DECLARE #Test TABLE
(
Ref VARCHAR(10),
Equipment VARCHAR(10),
start_date DATE,
end_date DATE
)
INSERT INTO #Test VALUES ('1290','9999','2014-03-01','2016-04-16')
INSERT INTO #Test VALUES ('1380','9999','2016-04-01','2018-05-17')
INSERT INTO #Test VALUES ('2000','9999','2018-05-01','2020-06-27')
INSERT INTO #Test VALUES ('2900','9999','2020-06-01','2021-06-29')
INSERT INTO #Test VALUES ('1556','8888','2016-01-01','2017-02-27')
INSERT INTO #Test VALUES ('1876','8888','2017-02-01','2018-04-26')
INSERT INTO #Test VALUES ('2897','8888','2018-04-01','2020-03-30')
INSERT INTO #Test VALUES ('2653','7777','2017-09-01','2018-10-14')
INSERT INTO #Test VALUES ('4536','7777','2018-10-01','2019-11-13')
INSERT INTO #Test VALUES ('2987','7777','2019-11-01','2020-12-27')
INSERT INTO #Test VALUES ('2776','7777','2020-12-01','2021-11-30')
SELECT * FROM #Test;

Thanks for posting sample data and tables structures. Makes this so much easier to work on the problem. This should work based on your explanation of the issue. However, some of the new_end_date values you posted as desired do not match up to your description. For example, with Equipment 9999 you have the second start_date as 4/1/2016 but in your desired output you show 3/30. The day before 4/1 is 3/31. There are some other examples with dates like that in your desired output that are slightly off the day before.
DECLARE #Test TABLE
(
Ref VARCHAR(10),
Equipment VARCHAR(10),
start_date DATE,
end_date DATE
)
INSERT INTO #Test VALUES ('1290','9999','2014-03-01','2016-04-16')
INSERT INTO #Test VALUES ('1380','9999','2016-04-01','2018-05-17')
INSERT INTO #Test VALUES ('2000','9999','2018-05-01','2020-06-27')
INSERT INTO #Test VALUES ('2900','9999','2020-06-01','2021-06-29')
INSERT INTO #Test VALUES ('1556','8888','2016-01-01','2017-02-27')
INSERT INTO #Test VALUES ('1876','8888','2017-02-01','2018-04-26')
INSERT INTO #Test VALUES ('2897','8888','2018-04-01','2020-03-30')
INSERT INTO #Test VALUES ('2653','7777','2017-09-01','2018-10-14')
INSERT INTO #Test VALUES ('4536','7777','2018-10-01','2019-11-13')
INSERT INTO #Test VALUES ('2987','7777','2019-11-01','2020-12-27')
INSERT INTO #Test VALUES ('2776','7777','2020-12-01','2021-11-30')
select *
, new_end_date = isnull(dateadd(day, -1, lead(start_date, 1)over(partition by Equipment order by start_date)), end_date)
from #Test
ORDER BY Equipment desc
, start_date

Related

Calculate monthly income from weekly

I need help. Trying to solve the following problem: A table stores information about the weekly sales of a product.
Need to set up automatic conversion of weekly values ​​to monthly values. Sales in transitional weeks (part of the week in one month, part in another) must be distributed on weekdays (excluding weekends - Sat, Sun).
For example, sales for the week ended 03/05/2013 should be distributed as follows: 2 days for February, 3 days for March.
The result of solving the problem is the SQL query that will automatically receive the converted data in the "Result Table" format (month; amount) according to the entered parameter MonthNumber (month number). If the parameter is not specified, the entire table is displayed.
drop table if exists #Test
create table #Test
(
sales_date date,
payment_sum real
)
insert into #Test values ('26.02.2013', 312.00)
insert into #Test values ('05.03.2013', 833.00)
insert into #Test values ('12.03.2013', 225.00)
insert into #Test values ('19.03.2013', 453.00)
insert into #Test values ('26.03.2013', 774.00)
insert into #Test values ('02.04.2013', 719.00)
insert into #Test values ('09.04.2013', 136.00)
insert into #Test values ('23.04.2013', 157.00)
insert into #Test values ('30.04.2013', 850.00)
insert into #Test values ('07.05.2013', 940.00)
insert into #Test values ('14.05.2013', 933.00)
insert into #Test values ('21.05.2013', 422.00)
insert into #Test values ('28.05.2013', 952.00)
insert into #Test values ('04.06.2013', 136.00)
insert into #Test values ('11.06.2013', 701.00)
;
I started by trying to weed out weekends. But how to understand which of the weeks should be divided between adjacent months?
SELECT * FROM #Test
WHERE ((DATEPART(dw, sales_date) + ##DATEFIRST) % 3) NOT IN (6, 7)
I want to receive this:
MonthNumber = 0
|Month|Incom|
|:----|:----|
|01 |1100 |
|02 |1120 |
|03 |1488 |
|04 |6112 |
|05 |7300 |
|06 |1360 |
|07 |8800 |
|08 |1400 |
|09 |1300 |
|10 |5070 |
|11 |3020 |
|12 |7800 |
MonthNumber = 1
|01 |1100 |
Build a calendar table, including a column that maps each day to the appropriate week and reporting month. Join that to allocate the weekly sales to the weekdays, something like:
drop table if exists #Test
drop table if exists #calendar
set dateformat dmy
create table #Test
(
sales_date date,
payment_sum real
)
insert into #Test values ('26.02.2013', 312.00)
insert into #Test values ('05.03.2013', 833.00)
insert into #Test values ('12.03.2013', 225.00)
insert into #Test values ('19.03.2013', 453.00)
insert into #Test values ('26.03.2013', 774.00)
insert into #Test values ('02.04.2013', 719.00)
insert into #Test values ('09.04.2013', 136.00)
insert into #Test values ('23.04.2013', 157.00)
insert into #Test values ('30.04.2013', 850.00)
insert into #Test values ('07.05.2013', 940.00)
insert into #Test values ('14.05.2013', 933.00)
insert into #Test values ('21.05.2013', 422.00)
insert into #Test values ('28.05.2013', 952.00)
insert into #Test values ('04.06.2013', 136.00)
insert into #Test values ('11.06.2013', 701.00);
with q as
(
select top 365 row_number() over (order by (select null))-1 i
from sys.messages
), d as
(
select dateadd(day,i,'20130101') dt
from q
)
select d.dt,
dateadd(day,3-datepart(dw,dt),dt) week_start,
case when datepart(dw,dt) in (6,7) then 0 else 1 end is_weekday,
datepart(dw,dt) day_of_week,
month(dt) month_num
into #calendar
from d
select month_num, sum(payment_sum/5)
from #test s
join #calendar c
on s.sales_date = c.week_start
where c.is_weekday = 1
group by month_num
outputs
month_num
----------- ----------------------
2 312.000007629395
3 2428.80004119873
4 1378.20001411438
5 3587.00000762939
6 836.999988555908
The following will extend sales date 6 days and then aggregate n/5 by month excluding weekends.
Example
Select D =EOMonth(D)
,Sales=sum(Payment_sum/5)
From #Test A
Cross Apply ( values ( dateadd(DAY, 0,sales_date) )
,( dateadd(DAY,-1,sales_date) )
,( dateadd(DAY,-2,sales_date) )
,( dateadd(DAY,-3,sales_date) )
,( dateadd(DAY,-4,sales_date) )
,( dateadd(DAY,-5,sales_date) )
,( dateadd(DAY,-6,sales_date) )
)D(D)
Where datename(WEEKDAY,D) not in ('Saturday','Sunday')
Group By EOMonth(D)
Results
D Sales
2013-02-28 645.20
2013-03-31 2383.20
2013-04-30 1430.60
2013-05-31 3328.60
2013-06-30 755.40

How to get the latest last 2 inserted records from the table

CREATE TABLE `testskm`(
`mem_id` NUMBER(5) NOT NULL,
`mem_sal` NUMBER(5) NOT NULL);
insert into `testskm` values (1,100);
insert into `testskm` values (1,200);
insert into `testskm` values (2,350);
insert into testskm values (2,150);
insert into testskm values (3,12);
insert into testskm values (1,300);
insert into testskm values (2,50);
insert into testskm values (3,13);
insert into testskm values (3,14);
insert into testskm values (3,15);
i have insert statements for mem_id 1,2, & 3. I want to get the last 2 inserted records in the table for all mem_id.
I have the tried the below code , but its giving me only records based on the mem_sal as i used the order by..
select * from(
select
mem_id, mem_sal,
--max(sysdate) over (partition by mem_id) latest,
rank() over( partition BY mem_id order by mem_sal ) RISK_ORDER
from testskm)
where RISK_ORDER <= 2
i want output of these inserted records:
insert into `testskm` values (1,200);
insert into `testskm` values (1,300);
insert into `testskm` values (2,150);
insert into `testskm` values (2,50);
insert into `testskm` values (3,14);
insert into `testskm` values (3,15);
SQL tables represent unordered sets. There is no "last two rows" unless a column specifies the ordering. Your table has no such column.
You can define one. In MySQL, this looks like:
create table `testskm` (
testskm_id int auto_increment primary key,
`mem_id` int NOT NULL,
`mem_sal` int NOT NULL
);
Then you can use row_number():
select ts.*
from (select ts.*, row_number() over (partition by mem_id order by testskm_id desc) as seqnum
from testskm ts
) ts
where seqnum <= 2;
Here is a db<>fiddle.

Reference table from subquery in Oracle

I have simplified my tables but essentially I have a table of accounts that have a cycle_no and end date. This end date is always set to the first of the month but I need to get the real end date by looking in the calendar details table. The real end date is the next date for this cycle_no.
To create the simplified tables and enter a few rows of data:
CREATE TABLE DATA_OWNER.ACCOUNT
(
ACCNO NUMBER(4),
CYCLE_NO NUMBER(4),
ENDDATE DATE
);
CREATE TABLE DATA_OWNER.CALENDAR_DETAILS
(
CALENDAR_DT DATE,
BILL_CYCL_NO NUMBER(4)
);
INSERT INTO calendar_Details
VALUES
('18/DEC/2017',
17);
INSERT INTO calendar_Details
VALUES
('23/DEC/2017',
20);
INSERT INTO calendar_Details
VALUES
('18/JAN/2018',
17);
INSERT INTO calendar_Details
VALUES
('23/JAN/2018',
20);
INSERT INTO calendar_Details
VALUES
('20/FEB/2018',
17);
INSERT INTO calendar_Details
VALUES
('21/FEB/2018',
20);
INSERT INTO account
VALUES
(1, 17, '01/DEC/2107');
INSERT INTO account
VALUES
(2, 20, '01/DEC/2107');
If we run this query though we get "ACC". "ENDDATE": invalid identifier:
SELECT accno, cycle_no, enddate, actual_date
FROM account acc
JOIN
(
SELECT MIN(calendar_dt) actual_date
FROM calendar_details cal
WHERE calendar_dt > acc.enddate
)
ON acc.cycle_no = cal.bill_cycl_no;
Can anyone give us some pointers on the best way to achieve this please?
You cannot refer to outer table references in a subquery in the FROM. Just use a correlated subquery:
SELECT accno, cycle_no, enddate,
(SELECT MIN(cal.calendar_dt) as actual_date
FROM calendar_details cal
WHERE cal.calendar_dt > acc.enddate AND acc.cycle_no = cal.bill_cycl_no
) as actual_date
FROM account acc;

Computing difference in rows for all except consecutive days?

I have a table as follows. I want to compute the difference in dates (in seconds) between consecutive rows according to the following:
If the dates differ by more than a day, then we go ahead and compute the difference
If the dates differ by more than a day and there are consecutive days with the value 84600 for the second date, then I want to first combine the dates before taking a difference
I am currently doing a self-join to handle the first case but am not sure if there is a good way to handle the second case. Any suggestion?
The following also gives an example:
CREATE TABLE #TEMP(Person VARCHAR(100), StartTime Datetime, TotalSeconds INT)
INSERT INTO #TEMP VALUES('A', '2013-02-20', 49800); -- We want to take the difference with the next row in this case
INSERT INTO #TEMP VALUES('A', '2013-02-25', 3000); -- Before taking the difference, I want to first merge the next four rows because 5th March is followed by three days with the value 86400
INSERT INTO #TEMP VALUES('A', '2013-03-05', 2100);
INSERT INTO #TEMP VALUES('A', '2013-03-06', 86400);
INSERT INTO #TEMP VALUES('A', '2013-03-07', 86400);
INSERT INTO #TEMP VALUES('A', '2013-03-08', 86400);
INSERT INTO #TEMP VALUES('A', '2013-03-09', 17100);
INSERT INTO #TEMP VALUES('B', '2012-04-24', 22500);
INSERT INTO #TEMP VALUES('B', '2012-04-26', 600);
INSERT INTO #TEMP VALUES('B', '2012-04-27', 10500);
INSERT INTO #TEMP VALUES('B', '2012-04-29', 41400);
INSERT INTO #TEMP VALUES('B', '2012-05-04', 86100);
SELECT *
FROM #TEMP
DROP TABLE #TEMP
The following handles the second case:
select Person, MIN(StartTime) as StartTime, MAX(StartTime) as maxStartTime
from (SELECT *,
dateadd(d, - ROW_NUMBER() over (partition by person order by StartTime), StartTime) as thegroup
FROM #TEMP t
) t
group by Person, thegroup
It groups all the time periods for a person, with consecutive dates collapsing into a single period (with a begin and end time). The trick is to assign a sequence number, using row_number() and then take the difference from StartTime. This difference is constant for a group of consecutive dates -- hence the outer group by.
You can use a with statement to put this into your query and then get the difference that you desire between consecutive rows.

Comparing value to that of previous row depending on different field

I have SQL which outputs rows of a date time stamp and a status change flag (0 or 1)
I need to get the timespan from the first record, where the flag will be 0, to when the status flag changes to 1, ignore records when the flag is still at 1, then get the time span after it changes back to 0 till the last record. The status change flag may flip between 0 and 1 any number of times.
So i need to be able to compare the status change flag to the previous row, and decide whether I need to keep accumulating the difference in the date time stamps.
I have been looking into writing a cursor but keep reading about how cursors are horribly inefficient.
Hopes this make any sense.
DECLARE #test TABLE ([group] int,t DateTime,[status] bit)
INSERT INTO #test values (1,'20130101 11:11:11',0)
INSERT INTO #test values (1,'20130101 11:11:12',0)
INSERT INTO #test values (1,'20130101 11:11:13',0)
INSERT INTO #test values (1,'20130101 11:11:14',1)
INSERT INTO #test values (1,'20130101 11:11:15',1)
INSERT INTO #test values (1,'20130101 11:11:16',1)
INSERT INTO #test values (1,'20130101 11:11:17',0)
INSERT INTO #test values (1,'20130101 11:11:18',0)
INSERT INTO #test values (1,'20130101 11:11:19',0)
Select [Group],MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
,DateDiff(ss,MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
) as Seconds
from #test t where Status=0
group by [group]
I think something like this will work. But I might need more info on the table structure
WITH FirstFlag(FlagType, FlagTime)
AS
(
SELECT
FlagType
, min(DateCreated) as FlagTime
FROM TheTable
WHERE Flag = 0
)
, SecondFlag(FlagTime1, FlagTime2)
AS
(
SELECT
F.FlagTime as FlagTime
, min(T.DateCreated) as FlagTime
FROM TheTable as T
INNER JOIN FirstFlag as F
ON T.FlagType = F.FlagType
WHERE Flag = 1
AND T.DateCreated > F.FlagTime
)
SELECT datediff(min, FlagTime1, FlagTime2)
FROM SecondFlag