How to create a specific SQL Server stored procedure? [closed] - sql

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have a CONTRACTOR_SCHEDULER table. Letters in columns "Schedule" mean working time (m - 8:00-20:00, n - 20:00-8:00(next day), d - 8:00-8:00(next day), h-day off). (Name, Begin_date) is unique.
Name
Schedule
Begin_date
End_date
John
nhmh
2019-01-01
2019-01-08
John
nnh
2019-01-09
2019-01-25
Kate
dh
2019-01-01
2019-01-07
Kate
mnhdh
2019-01-08
2019-01-14
Mike
nh
2019-01-01
2019-02-01
Mike
mh
2019-02-02
2019-12-31
I need a SQL Server stored procedure that creates new table CONTRACTOR_WORK_DAY of working days using CONTRACTOR_SCHEDULER rows (days off do not appear in table).
Example:
First row - John has schedule nhm. First letter is n so begin_date - 2019-01-01 20:00, End_date - 2019-01-02 08:00. Next letter is h, skip it as a day off. Last letter is m so begin_date - 2019-01-03 08:00, End_date - 2019-01-03 20:00. Repeat schedule until 2019-01-08 - end_date in first table.
Table for the first row of CONTRACTOR_SCHEDULER would be:
Name
Begin_date
End_date
John
2019-01-01 20:00
2019-01-02 08:00
John
2019-01-03 08:00
2019-01-03 20:00
John
2019-01-05 20:00
2019-01-06 08:00
John
2019-01-08 08:00
2019-01-08 20:00
I wrote this in python using some loop over schedule string etc. but can not figure out how to do it in T-SQL for SQL Server.

Yet another option.
Not sure if I agree with the last record of the desired results. I have 2019-01-07 while you have 2019-01-08
Example or dbFiddle
Select A.[Name]
,[Begin_Date] = convert(datetime,left(dateadd(DAY,N ,[Begin_date]),10)+' '+BegTime)
,[End_Date] = convert(datetime,left(dateadd(DAY,N+NxtDay,[Begin_date]),10)+' '+EndTime)
From YourTable A
Cross Apply ( values ( left(replicate(Schedule,10),datediff(DAY,Begin_Date,End_Date)) ) )B(S)
Cross Apply (
Select N=N-1
,Subs=substring(B.S,N,1)
From ( Select Top (len(S)+1) N=Row_Number() Over (Order By (Select Null)) From master..spt_values n1 ) B1
) C
Join ( values ('m','08:00','20:00',0)
,('n','20:00','08:00',1)
,('d','08:00','08:00',1)
) D(SchdCd,BegTime,EndTime,NxtDay) on Subs=SchdCd
Order By [Begin_Date]
Results
Name Begin_Date End_Date
John 2019-01-01 20:00:00.000 2019-01-02 08:00:00.000
John 2019-01-03 08:00:00.000 2019-01-03 20:00:00.000
John 2019-01-05 20:00:00.000 2019-01-06 08:00:00.000
John 2019-01-07 08:00:00.000 2019-01-07 20:00:00.000

WITH
numbers AS
(
-- Generate a table of values (from 0 upwards)
-- Must be at least as long as the longest schedule string
SELECT
ROW_NUMBER() OVER (ORDER BY sv.number) - 1 AS id
FROM
master..spt_values sv
),
pivotted AS
(
-- Create one row for each day in the date range
-- Extract the relevant character from the schedule for each day
SELECT
c.*,
n.id AS date_offset,
SUBSTRING(c.schedule, (n.id % LEN(c.schedule)) + 1, 1) AS schedule_char
FROM
CONTRACTOR_SHERULER c
INNER JOIN
numbers n
ON n.id <= DATEDIFF(DAY, c.begin_date, c.end_date)
)
-- Add a number of days to the begin_date
-- Then add a number of hours based on the current character from the schedule
SELECT
pivotted.*,
DATEADD(
HOUR,
CASE pivotted.schedule_char WHEN 'm' THEN 8
WHEN 'n' THEN 20
WHEN 'd' THEN 8 END,
DATEADD(DAY, date_offset, pivotted.begin_date)
)
AS begin_datetime,
DATEADD(
HOUR,
CASE pivotted.schedule_char WHEN 'm' THEN 20
WHEN 'n' THEN 32
WHEN 'd' THEN 32 END,
DATEADD(DAY, date_offset, pivotted.begin_date)
)
AS end_datetime
FROM
pivotted
WHERE
pivotted.schedule_char <> 'h'
Demo : https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=f6c52e6718329f2ae013dc93edad5d78

