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

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.

Related

Compare Sales with last year sales per customer

I have a 2 queries that I have used UNION on to compare the sales value per customer over 2 years. The below returns 2 columns, one for organizations and one for the sales value/customer. But the 2 year split is not working. It seems to be pulling all the values into one column rather than having a column for 2018 and a column for 2019. Maybe I should be using a join or a sub-query?
The return result would look something like:
Organization Last Year Previous year
CUS 12000 160000
etc
SELECT Top 25
FORMAT(SUM(dbo.ARInvoices.arpFullInvoiceSubtotalBase), 'C2') AS "previousyear ", dbo.Organizations.cmoOrganizationID AS "Organization"
FROM (dbo.ARInvoices
LEFT OUTER JOIN dbo.Organizations ON dbo.ARInvoices.arpCustomerOrganizationID = dbo.Organizations.cmoOrganizationID)
WHERE YEAR(arpInvoiceDate) = year(DATEADD(year, -2, getdate()))AND arpInvoiceType = 1
GROUP BY dbo.Organizations.cmoOrganizationID
UNION
SELECT Top 25
FORMAT(SUM(dbo.ARInvoices.arpFullInvoiceSubtotalBase), 'C2') AS "Lastyear", dbo.Organizations.cmoOrganizationID AS "Organization"
FROM (dbo.ARInvoices
LEFT OUTER JOIN dbo.Organizations ON dbo.ARInvoices.arpCustomerOrganizationID = dbo.Organizations.cmoOrganizationID)
WHERE YEAR(arpInvoiceDate) = year(DATEADD(year, -1, getdate()))AND arpInvoiceType = 1
GROUP BY dbo.Organizations.cmoOrganizationID'
You could do conditional aggregation
select
sum(case
when arpInvoiceDate >= datefromparts(year(getdate()) - 2, 1, 1)
and arpInvoiceDate < datefromparts(year(getdate()) - 1, 1, 1)
then i.arpFullInvoiceSubtotalBase
end) as previousYear,
sum(case
when arpInvoiceDate >= datefromparts(year(getdate()) - 1, 1, 1)
then i.arpFullInvoiceSubtotalBase
end) as lastYear
from dbo.ARInvoices i
inner join dbo.Organizations o ON i.arpCustomerOrganizationID = o.cmoOrganizationID
where arpInvoiceType = 1 and arpInvoiceDate >= datefromparts(year(getdate()) - 2, 1, 1)
group by o.cmoOrganizationID
Notes:
I used INNER JOIN instead of LEFT JOIN since one of the columns for the left table is used in the GROUP BY clause
table aliases do make the query shorter to write and easier to read
I changed the implementation of the date filtering so no date function is used on the table columns (this makes the query more efficient)

Similar to the last Joining two tables on columns that don't equal and summing 3 columns in the second table

I have a equipment table and a mobile work order table that I am wanting to join, I am wanting to display all the equipment and the reactive hours. If there is no reactive hours for a certain piece of equipment then I want to display a zero in the rows where value is null. This is what I have below. It only gives me the equipment that has reactive hours in the other table.
SQL Server
Select e.EquipNbr, coalesce(sum(mw.MaintTech1hours + mw.MaintTech2hours + mw.MaintTech3hours), 0) ReactiveHours
From MblEquip e
inner join MobileWorkOrder mw on
mw.EquipNbr = e.EquipNbr
and mw.DateTm between DATEADD(month, DATEDIFF(month, 0, getDate()), 0)
and DATEADD(month, DATEDIFF(month, -1, getDate()), -1)
where e.DelFlg = 0 and mw.Category = 'Reactive'
group by e.EquipNbr order by ReactiveHours Desc;
If you test a value into 'where condition', your 'left outer join' is equivalent to 'inner join'.
Try this (SQL remained)
Select e.EquipNbr, coalesce(sum(isnull(mw.MaintTech1hours, 0) + isnull(mw.MaintTech2hours, 0) + isnull(mw.MaintTech3hours, 0)), 0) ReactiveHours
From MblEquip e
left outer join MobileWorkOrder mw on mw.EquipNbr = e.EquipNbr and mw.Category = 'Reactive'
and Year(mw.DateTm) =Year(getdate()) and Month(mw.DateTm) =Month(getdate())
where e.DelFlg = 0
group by e.EquipNbr
order by ReactiveHours Desc;

