TSQL Subquery effeciency - sql-server

I am trying to write a subquery in a view to be returned as a column but I am not exactly sure what would give me the most efficient call.
I have View A that gathers a bunch of fields from different tables (one table being Listings that has has a 1 to many relationship with OpenHours) then one of the fields I want it to be from another table (OpenHours) that will only be Today's Open hours field.
The OpenHours table has ListingID, Day (0 based for the day of the week), Hours (text of the open hours such as "8:00am-5:00pm"). Here is what I need to do:
Check if OpenTable has a record for that particular listing that day = 7, if its 7 (which is not a day of the week) then return "Open 24 hours".
If does not exist then return the next record, since SQL Servers datepart(dw.. is 1 based, following will be used select datepart(dw,getdate())-1 to get a 0 day based day of week starting on Sunday (Sunday being 0)
Return nothing if no records exist that match the criteria.
I would appreciate some help on this. I attempted to write this but could not get far. I am not sure how to declare variables for day of the week in the view.
UPDATE
here is my function, anyone see any glaring inefficiencies?
ALTER FUNCTION [dbo].[GetTodaysOpenHours](#ListingID int)
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #DayOfWeek int
--SQL Day of week starts on sunday but it is 1 based, listing open hours are 0 based
SET #DayOfWeek = DATEPART(DW, GETDATE()) - 1
DECLARE #OpenHours VARCHAR(50)
IF EXISTS(SELECT * FROM OpenHours WHERE Day = 7 AND ListingID = #ListingID)
SET #OpenHours = 'Open 24 Hours'
ELSE
SELECT #OpenHours = Hours FROM OpenHours WHERE ListingID = #ListingID AND Day = #DayOfWeek
RETURN #OpenHours
END
UPDATED VIEW
ALTER view [dbo].[vListings]
as
SELECT l.ListingID, l.ExpiryDate, l.IsApproved, l.IsActive, l.Position,MoneyField1, DateField1,
IntField1, IntField2, IntField3, IntField4,
BoolField1, BoolField2, BoolField3,
OptionField1, OptionField2, OptionField3, OptionField4,
IsTop, TopStartDate, TopExpireDate, Address, Address + ' ' + c.Name + ' ' + p.Name AS FullAddress,
o1.Description as Options1Description,
o2.Description as Options2Description,
o3.Description as Options3Description,
o4.Description as Options4Description,
COALESCE(
(SELECT TOP 1 ThumbnailPath
FROM Attachments
WHERE ListingID = l.listingID), '/content/images/noImageThumbnail2.jpg') AS MainThumbnail,
COALESCE(
(SELECT TOP 1 ThumbnailPath2
FROM Attachments
WHERE ListingID = l.listingID), '/content/images/noImageThumbnail.jpg') AS MainThumbnail2,
l.UserID,
c.SubDomainName as CitySubDomainName, l.Name,
CASE
WHEN l.IsAutoGenerated = 1 THEN l.ImportedPhoneNumber
ELSE dbo.FormatPhoneNumber(u.PhoneNumber)
END as PhoneNumber,
CASE
WHEN l.IsAutoGenerated = 1 THEN l.ImportedContactInfo
ELSE u.FirstName + ' ' + u.LastName
END as ContractInfo,
p.Abbv as StateAbbv,
cn.Code as CountryCode,
l.Comments, l.UniqueID, l.Rating, l.Website,
(select L.ListingID,
isnull(H1.Hours, H2.Hours) as Hours
from Listings L
outer apply (
select Hours FROM OpenHours H WHERE H.Day = 7
AND H.ListingID = L.ListingID
) H1
outer apply (
select Hours FROM OpenHours H WHERE Day = DATEPART(DW, GETDATE()) - 1
AND H.ListingID = L.ListingID
) H2
--dbo.GetTodaysOpenHours(l.ListingID) as TodaysOpenHours
FROM Listings l
INNER JOIN Cities c ON c.CityID = l.CITYID
INNER JOIN Provinces p ON p.ProvinceID = c.ProvinceID
INNER JOIN Countries cn ON cn.CountryID = p.CountryID
INNER JOIN AspNetUsers u ON u.Id = l.UserID
LEFT OUTER JOIN Options1 o1 ON o1.OptionID = l.OptionField1
LEFT OUTER JOIN Options2 o2 ON o2.OptionID = l.OptionField2
LEFT OUTER JOIN Options3 o3 ON o3.OptionID = l.OptionField3
LEFT OUTER JOIN Options4 o4 ON o4.OptionID = l.OptionField4
GO
I get an error that says "incorrect syntax near the keyword FROM
(FROM Listings l)
I upadted the view (added FROM) to the select statements as well
I prefer not to use t he function because I had to add an index to my openhours table on listingid and day in order to make it a little faster but if adding sql into view itself would be better that would be awesome

User defined function isn't usually the best performing solution. You could try just to add that SQL into the view / query by doing something like this (sorry, can't test this, hopefully there's no syntax errors):
select
L.ListingID,
isnull(H1.Hours, H2.Hours) as Hours
from Listing L
outer apply (
select Hours OpenHours H WHERE H.Day = 7
AND H.ListingID = L.ListingID
) H1
outer apply (
select Hours OpenHours H WHERE Day = DATEPART(DW, GETDATE()) - 1
AND H.ListingID = L.ListingID
) H2

Related

SQL Query to group by time and roll up and concatenate string values

I am trying to get a particular format from a group of times and days between two tables.
Database:
MeetingTime table has a relationship from MeetingTime.DayOfWeekId (foreign key) to table DayOfWeek.Id (Primary Key). Example Query:
select t.ClassId, d.Name, t.StartTime, t.EndTime
From MeetingTime t
Inner Join DaysOfWeek d on d.Id = t.DayOfWeekId
Where t.classId = 8
Results:
My desired results for this set of data would be one row, because the start and end times are the same.
09:00-15:35 M/T/W/Th/F
NOTE, the start and end time above, can be separate columns above, the main goal is display the days of the week for each grouped time.
The monkey wrench is that the times can be completely different or the same. For example this data set:
I would want displayed in 2 rows:
07:35-14:15 M/T/W
08:00-14:15 Th/F
And finally, this dataset where all times are different:
Would display in 5 rows:
13:48-14:48 M
15:48-16:48 T
05:49-23:53 W
14:49-16:49 Th
13:49-16:49 F
I haven't had much success with grouping the times. I did figure out how to concatenate the days of the week rolling the days up into one column using the 'Stuff' Operator, but didn't get anywhere with the grouping of the start and end time coupled with this yet.
Concatenating and rolling up days:
STUFF((SELECT '/ ' +
(CASE
WHEN d.[Name] = 'Thursday' THEN SUBSTRING(d.[Name], 1, 2)
WHEN d.[Name] = 'Sunday' THEN 'U'
WHEN d.[Name] != '' THEN SUBSTRING(d.[Name], 1, 1)
ELSE NULL
END)
FROM MeetingTime m
Inner Join [DayOfWeek] d on d.Id = m.DayOfWeekId
Where m.ClassId = class.Id
FOR XML PATH('')), 1, 1, '') [ClassSchedule]
I'm also not opposed to just returning the rows and handling the data manipulation in C# code, but wanted to see if SQL could handle it.
I was able to get this working. Here is the query:
select
t.ClassId,
t.StartTime,
t.EndTime,
STUFF((SELECT '/' + (CASE
WHEN w.[Name] = 'Thursday' THEN SUBSTRING(w.[Name], 1, 2)
WHEN w.[Name] = 'Sunday' THEN 'U'
WHEN w.[Name] != '' THEN SUBSTRING(w.[Name], 1, 1)
ELSE NULL
END)
From MeetingTime s
Inner Join DayOfWeek w on w.Id = s.DayOfWeekId
Where s.classId = 7 and s.DayOfWeekId > 0
and s.StartTime = t.StartTime
and s.EndTime = t.EndTime
FOR XML PATH('')), 1, 1, '') [ClassSchedule]
From MeetingTime t
Inner Join DayOfWeek d on d.Id = t.DayOfWeekId
Where t.classId = 7 and t.DayOfWeekId > 0
Group by t.StartTime, t.EndTime, t.ClassId
Obviously hardcoded Id you would want to create a variable.
Results where the start and end time are all the same:
Some times the same and some different:
Some times the same and some different with days not in order:
Times all different:
Times with only Mon/Wed/Fri.
I feel pretty good about this, except I'd like to fix the order of the above result image where all times are different and the days are not in chronological order.

SQL - Finding Gaps in Coverage

I am running this problem on SQL server
Here is my problem.
have something like this
Dataset A
FK_ID StartDate EndDate Type
1 10/1/2018 11/30/2018 M
1 12/1/2018 2/28/2019 N
1 3/1/2019 10/31/2019 M
I have a second data source I have no control over with data something like this:
Dataset B
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/15/2018 M
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
What I am trying to accomplish is to check to make sure every date within each TYPE M record in Dataset A has at least 1 record in Dataset B.
For example record 1 in Dataset A does NOT have coverage from 10/26/2018 through 11/30/2018. I really only care about when the coverage ends, in this case I want to return 10/26/2018 because it is the first date where the span has no coverage from Dataset B.
I've written a function that does this but it is pretty slow because it is cycling through each date within each M record and counting the number of records in Dataset B. It exits the loop when it finds the first one but I would really like to make this more efficient. I am sure I am not thinking about this properly so any suggestions anyone can offer would be helpful.
This is the section of code I'm currently running
else if #SpanType = 'M'
begin
set #CurrDate = #SpanStart
set #UncovDays = 0
while #CurrDate <= #SpanEnd
Begin
if (SELECT count(*)
FROM eligiblecoverage ec join eligibilityplan ep on ec.plandescription = ep.planname
WHERE ec.masterindividualid = #IndID
and ec.planbegindate <= #CurrDate and ec.planenddate >= #CurrDate
and ec.sourcecreateddate = #MaxDate
and ep.medicaidcoverage = 1) = 0
begin
SET #Result = concat('NON Starting ',format(#currdate, 'M/d/yyyy'))
BREAK
end
set #CurrDate = #CurrDate + 1
end
end
I am not married to having a function it just could not find a way to do this in queries that wasn't very very slow.
EDIT: Dataset B will never have any TYPEs except M so that is not a consideration
EDIT 2: The code offered by DonPablo does de-overlap the data but only in cases where there is an overlap at all. It reduces dataset B to:
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
instead of
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
I am still futzing around with it but it's a start.
I would approach this by focusing on B. My assumption is that any absent record would follow span_end in the table. So here is the idea:
Unpivot the dates in B (adding "1" to the end dates)
Add a flag if they are present with type "M".
Check to see if any not-present records are in the span for A.
Check the first and last dates as well.
So, this looks like:
with bdates as (
select v.dte,
(case when exists (select 1
from b b2
where v.dte between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1 else 0
end) as in_b
from b cross apply
(values (spanstart), (dateadd(day, 1, spanend)
) v(dte)
where b.type = 'M' -- all we care about
group by v.dte -- no need for duplicates
)
select a.*,
(case when not exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 0
when not exists (select 1
from b b2
where a.enddate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
when exists (select 1
from bdates bd
where bd.dte between a.startdate and a.enddate and
bd.in_b = 0
)
then 0
when exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1
else 0
end)
from a;
What is this doing? Four validity checks:
Is the starttime valid?
Is the endtime valid?
Are any intermediate dates invalid?
Is there at least one valid record?
Start by framing the problem in smaller pieces, in a sequence of actions like I did in the comment.
See George Polya "How To Solve It" 1945
Then Google is your friend -- look at==> sql de-overlap date ranges into one record (over a million results)
UPDATED--I picked Merge overlapping dates in SQL Server
and updated it for our table and column names.
Also look at theory from 1983 Allen's Interval Algebra https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html
Or from 2014 https://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/
This is a primer on how to setup test data for this problem.
Finally determine what counts via Ranking the various pairs of A vs B --
bypass those totally Within, then work with earliest PartialOverlaps, lastly do the Precede/Follow items.
--from Merge overlapping dates in SQL Server
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
)
Select * from DeOverlapped_B
Now we have something to feed into the next steps, and we can use the above as a CTE
======================================
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
),
-- find A row's coverage
ACoverage as (
Select
a.*, b.SpanEnd, b.SpanStart,
Case
When SpanStart <= StartDate And StartDate <= SpanEnd
And SpanStart <= EndDate And EndDate <= SpanEnd
Then '1within' -- starts, equals, during, finishes
When EndDate < SpanStart
Or SpanEnd < StartDate
Then '3beforeAfter' -- preceeds, meets, preceeded, met
Else '2overlap' -- one or two ends hang over spanStart/End
End as relation
From Coverage_A a
Left Join DeOverlapped_B b
On a.FK_ID = b.FK_ID
Where a.Type = 'M'
)
Select
*
,Case
When relation1 = '2' And StartDate < SpanStart Then StartDate
When relation1 = '2' Then DateAdd(d, 1, SpanEnd)
When relation1 = '3' Then StartDate
End as UnCoveredBeginning
From (
Select
*
,SUBSTRING(relation,1,1) as relation1
,ROW_NUMBER() Over (Partition by A_ID Order by relation, SpanStart) as Rownum
from ACoverage
) aRNO
Where Rownum = 1
And relation1 <> '1'

Complete Select Statement ELSE a defaulted value?

I'm wondering if there is a way to have a complete select statement and if no row is returned to return a row with a default value in a specific field (SQL Server)? Here's a generic version of my SQL to better explain:
SELECT COUNT(CASE WHEN CAST(c.InjuryDate as DATE)>DATEADD(dd,-60, getdate ()) THEN b.InjuryID end) InjuryCount, a.PersonID
FROM Person_Info a
JOIN Injury_Subject b on b.PersonID=a.PersonID
JOIN Injury_Info c on c.InjuryID=b.InjuryID
WHERE EXISTS (SELECT * FROM Hospital_Record d WHERE d.PersonID=b.PersonID and d.InjuryID=b.InjuryID) --There could be multiple people associated with the same InjuryID
GROUP BY a.PersonID
If NOT EXISTS (SELECT * FROM Hospital_Record d WHERE d.PersonID=a.PersonID) THEN '0' in InjuryCount
I want a row for each person who has had an injury to display. Then I'd like a count of how many injuries resulted in hospitalizations in the last 60 days. If they were not hospitalized, I'd like the row to still be generated, but display '0' in InjuryCount column. I've played with this a bunch, moving my date from the WHERE to the SELECT, trying IF ELSE combos, etc. Could someone help me figure out how to get what I want please?
It's hard to tell without an example of input and desired output, but I think this is what you are going for:
select
InjuryCount = count(case
when cast(ii.InjuryDate as date) > dateadd(day,-60,getdate())
then i.InjuryId
else null
end
)
, p.PersonId
from Person_Info p
left join Hosptal_Record h on p.PersonId = h.PersonId
left join Injury_Subject i on i.PersonId = h.PersonId
and h.InjuryId = i.InjuryId
left join Injury_Info ii on ii.InjuryId = i.InjuryId
group by p.PersonId;

Exclude certain line depending on a certain condition in t-SQL

Firstly, I have created the following stock sales SELECT statement:
SELECT
PostST.TxDate AS TxDate,
PostST.Reference AS InvNum,
Client.Name AS CustomerName,
SalesRep.Code AS SalesRep,
WhseMst.Code AS Whse,
CONCAT (StkItem.Description_1, ' - ',StkItem.Code) AS Item,
_etblLotTracking.cLotDescription AS LotNumber,
CASE
WHEN PostST.TrCodeID = 34 THEN (PostST.Quantity * 1)
WHEN PostST.TrCodeID = 30 THEN (PostST.Quantity * -1)
ELSE 0
END AS QtySold,
CAST
(
CASE
WHEN PostST.TrCodeID = 34 THEN (PostST.Credit / PostST.Quantity)
WHEN PostST.TrCodeID = 30 THEN (PostST.Debit / (PostST.Quantity * -1))
ELSE 0
END
as [money]
)
AS CustomerPrice,
StkItem.ItemGroup AS ItemGroup,
StkItem.ulIIStockType AS StockType,
YEAR (PostST.TxDate) AS Year,
DATENAME (MONTH, (PostST.TxDate)) AS Month
FROM
PostST
INNER JOIN StkItem
ON StkItem.StockLink = PostST.AccountLink
INNER JOIN PostAR
ON PostAR.cAuditNumber = PostST.cAuditNumber
INNER JOIN Client
ON Client.DCLink = PostAR.AccountLink
INNER JOIN SalesRep
ON SalesRep.idSalesRep = PostAR.RepID
INNER JOIN WhseMst
ON WhseMst.WhseLink = PostST.WarehouseID
FULL JOIN _etblLotTracking
ON _etblLotTracking.idLotTracking = PostST.iLotID
WHERE
PostST.TrCodeID IN (34, 30) AND StkItem.ItemGroup <> 'TRAN'
This query is run from Sage Evolution and data is dumped into Excel. From there I manipulate to view each item and the sales history in quantity per month and year.
My problem is as follows:
Within the data that follows, is sometimes a customer who has perhaps stopped buying a particular item. What I need is for this statement to also filter out a line by the following condition:
If the customer does not have a sale for 6 months or more, then that STOCK ITEM must be filtered out for that specific customer.
Thank you!

Find Previous record by date stored in another Table

So i have 2 tables, one keeps the record of the cash on tjhe cash register at the end of the day and how much money is left for the next day, the other keeps a record of basicly the date of the record (tables cannot be joined) All that looks like this:
RegistersTable
------------------
Register_ID| DATE
5 | 02/02/2012
8 | 04/02/2012
1 | 10/02/2012
CashTable
----------------
Register_ID|CashEOD|CashFND
8 |3235 |325
5 |6843 |435
1 |1236 |1953
So what im trying to get is a select statement that should return this
RegisterID| DATE|CashEOD|PrevCashFND
1 |10/02/2012|1236 |325
8 |04/02/2012|3235 |435
5 |02/02/2012|6843 |0/Null
Start with a RegisterID on the CashTable, find the previous RegisterID by the DATE in RegistersTable, get the previous CashFND so the final goal is to know how much was selled on that day.
Cash End of Day minus the Cash left on the register from the previous day should tell me that. Thanks in advance.
Please try the following - I hope it helps:
SELECT
D.RegisterID,
D.[DATE],
CashTable.CashEOD,
CashTable_Prev.CashFND AS PrevCashFND
FROM
(
SELECT
A.RegisterID,
A.[DATE],
B.RegisterID AS PrevID
FROM
RegistersTable A
INNER JOIN RegistersTable B ON A.RegisterID <> B.RegisterID AND A.[DATE] > B.[DATE]
INNER JOIN
(
SELECT
_innA.RegisterID,
_innB.RegisterID As PrevID,
MIN(_innA.[DATE] - _innB.[DATE]) AS MinDateDiff
FROM
RegistersTable _innA
INNER JOIN RegistersTable _innB ON _innA.RegisterID <> _innB.RegisterID AND _innA.[DATE] > _innB.[DATE]
GROUP BY
_innA.RegisterID,
_innB.RegisterID
) C
ON A.RegisterID = C.RegisterID AND B.RegisterID = C.PrevID AND (A.[DATE] - B.[DATE]) = C.MinDateDiff
) AS D
INNER JOIN CashTable
ON CashTable.Register_ID = D.RegisterID
INNER JOIN CashTable AS CashTable_Prev
ON CashTable_Prev.Register_ID = D.PrevID

Resources