Overstock qty query SQL server - sql-server

This question is a little modified based on the recommendation from Eric:
What I want to achieve: In order to determine an appropriate obsolescence provision level I would need to calculate an overstock qty per material item and per warehouse (we have several warehouses). The overstock qty will then be the basis for obsolescence calculations.
In order to achieve this I need to compare the current stock level in our warehouses with the consumption of stock in the past 5 years. However first of all I would like to aggregate the stock qty for all of our warehouses (should be a separate column "StockAll" in output table). Important is that I donĀ“t want one unique entry per item code. E.g. item code ABC is on stock in warehouse1 (5pc) and warehouse2 (5Pc) then the new column "StockAll" should contain 10pc for item code ABC and should pop up twice in the output table namely for warehouse1 and warehouse2.
The overstock (should be a new column "OverstockAll" in output table) is the difference "Stock All" and SH2.BAAS_qty_sold (the qty coming from the union to be found in the code below). Last but not least I need to allocate the Overstock qty being shown in output column "OverstockAll" by using each warehouse share on the correspondent material item number. I.e. based on the example above for item ABC. Stock all shows 10 PC and assume that result of Overstock all is 6PC for ABC. Then I would like to have separate column "overstock Local" in the output table which shows for warehouse1 3pc as overstock and the same for warehouse2 (each warehouse has 5pc on stock for material ABC and hence each warehouse should get allocated 50% of the "overstockall, hence 6*0,5)
Is there anybody whith an idea how to achieve this?
select
bds.[Warehouse code]
,bds.[Item code]
,bds.[Free text 2]
,bds.[Current Stock]
,bds.[unit cost]
,bds.[unit cost currency]
,SH2.BAAS_qty_sold
,case
when bds.[Current Stock]-SH2.BAAS_qty_sold >0
then bds.[Current Stock]-SH2.BAAS_qty_sold
else 0
end as overstock
from [BAAS_PowerBI].[dbo].[BAAS_Daily_Stock] as bds
Left join
(select
sum(SH.Bill_qty) as BAAS_qty_sold
, SH.MM_Material
from
-- Union starts here
(SELECT
Bill_BillingDate
, MM_Material
, Bill_qty
FROM dbo.BAAS_Bill_done
UNION All
SELECT
Bill_BillingDate
, MM_Material
, Bill_qty
FROM [BAAS_PowerBI].dbo.[GreatPlains Sales History 2012-102017]) SH
where
sh.Bill_BillingDate > dateadd(year, -5, getdate())
group by
SH.MM_Material) SH2
on bds.[Item code]= SH2.[mm_material]
Thank you in advance
Br
c.

Is this what you're looking for? The CASE expression calculates a difference if the current stock is greater than the 5_yr_consumption. Otherwise it returns a 0.
Also, I added a table alias for readability, and changed the JOIN from a LEFT to an INNER, since you won't have any overstock on items you don't have sales records for.
SELECT
bds.[Warehouse code]
,bds.[Item code]
,bds.[Free text 2]
,bds.[Current Stock]
,bds.[unit cost]
,bds.[unit cost currency]
,SH2.BAAS_qty_sold AS 5_yr_consumption
,CASE
WHEN bds.[Current Stock] > SH2.BAAS_qty_sold
THEN bds.[Current Stock] - SH2.BAAS_qty_sold
ELSE 0
END AS Overstock
FROM
BAAS_PowerBI.dbo.BAAS_Daily_Stock AS bds
JOIN
(
SELECT
BAAS_qty_sold = SUM(SH.Bill_qty)
,SH.MM_Material
FROM
-- Union starts here
(
SELECT
Bill_BillingDate
,MM_Material
,Bill_qty
FROM
dbo.BAAS_Bill_done
UNION ALL
SELECT
Bill_BillingDate
,MM_Material
,Bill_qty
FROM
BAAS_PowerBI.dbo.[GreatPlains Sales History 2012-102017]
) AS SH
WHERE
SH.Bill_BillingDate > DATEADD(YEAR, -5, GETDATE())
GROUP BY
SH.MM_Material
) AS SH2
ON
bds.[Item code] = SH2.mm_material
If all the boss cares about is inventory that actually HAS an overstock, just add this:
WHERE
CASE
WHEN bds.[Current Stock] > SH2.BAAS_qty_sold
THEN bds.[Current Stock] - SH2.BAAS_qty_sold
ELSE 0
END > 0

Related

Calculating Days Between Dates in Separate Rows For Same UnitID

I am trying to calculate the time a commercial real estate space sits vacant. I have move-in & move-out dates for each tenant that has occupied that unit. It is easy to calculate the occupied time of each tenant as that data is within the same row. However, I want to calculate the vacant time: the time between move-out of the previous tenant and move-in of the next tenant. These dates appear in separate rows.
Here is a sample of what I have currently:
SELECT
uni_vch_UnitNo AS UnitNumber,
uty_vch_Code AS UnitCode,
uty_int_Id AS UnitID, tul_int_FacilityId AS FacilityID,
tul_dtm_MoveInDate AS Move_In_Date,
tul_dtm_MoveOutDate AS Move_Out_Date,
DATEDIFF(day, tul_dtm_MoveInDate, tul_dtm_MoveOutDate) AS Occupancy_Days
FROM TenantUnitLeases
JOIN units
ON tul_int_UnitId = uni_int_UnitId
JOIN UnitTypes
ON uni_int_UnitTypeId = uty_int_Id
WHERE
tul_int_UnitId = '26490'
ORDER BY tul_dtm_MoveInDate ASC
Is there a way to assign an id to each row in chronological, sequential order and find the difference between row 2 move-in date less row 1 move-out date and so on?
Thank you in advance for the help.
I can't really tell which tables provide which columns for your query. Please alias and dot-qualify them in the future.
If you're using SQL 2012 or later, you've got LEAD and LAG functions which do exactly what you want: bring a "leading" or "lagging" row into a current row. See if this works (hopefully it should at least get you started):
SELECT
uni_vch_UnitNo AS UnitNumber,
uty_vch_Code AS UnitCode,
uty_int_Id AS UnitID, tul_int_FacilityId AS FacilityID,
tul_dtm_MoveInDate AS Move_In_Date,
tul_dtm_MoveOutDate AS Move_Out_Date,
DATEDIFF(day, tul_dtm_MoveInDate, tul_dtm_MoveOutDate) AS Occupancy_Days
, LAG(tul_dtm_MoveOutDate) over (partition by uni_vch_UnitNo order by tul_dtm_MoveOutDate) as Previous_Move_Out_Date
, DATEDIFF(day,LAG(tul_dtm_MoveOutDate) over (partition by uni_vch_UnitNo order by tul_dtm_MoveOutDate),tul_dtm_MoveInDate) as Days_Vacant
FROM TenantUnitLeases
JOIN units
ON tul_int_UnitId = uni_int_UnitId
JOIN UnitTypes
ON uni_int_UnitTypeId = uty_int_Id
WHERE
tul_int_UnitId = '26490'
ORDER BY tul_dtm_MoveInDate ASC
Just comparing a value from the current row with a value in the previous row is functionality provided by the lag() function.
Try this in your query:
select...
tul_dtm_MoveInDate AS Move_In_Date,
tul_dtm_MoveOutDate AS Move_Out_Date,
DateDiff(day, Lag(tul_dtm_MoveOutDate,1) over(partition by uty_vch_Code, tul_int_FacilityId order by tul_dtm_MoveInDate), tul_dtm_MoveInDate) DaysVacant,
...
This needs a window function or correlated sub query. The goal is to provide the previous move out date for each row, which is in turn a function of that row. The term 'window' in this context means to apply an aggregate function over a smaller range than the whole set.
If you had a function called GetPreviousMoveOutDate, the parameters would be the key to filter on, and the ranges to search within the filter. So we would pass the UnitID as the key and the MoveInDate for this row, and the function should return the most recent MoveOutDate for the same unit that is before the passed in date. By getting the max date before this one, we will ensure we get only the previous occupancy if it exists.
To use a sub-query in ANSI-SQL you just add the select as a column. This should work on MS-SQL as well as other DB platforms; however, it requires using aliases for the table names so they can be referenced in the query more than once. I've updated your sample SQL with aliases using the AS syntax, although it looks redundant to your table naming convention. I added a uni_dtm_UnitFirstAvailableDate to your units table to handle the first vacancy, but this can be a default:
SELECT
uni.uni_vch_UnitNo AS UnitNumber,
uty.uty_vch_Code AS UnitCode,
uty.uty_int_Id AS UnitID, tul_int_FacilityId AS FacilityID,
tul.tul_dtm_MoveInDate AS Move_In_Date,
tul.tul_dtm_MoveOutDate AS Move_Out_Date,
DATEDIFF(day, tul.tul_dtm_MoveInDate, tul.tul_dtm_MoveOutDate) AS Occupancy_Days,
-- select the date:
(SELECT MAX (prev_tul.tul_dtm_MoveOutDate )
FROM TenantUnitLeases AS prev_tul
WHERE prev_tul.tul_int_UnitId = tul.tul_int_UnitId
AND prev_tul.tul_dtm_MoveOutDate > tul.tul_dtm_MoveInDate
AND prev_tul.tul_dtm_MoveOutDate is not null
) AS previous_moveout,
-- use the date in a function:
DATEDIFF(day, tul.tul_dtm_MoveInDate,
ISNULL(
(SELECT MAX (prev_tul.tul_dtm_MoveOutDate )
FROM TenantUnitLeases AS prev_tul
WHERE prev_tul.tul_int_UnitId = tul.tul_int_UnitId
AND prev_tul.tul_dtm_MoveOutDate > tul.tul_dtm_MoveInDate
AND prev_tul.tul_dtm_MoveOutDate is not null
) , uni.uni_dtm_UnitFirstAvailableDate) -- handle first occupancy
) AS Vacancy_Days
FROM TenantUnitLeases AS tul
JOIN units AS uni
ON tul.tul_int_UnitId = uni.uni_int_UnitId
JOIN UnitTypes AS uty
ON uni.uni_int_UnitTypeId = uty.uty_int_Id
WHERE
tul.tul_int_UnitId = '26490'
ORDER BY tul.tul_dtm_MoveInDate ASC

T-SQL - Get last as-at date SUM(Quantity) was not negative

I am trying to find a way to get the last date by location and product a sum was positive. The only way i can think to do it is with a cursor, and if that's the case I may as well just do it in code. Before i go down that route, i was hoping someone may have a better idea?
Table:
Product, Date, Location, Quantity
The scenario is; I find the quantity by location and product at a particular date, if it is negative i need to get the sum and date when the group was last positive.
select
Product,
Location,
SUM(Quantity) Qty,
SUM(Value) Value
from
ProductTransactions PT
where
Date <= #AsAtDate
group by
Product,
Location
i am looking for the last date where the sum of the transactions previous to and including it are positive
Based on your revised question and your comment, here another solution I hope answers your question.
select Product, Location, max(Date) as Date
from (
select a.Product, a.Location, a.Date from ProductTransactions as a
join ProductTransactions as b
on a.Product = b.Product and a.Location = b.Location
where b.Date <= a.Date
group by a.Product, a.Location, a.Date
having sum(b.Value) >= 0
) as T
group by Product, Location
The subquery (table T) produces a list of {product, location, date} rows for which the sum of the values prior (and inclusive) is positive. From that set, we select the last date for each {product, location} pair.
This can be done in a set based way using windowed aggregates in order to construct the running total. Depending on the number of rows in the table this could be a bit slow but you can't really limit the time range going backwards as the last positive date is an unknown quantity.
I've used a CTE for convenience to construct the aggregated data set but converting that to a temp table should be faster. (CTEs get executed each time they are called whereas a temp table will only execute once.)
The basic theory is to construct the running totals for all of the previous days using the OVER clause to partition and order the SUM aggregates. This data set is then used and filtered to the expected date. When a row in that table has a quantity less than zero it is joined back to the aggregate data set for all previous days for that product and location where the quantity was greater than zero.
Since this may return multiple positive date rows the ROW_NUMBER() function is used to order the rows based on the date of the positive quantity day. This is done in descending order so that row number 1 is the most recent positive day. It isn't possible to use a simple MIN() here because the MIN([Date]) may not correspond to the MIN(Quantity).
WITH x AS (
SELECT [Date],
Product,
[Location],
SUM(Quantity) OVER (PARTITION BY Product, [Location] ORDER BY [Date] ASC) AS Quantity,
SUM([Value]) OVER(PARTITION BY Product, [Location] ORDER BY [Date] ASC) AS [Value]
FROM ProductTransactions
WHERE [Date] <= #AsAtDate
)
SELECT [Date], Product, [Location], Quantity, [Value], Positive_date, Positive_date_quantity
FROM (
SELECT x1.[Date], x1.Product, x1.[Location], x1.Quantity, x1.[Value],
x2.[Date] AS Positive_date, x2.[Quantity] AS Positive_date_quantity,
ROW_NUMBER() OVER (PARTITION BY x1.Product, x1.[Location] ORDER BY x2.[Date] DESC) AS Positive_date_row
FROM x AS x1
LEFT JOIN x AS x2 ON x1.Product=x2.Product AND x1.[Location]=x2.[Location]
AND x2.[Date]<x1.[Date] AND x1.Quantity<0 AND x2.Quantity>0
WHERE x1.[Date] = #AsAtDate
) AS y
WHERE Positive_date_row=1
Do you mean that you want to get the last date of positive quantity come to positive in group?
For example, If you are using SQL Server 2012+:
In following scenario, when the date going to 01/03/2017 the summary of quantity come to 1(-10+5+6).
Is it possible the quantity of following date come to negative again?
;WITH tb(Product, Location,[Date],Quantity) AS(
SELECT 'A','B',CONVERT(DATETIME,'01/01/2017'),-10 UNION ALL
SELECT 'A','B','01/02/2017',5 UNION ALL
SELECT 'A','B','01/03/2017',6 UNION ALL
SELECT 'A','B','01/04/2017',2
)
SELECT t.Product,t.Location,SUM(t.Quantity) AS Qty,MIN(CASE WHEN t.CurrentSum>0 THEN t.Date ELSE NULL END ) AS LastPositiveDate
FROM (
SELECT *,SUM(tb.Quantity)OVER(ORDER BY [Date]) AS CurrentSum FROM tb
) AS t GROUP BY t.Product,t.Location
Product Location Qty LastPositiveDate
------- -------- ----------- -----------------------
A B 3 2017-01-03 00:00:00.000

SQL Server : optimize the efficiency with many joins relationship

I have SQL Server code which takes long time to run the result. In the past, it took 15 minutes. But recently, might as a result of accumulated sales data, it took 2 hours to get the result!!
Therefore, I would like to get some advice regarding how to optimize the code:
The code structure is simple: just to get the sales sum for different regions for different time periods and for each SKU. (I have deleted some code here is to find the different SKU for each materials without size).
Many thanks in advance for your help.
The main code structure is as below, since it is almost the same, so I just give the first 2 paragraphs as example:
SELECT SKU from [MATINFO]
-- Global Sales History Qty - All the years
LEFT JOIN
(
SELECT SKU,SUM([SALES Qty]) as [Global Sales History Qty - All the years]
from dbo.[SALES]
where [PO] IS NOT NULL
group by SKU
)histORy
on MATINFO.[SKU]=histORy.[SKU]
-- Global Sales History Qty - Past 2 years
LEFT JOIN
(
SELECT (
SELECT SKU,SUM([SALES Qty]) as [Global Sales History Qty - All the years]
from dbo.[SALES]
where [PO] IS NOT NULL
group by SKU
/* date range */
and ([ORDER DATE] = '2015.11' OR [ORDER DATE] = '2015.12' or [ORDER DATE] like '%2015%' OR [ORDER DATE] like '%2016%' )
group by SKU
)histORy2
on MATINFO.[SKU]=histORy2.[SKU]
--Global Sales History Qty - Past 1 years
......SIMILAR TO THE CODE STRUCTURE AS ABOVE
The most likely cause of the poor performance is using string for dates and possibly the lack if as adequate indexes.
like '%2015%'
Using double-ended wildcards with like results in full table scans so subqueries are scanning the whole table each time you serach for a different date range. Using temp tables will not solve the underlying issues.
[added later]
Another facet of your original query structure might reduce the number of scans you need of the data - by using "conditional aggregates"
e.g. here is a condensed version of your original query
SELECT
SKU
FROM [MATINFO]
-- Global Sales History Qty - All the years
LEFT JOIN (SELECT
SKU
, SUM([SALES Qty]) AS [Global Sales History Qty - All the years]
FROM dbo.[SALES]
WHERE [PO] IS NOT NULL
GROUP BY
SKU) histORy ON MATINFO.[SKU] = histORy.[SKU]
-- Global Sales History Qty - Past 2 years
LEFT JOIN (SELECT
SKU
, SUM([SALES Qty]) AS [Global Sales History Qty - Past 2 years]
FROM dbo.[SALES]
WHERE [PO] IS NOT NULL
/* date range */
AND [ORDER DATE] >= '20151101' AND [ORDER DATE] < '20161101'
GROUP BY
SKU) histORy2 ON MATINFO.[SKU] = histORy2.[SKU]
That requires a 2 complete passes of the data in dbo.[SALES], but if you were to use a case expression inside the SUM() function you need only one pass of the data (in this example)
SELECT
SKU
, SUM([SALES Qty]) AS [Qty_all_years]
, SUM(CASE
WHEN [ORDER DATE] >= '20151101' AND [ORDER DATE] < '20161101'
THEN [SALES Qty]
END) AS [Qty_past_2_years]
FROM dbo.[SALES]
WHERE [PO] IS NOT NULL
GROUP BY
SKU
I suspect you could apply this logic to most of the columns and substantially improve efficiency of the query when coupled with date columns and appropriate indexing.
Expansion on my comment. Note it is just a suggestion, no guarantee if will run faster.
Take the following derived table histORy:
SELECT SKU,SUM([SALES Qty]) AS [Global Sales History Qty - All the years]
FROM dbo.[SALES]
WHERE [PO] IS NOT NULL
GROUP BY SKU
Before you run your query, materialize the derived table in a temporary table:
SELECT SKU,SUM([SALES Qty]) AS [Global Sales History Qty - All the years]
INTO #histORy
FROM dbo.[SALES]
WHERE [PO] IS NOT NULL
GROUP BY SKU
Then use the temporary table in the query:
LEFT JOIN #histORy AS h ON MATINFO.[SKU]=h.[SKU]
In this case you may want to have a index on the SKU field, so you could create the temporary table yourself, slap an index on it, populate with INSERT INTO #history... SELECT ... etc.

showing one record within 48 hours using sql

I am having an issue with my date values and the data types for the date field is date-time but at the sametime i am getting a lot of records for the same id within 48 hours. The goal is just to return one record only if patient makes visit to the hospital within 48. For example if patient A goes to ER on 1/1/2014 and again goes back to 1/2/2014 then i only want to show the first visit which 1/1/2014. I really believe the issue is at this line
AND A.[ADMT_TS] < DateAdd(d, 2, ADMT_TS)
and i think i need to do some conversion first in order to get the correct values.
here is my query and please not that i have other queries before the select statement here but i am only posting this section which where i am trying to get the first 48 hours.
SELECT [ID], [LOCATION], [ADMT_TS]
FROM ERS WHERE RN = 1
UNION ALL
SELECT [ID], [LOCATION], [ADMT_TS]
FROM ERS A
WHERE RN > 1 AND EXISTS (SELECT 1 FROM ERS WHERE RN = 1 AND [ID] = A.[ID])
AND NOT EXISTS(SELECT 1 FROM ERS WHERE RN = 1 AND [ID] = A.[ID] AND A.[ADMT_TS] < DateAdd(d, 2, ADMT_TS))
This will work but may not be the best option. If you post some data and give us an idea of how many rows may/will be in ERS table, I can adjust the query if needed
SELECT [Id]
,[Loc]
,MIN([admt_ts])
FROM [NewJunk].[dbo].[ERS]
WHERE RN = 1
GROUP BY id, loc

Flatten/merge overlapping time intervals

I have a 'Service' table with millions of rows. Each row corresponds to a service provided by a staff in a given date and time interval (Each row has a unique ID). There are cases where a staff might provide services in overlapping time frames. I need to write a query that merges overlapping time intervals and returns the data in the format shown below.
I tried grouping by StaffID and Date fields and getting the Min of BeginTime and Max of EndTime but that does not account for the non-overlapping time frames. How can I accomplish this? Again, the table contains several million records so a recursive CTE approach might have performance issues. Thanks in advance.
Service Table
ID StaffID Date BeginTime EndTime
1 101 2014-01-01 08:00 09:00
2 101 2014-01-01 08:30 09:30
3 101 2014-01-01 18:00 20:30
4 101 2014-01-01 19:00 21:00
Output
StaffID Date BeginTime EndTime
101 2014-01-01 08:00 09:30
101 2014-01-01 18:00 21:00
Here is another sample data set with a query proposed by a contributor.
http://sqlfiddle.com/#!6/bfbdc/3
The first two rows in the results set should be merged into one row (06:00-08:45) but it generates two rows (06:00-08:30 & 06:00-08:45)
I only came up with a CTE query as the problem is there may be a chain of overlapping times, e.g. record 1 overlaps with record 2, record 2 with record 3 and so on. This is hard to resolve without CTE or some other kind of loops, etc. Please give it a go anyway.
The first part of the CTE query gets the services that start a new group and are do not have the same starting time as some other service (I need to have just one record that starts a group). The second part gets those that start a group but there's more then one with the same start time - again, I need just one of them. The last part recursively builds up on the starting group, taking all overlapping services.
Here is SQLFiddle with more records added to demonstrate different kinds of overlapping and duplicate times.
I couldn't use ServiceID as it would have to be ordered in the same way as BeginTime.
;with flat as
(
select StaffID, ServiceDate, BeginTime, EndTime, BeginTime as groupid
from services S1
where not exists (select * from services S2
where S1.StaffID = S2.StaffID
and S1.ServiceDate = S2.ServiceDate
and S2.BeginTime <= S1.BeginTime and S2.EndTime <> S1.EndTime
and S2.EndTime > S1.BeginTime)
union all
select StaffID, ServiceDate, BeginTime, EndTime, BeginTime as groupid
from services S1
where exists (select * from services S2
where S1.StaffID = S2.StaffID
and S1.ServiceDate = S2.ServiceDate
and S2.BeginTime = S1.BeginTime and S2.EndTime > S1.EndTime)
and not exists (select * from services S2
where S1.StaffID = S2.StaffID
and S1.ServiceDate = S2.ServiceDate
and S2.BeginTime < S1.BeginTime
and S2.EndTime > S1.BeginTime)
union all
select S.StaffID, S.ServiceDate, S.BeginTime, S.EndTime, flat.groupid
from flat
inner join services S
on flat.StaffID = S.StaffID
and flat.ServiceDate = S.ServiceDate
and flat.EndTime > S.BeginTime
and flat.BeginTime < S.BeginTime and flat.EndTime < S.EndTime
)
select StaffID, ServiceDate, MIN(BeginTime) as begintime, MAX(EndTime) as endtime
from flat
group by StaffID, ServiceDate, groupid
order by StaffID, ServiceDate, begintime, endtime
Elsewhere I've answered a similar Date Packing question with
a geometric strategy. Namely, I interperet the date ranges
as a line, and utilize geometry::UnionAggregate to merge
the ranges.
Your question has two peculiarities though. First, it calls
for sql-server-2008. geometry::UnionAggregate is not then
avialable. However, download the microsoft library at
https://github.com/microsoft/SQLServerSpatialTools and load
it in as a clr assembly to your instance and you have it
available as dbo.GeometryUnionAggregate.
But the real peculiarity that has my interest is the concern
that you have several million rows to work with. So I thought
I'd repeat the strategy here but with an added technique to
improve it's performance. This technique will work well if
you have a lot of your StaffID/date subsets that are the same.
First, let's build a numbers table. Swap this out with your favorite
way to do it.
select i = row_number() over (order by (select null))
into #numbers
from #services; -- where i put your data
Then convert the dates to floats and use those floats to create
geometrical points.
These points can then be turned into lines via STUnion and STEnvelope.
With your ranges now represented as geometric lines, merge them via
UnionAggregate. The resulting geometry object 'lines' might contain
multiple lines. But any overlapping lines turn into one line.
select s.StaffID,
s.Date,
linesWKT = geometry::UnionAggregate(line).ToString()
-- If you have SQLSpatialTools installed then:
-- linesWKT = dbo.GeometryUnionAggregate(line).ToString()
into #aggregateRangesToGeo
from #services s
cross apply (select
beginTimeF = convert(float, convert(datetime,beginTime)),
endTimeF = convert(float, convert(datetime,endTime))
) prepare
cross apply (select
beginPt = geometry::Point(beginTimeF, 0, 0),
endPt = geometry::Point(endTimeF, 0, 0)
) pointify
cross apply (select
line = beginPt.STUnion(endPt).STEnvelope()
) lineify
group by s.StaffID,
s.Date;
You have one 'lines' object for each staffId/date combo. But depending
on your dataset, there may be many 'lines' objects that are the same
between these combos. This may very well be true if staff are expected
to follow a routine and data is recorded to the nearest whatever.
So get a distinct lising of 'lines' objects. This should improve
performance.
From this, extract the individual lines inside 'lines'. Envelope the lines,
which ensures that the lines are stored only as their endpoints. Read the
endpoint x values and convert them back to their time representations.
Keep the WKT representation to join it back to the combos later on.
select lns.linesWKT,
beginTime = convert(time, convert(datetime, ap.beginTime)),
endTime = convert(time, convert(datetime, ap.endTime))
into #parsedLines
from (select distinct linesWKT from #aggregateRangesToGeo) lns
cross apply (select
lines = geometry::STGeomFromText(linesWKT, 0)
) geo
join #numbers n on n.i between 1 and geo.lines.STNumGeometries()
cross apply (select
line = geo.lines.STGeometryN(n.i).STEnvelope()
) ln
cross apply (select
beginTime = ln.line.STPointN(1).STX,
endTime = ln.line.STPointN(3).STX
) ap;
Now just join your parsed data back to the StaffId/Date combos.
select ar.StaffID,
ar.Date,
pl.beginTime,
pl.endTime
from #aggregateRangesToGeo ar
join #parsedLines pl on ar.linesWKT = pl.linesWKT
order by ar.StaffID,
ar.Date,
pl.beginTime;

Resources