Getting current semester in SQL SERVER

I'm having a trouble on this query it says Incorrect syntax near the keyword 'BETWEEN' but when I tried to individually execute the BETWEEN conditions without CASE WHEN it runs normally, Also in relation to the Title is their a proper way to get the semester in SQL SERVER?
DECLARE #ActiveSemester INT
SET #ActiveSemester = ISNULL((SELECT [value]
FROM spms_tblProfileDefaults WHERE eid = 7078 and ps_id = 1), (SELECT [value]
FROM spms_configs WHERE actions = 'semester_active') )
SELECT FORMAT((SUM((DATEDIFF(MINUTE, a.actual_start_time, a.actual_end_time)
- isnull(datediff(minute, break_start, break_end), 0))/ 60.0)), '0.00')
FROM spms_tblSubTask as a
LEFT JOIN pmis.dbo.employee as b ON a.eid = b.eid
LEFT JOIN pmis.dbo.time_reference as c ON c.code = ISNULL(b.TimeReference, 'TIME14')
LEFT JOIN dbo.spms_vwOrganizationalChart as e ON a.eid = e.eid
cross apply
(
select break_start = case when c.break_from between a.actual_start_time and a.actual_end_time
then c.break_from
when a.actual_start_time between c.break_from and c.break_to
then a.actual_start_time
else NULL
end,
break_end = case when c.break_to between a.actual_start_time and a.actual_end_time
then c.break_to
when a.actual_end_time between c.break_from and c.break_to
then a.actual_end_time
end
) as d
WHERE
b.Shift = 0 and a.eid = 7078 and
YEAR(a.start_date_time) = YEAR(GETDATE()) and a.action_code = 1
and
(CASE
WHEN
#ActiveSemester = 1
THEN
a.start_date_time BETWEEN CAST(CONCAT('01/01/',YEAR(GETDATE())) as date) AND
CAST(CONCAT('06/30/',YEAR(GETDATE())) as date)
ELSE
a.start_date_time BETWEEN CAST(CONCAT('07/01/',YEAR(GETDATE())) as date) AND
CAST(CONCAT('12/31/',YEAR(GETDATE())) as date)
END)
It seems that the other answer was Deleted and here's what I did as the other points out about the proper use of CASE WHEN
WHERE
'other condition' and
a.start_date_time BETWEEN
(CASE
WHEN #ActiveSemester = '1'
THEN
CAST(CONCAT('01/01/',YEAR(GETDATE())) as date)
ELSE
CAST(CONCAT('07/01/',YEAR(GETDATE())) as date)
END)
AND
(CASE
WHEN #ActiveSemester = '1'
THEN
CAST(CONCAT('06/30/',YEAR(GETDATE())) as date)
ELSE
CAST(CONCAT('12/31/',YEAR(GETDATE())) as date)
END)
with this, I get the results I wanted. Thanks
Assuming #ActiveSemester can take on the values 0 or 1, I'd just write this as:
WHERE
b.Shift = 0 and a.eid = 7078 and
YEAR(a.start_date_time) = YEAR(GETDATE()) and a.action_code = 1
and
#ActiveSemester = ((MONTH(a.start_date_time)-1)/6)
Now, it's worth noting that this expression cannot use an index on start_date_time to satisfy this final predicate. If that's an issue, and the query isn't performing well, then I'd either recommend persisted computed columns in this table to extract the year and month value from the start_date_time column, modify the above query to use those columns and add appropriate indexes, or to populate a calendar table which has such values (and semester) properly represented and join to that table above, to again simplify the expressions and allow indexes to be used.
Note, also, that the column name worries me. If it does contain a time portion as well as a date, then note that the expressions in your original query would have excluded any values that occurred after midnight on 30th June and 31st December1. I presume that wasn't your intent and the query above doesn't suffer this same shortcoming.
1Because 2018-06-30T00:01:00 is strictly later than 2018-06-30T00:00:00 (which is what CAST(CONCAT('06/30/',YEAR(GETDATE())) as date) expands out to) and so isn't BETWEEN the start and end dates you gave.