Related

SQL Select with grouping and replacing a column

I have a requirement in which I need to retrieve rows in a select query in which I have to get value of END_DATE as EFFECTIVE_DATE -1 DAY for the records with same key (CARD_NBR in this case)
I have tried using it by GROUP by but I am not able to get the desired output. Could someone please help in guiding me ? The record with most recent effective date should have END_DATE as 9999-12-31 only.
Table:
CARD_NBR
SERIEL_NO
EFFECTIVE_DATE
END_DATE
12345
1
2021-01-01
9999-12-31
12345
2
2021-01-25
9999-12-31
12345
3
2021-02-15
9999-12-31
67899
1
2021-03-01
9999-12-31
67899
2
2021-04-02
9999-12-31
67899
3
2021-05-24
9999-12-31
Output:
CARD_NBR
SERIEL_NO
EFFECTIVE_DATE
END_DATE
12345
1
2021-01-01
2021-01-24
12345
2
2021-01-25
2021-02-14
12345
3
2021-02-15
9999-12-31
67899
1
2021-03-01
2021-04-01
67899
2
2021-04-02
2021-05-24
67899
3
2021-05-24
9999-12-31
You can use lead():
select t.*,
lead(effective_date - interval '1 day', 1, effective_date) over (partition by card_nbr order by effective_date) as imputed_end_date
from t;
Date manipulations are highly database-dependent so this uses Standard SQL syntax. You can incorporate this into an update, but the best approach also depends on the database.
SQLite v.3.25 now supports windows function and you can use below code to get your result.
SELECT A.CARD_NBR,
A.SRL_NO,
A.START_DT,
COALESCE(B.START_DT,A.END_DT) AS END_DT
FROM
(
SELECT A.CARD_NBR,
A.SRL_NO,
A.START_DT,
A.END_DT,
ROW_NUMBER() OVER(PARTITION BY A.CARD_NBR ORDER BY A.SRL_NO ASC) RNUM1
FROM T1 A
)A
LEFT JOIN
(
SELECT B.CARD_NBR,
B.SRL_NO,
B.START_DT,
B.END_DT,
ROW_NUMBER() OVER(PARTITION BY B.CARD_NBR ORDER BY B.SRL_NO ASC) RNUM1
FROM T1 B
)B
ON A.CARD_NBR=B.CARD_NBR
AND A.RNUM1+1=B.RNUM1

Join sum to closest timestamp once up to interval cap

I am trying to join a site_interactions table with a store_transactions table. For this, I want that the store_transactions.sales_amount for a given username gets attached to the closest site_interactions.timestamp match, at most one time and up to 7 days of the site_interactions.timestamp variable.
site_interaction table:
username timestamp
John 01.01.2020 15:00:00
John 02.01.2020 11:30:00
Sarah 03.01.2020 12:00:00
store_transactions table:
username timestamp sales_amount
John 02.01.2020 16:00:00 45
John 03.01.2020 16:00:00 70
John 09.01.2020 16:00:00 15
Sarah 02.01.2020 09:00:00 35
Tim 02.01.2020 10:00:00 60
Desired output:
username timestamp sales_amount
John 01.01.2020 15:00:00 NULL
John 02.01.2020 11:30:00 115
Sarah 03.01.2020 12:00:00 NULL
Explanation:
John has 3 entries/transactions in the store_transactions table. The first and the second purchase were realized within the 7 days interval/limit, and the sum of these two transactions (45 + 70 = 115) were attached/joined to the closest and nearest match only once - i.e. to John's second interaction (timestamp = 02.01.2020 11:30:00). John's third transactions was not attached to any site interaction, because it exceeds the 7 days interval (including the time).
Sarah has one transaction realized before her interaction with the site. Thus her sales_amount of 35 was not attached to the site_interaction table.
Last, Tim's transaction was not attached anywhere - because this username does not show in the site_interaction table.
Here a link of the tables: https://rextester.com/RKSUK73038
Thanks in advance!
Below is for BigQuery Standard SQL
#standardSQL
select i.username, i.timestamp,
sum(sales_amount) as sales_amount
from (
select username, timestamp,
ifnull(lead(timestamp) over(partition by username order by timestamp), timestamp_add(timestamp, interval 7 day)) next_timestamp
from `project.dataset.site_interaction`
) i
left join `project.dataset.store_transactions` t
on i.username = t.username
and t.timestamp >= i.timestamp
and t.timestamp < least(next_timestamp, timestamp_add(i.timestamp, interval 7 day))
group by username, timestamp
if to apply to sample data from your question - output is