How to use GROUPING function in SQL common table expression - CTE

I have the below T-SQL CTE code where i'm trying to do some row grouping on four columns i.e Product, ItemClassification, Name & Number.
;WITH CTE_FieldData
AS (
SELECT
CASE(GROUPING(M.CodeName))
WHEN 0 THEN M.CodeName
WHEN 1 THEN 'Total'
END AS Product,
CASE(GROUPING(KK.ItemClassification))
WHEN 0 THEN KK.[ItemClassification]
WHEN 1 THEN 'N/A'
END AS [ItemClassification],
CASE(GROUPING(C.[Name]))
WHEN 0 THEN ''
WHEN 1 THEN 'Category - '+ '('+ItemClassification+')'
END AS [Name],
CASE(GROUPING(PYO.Number))
WHEN 0 THEN PYO.Number
WHEN 1 THEN '0'
END AS [Number],
ISNULL(C.[Name],'') AS ItemCode,
MAX(ISNULL(PYO.Unit, '')) AS Unit,
MAX(ISNULL(BT.TypeName, '')) AS [Water Type],
MAX(ISNULL(PYO.OrderTime, '')) AS OrderTime,
MAX(ISNULL(BUA.Event, '')) AS Event,
MAX(ISNULL(PYO.Remarks, '')) AS Remarks,
GROUPING(M.CodeName) AS ProductGrouping,
GROUPING(KK.ItemClassification) AS CategoryGrouping,
GROUPING(C.[Name]) AS ItemGrouping
FROM CTable C INNER JOIN CTableProducts CM ON C.Id = CM.Id
INNER JOIN MyData R ON R.PId = CM.PId
INNER JOIN MyDataDetails PYO ON PYO.CId = C.CId AND PYO.ReportId = R.ReportId
INNER JOIN ItemCategory KK ON C.KId = KK.KId
INNER JOIN Product M ON R.ProductId = M.ProductId
INNER JOIN WaterType BT ON PYO.WId = BT.WId
INNER JOIN WaterUnit BUA ON PYO.WUId = BUA.WUId
WHERE R.ReportId = 4360
GROUP BY M.CodeName, KK.ItemClassification, C.Name, PYO.Number
WITH ROLLUP
)
SELECT
Product,
[Name] AS Category,
Number,
Unit as ItemCode,
[Water Type],
OrderTime,
[Event],
[Comment]
FROM CTE_FieldData
Below are the issues/problems with the data being returned by the script above and they are the ones i'm trying to fix.
At the end of each ItemClassification grouping, i extra record is being added yet it does not exist in the table. (See line number 4 & 10 in the sample query results screenshot attached).
I want the ItemClassification grouping in column 2 to be at the beginning of the group not at the end of the group.
That way, ItemClassification "Category- (One)" would be at line 1 not the current line 5.
Also ItemClassification "Category- (Two)" would be at line 5 not the current line 11
Where the "ItemClassification" is displaying i would like to have columns (Number, ItemCode, [Water Type], [OrderTime], [Event], [Comment]) display null.
In the attached sample query results screenshot, those would be rows 11 & 5
The last row (13) is also unwanted.
I'm trying to understand SQL CTE and the GROUPING function but i'm not getting things right.
It looks like this is mostly caused by WITH ROLLUP and GROUPING. ROLLUP allows you to make essentially a sum line for your groupings. When you have WITH ROLLUP, it will give you NULL values for all of your non-aggregated fields in your select statement. You use GROUPING() in conjunction with ROLLUP to then label those NULL's as 'Total' or '0' or 'Category' as your query does.
1) Caused by GROUPING and ROLLUP. Take away both and this should be resolved.
2) Not sure what determines your groups and what would be defined as beginning or end. Order BY should suffice
3) Use ISNULL or CASE WHEN. If the Item Classification has a non null or non blank value, NULL each field out.
4) Take off WITH ROLLUP.

TSQL Subquery effeciency

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

Resources