Find missing record between date range

At the end of an enormous stored procedure (in SQL Server), I've created two CTE. One with some date ranges (with 6 month intervals) and one with some records.
Let's assume i have date ranges on table B from 2020-01-01 to 2010-01-01 (with 6 months intervals)
Start End
----------------------
2020-01-01 | 2020-07-01
... ...
other years here
... ...
2010-01-01 | 2010-07-01
and on table A this situation:
Name Date
-----------------
John 2020-01-01
John 2019-01-01
John 2018-07-01
... ...
Rob 2020-01-01
Rob 2019-07-01
Rob 2018-07-01
... ...
I'm trying to generate a recordset like this:
Name MissingDate
-----------------
John 2019-07-01
... ...
John 2010-01-01
Rob 2019-01-01
... ...
Rob 2010-01-01
I've got the flu and I barely know who I am at this moment, I hope it was clear and if anyone could help me with this I would really appreciate it.
If you want missing dates (which appear to be by month), then generate all available dates and take out the ones you have.
with cte as (
select start, end
from dateranges
union all
select dateadd(month, 1, start), end
from cte
where start < end
)
select n.name, cte.start
from cte cross join
(select distinct name from tablea) n left join
tablea a
on a.date = cte.start and a.name = n.name
where a.date is null;

How duplicate a rows in SQL base on difference between date columns and divided aggregated column per duplicate row?

I have a table with some records about fuel consumption. The important columns in the table are: CONSUME_DATE_FROM and CONSUM_DATE_TO.
I want to calculate average fuel consumption per cars on a monthly basis but some rows are not in the same month. For example some have a three month difference between them and the total of gas per litre is aggregated in a single row.
Now I should find records that have difference more than a month between CONSUME_DATE_FROM and CONSUM_DATE_TO, and duplicate them in current or second table per count of month and divide the total gas per litre between related rows.
I've this table with the following data:
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER
1 100 2018-10-25 2018-12-01 600
2 101 2018-07-19 2018-07-24 100
3 102 2018-12-31 2019-01-01 400
4 103 2018-03-29 2018-05-29 200
5 104 2018-02-05 2018-02-09 50
The expected output table should be as below
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER
1 100 2018-10-25 2018-12-01 200
1 100 2018-10-25 2018-12-01 200
1 100 2018-10-25 2018-12-01 200
2 101 2018-07-19 2018-07-24 100
3 102 2018-12-31 2019-01-01 200
3 102 2018-12-31 2019-01-01 200
4 103 2018-03-29 2018-05-29 66.66
4 103 2018-03-29 2018-05-29 66.66
4 103 2018-03-29 2018-05-29 66.66
5 104 2018-02-05 2018-02-09 50
Or as below
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER DATE_RELOAD_GAS
1 100 2018-10-25 2018-12-01 200 2018-10-01
1 100 2018-10-25 2018-12-01 200 2018-11-01
1 100 2018-10-25 2018-12-01 200 2018-12-01
2 101 2018-07-19 2018-07-24 100 2018-07-01
3 102 2018-12-31 2019-01-01 200 2018-12-01
3 102 2018-12-31 2019-01-01 200 2019-01-01
4 103 2018-03-29 2018-05-29 66.66 2018-03-01
4 103 2018-03-29 2018-05-29 66.66 2018-04-01
4 103 2018-03-29 2018-05-29 66.66 2018-05-01
5 104 2018-02-05 2018-02-09 50 2018-02-01
Can someone please help me out with this query?
I'm using oracle database
Your business rule treats the difference between CONSUME_DATE_FROM and CONSUM_DATE_TO as absolute months. So you expect the difference between 2018-10-25 and 2018-12-01 to be three months whereas the difference in days actually equates to about 1.1 months. So we can't use simple date arithmetic to get your desired output, we need to do some additional massaging of the dates.
The query below implements your desired logic by deriving the first day of the month for CONSUME_DATE_FROM and the last day of the month for CONSUME_DATE_TO, then using ceil() to round the difference up to the nearest whole number of months.
This is calculated in a subquery which is used in the main query with the old connect by level trick to multiply a record by level number of times:
with cte as (
select f.*
, ceil(months_between(last_day(CONSUM_DATE_TO)
, trunc(CONSUME_DATE_FROM,'mm'))) as diff
from fuel_consumption f
)
select cte.id
, cte.VehicleId
, cte.CONSUME_DATE_FROM
, cte.CONSUM_DATE_TO
, cte.GAS_PER_LITER/cte.diff as GAS_PER_LITER
, add_months(trunc(cte.CONSUME_DATE_FROM, 'mm'), level-1) as DATE_RELOAD_GAS
from cte
connect by level <= cte.diff
and prior cte.id = cte.id
and prior sys_guid() is not null
;
"what about if add a additional column "DATE_RELOAD_GAS" that display difference date for similar rows"
From your posted sample it seems like DATE_RELOAD_GAS is the first day of the month for each month bounded by CONSUME_DATE_FROM and CONSUM_DATE_TO. I have amended my solution to implement this rule.
By using connect by level structure with considering to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') as month I was able to resolve as below :
select ID, VehicleId, myMonth, CONSUME_DATE_FROM, CONSUM_DATE_TO,
trunc(GAS_PER_LITER/max(rn) over (partition by ID order by ID),2) as GAS_PER_LITER,
'01.'||substr(myMonth,5,2)||'.'||substr(myMonth,1,4) as DATE_RELOAD_GAS
from
(
with consumption( ID, VehicleId, CONSUME_DATE_FROM, CONSUM_DATE_TO, GAS_PER_LITER ) as
(
select 1,100,date'2018-10-25',date'2018-12-01',600 from dual union all
select 2,101,date'2018-07-19',date'2018-07-24',100 from dual union all
select 3,102,date'2018-12-31',date'2019-01-01',400 from dual union all
select 4,103,date'2018-03-29',date'2018-05-29',200 from dual union all
select 5,104,date'2018-02-05',date'2018-02-09', 50 from dual
)
select ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') myMonth,
VehicleId, c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, GAS_PER_LITER,
row_number() over (partition by ID order by ID) as rn
from dual join consumption c
on c.ID >= 2
group by ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm'), VehicleId,
c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, c.GAS_PER_LITER
connect by level <= c.CONSUM_DATE_TO - c.CONSUME_DATE_FROM + 1
union all
select ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') myMonth,
VehicleId, c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, GAS_PER_LITER,
row_number() over (partition by ID order by ID) as rn
from dual join consumption c
on c.ID = 1
group by ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm'), VehicleId,
c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, c.GAS_PER_LITER
connect by level <= c.CONSUM_DATE_TO - c.CONSUME_DATE_FROM + 1
) q
group by ID, VehicleId, myMonth, CONSUME_DATE_FROM, CONSUM_DATE_TO, GAS_PER_LITER, rn
order by ID, myMonth;
I met an interesting issue that if I consider the join condition in the subquery as c.ID >= 1 query hangs on for huge period of time, so splitted into two parts by union all
as c.ID >= 2 and c.ID = 1
Rextester Demo

Oracle SQL query about Date

I have a database table named availableTimeslot with fields pk, startDate, endDate, e.g.
PK startDate endDate
1. 2017-03-07 09:00:00 2017-03-07 18:00:00
2. 2017-03-07 18:00:00 2017-03-07 21:00:00
3. 2017-03-08 09:00:00 2017-03-08 18:00:00
records starting from 09:00:00 to 18:00:00 indicate it is a morning time slot, while 18:00:00 to 23:00:00 indicating it is a afternoon time slot
storing available timeslot dates (e.g. 2017-03-06, 2017-03-08) which are available for the customer to choose one.
Can I use one query to get exactly 10 available time slots dates starting on the day after the order date?
e.g. if I order a product on 2016-03-07, then the query returns
2017-03-08 09:00:00
2017-03-08 18:00:00
2017-03-09 09:00:00
2017-03-09 18:00:00
2017-03-10 ...
2017-03-11 ...
2017-03-13 ...
as 12 is a public holiday and not in the table.
In short, it returns 10 dates (5 days with each day having am and pm sessions)
remark: the available time slot dates are in order, but may not be consecutive
select available_date
from ( select available_date, row_number() over (order by available_date) as rn
from your_table
where available_date > :order_date
)
where rn <= 5;
:order_date is a bind variable - the date entered by the user/customer through the interface.
Do you want 5 for a single customer?
select ts.*
from (select ts.*
from customer c join
timeslots ts
on ts.date > c.orderdate
where c.customerid = v_customerid
order by ts.date asc
) ts
where rownum <= 5