How can I manipulate a string a T-SQL? - sql

business_id
open_day
open_time
close_day
close_time
1
FRIDAY
08:00
FRIDAY
12:00
1
FRIDAY
13:00
FRIDAY
17:00
1
MONDAY
08:00
MONDAY
17:00
2
SATURDAY
08:00
SATURDAY
16:00
2
SUNDAY
08:00
SUNDAY
16:00
3
MONDAY
08:00
MONDAY
16:00
I have a task to create and format opening hours for a business, I'm required to group these by days in a single string. However, it is repeating the day. Would it be possible to search for a particular value if it occurs twice within a string? I have gotten this far:
create table open_times (business_id int, open_day varchar(10), open_time varchar(10), close_day varchar(10), close_time varchar(10) )
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (1, 'FRIDAY', '08:00', 'FRIDAY', '12:00')
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (1, 'FRIDAY', '13:00', 'FRIDAY', '17:00')
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (1, 'MONDAY', '08:00', 'MONDAY', '17:00')
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (2, 'SATURDAY', '08:00', 'SATURDAY', '16:00')
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (2, 'SUNDAY', '08:00', 'SUNDAY', '16:00')
insert into open_times (business_id, open_day, open_time, close_day, close_time) values (3, 'MONDAY', '08:00', 'MONDAY', '16:00')
drop table open_times
I appreciate your help.
select
business_id
,left(name_values, LEN(name_values)-1) as opening_hours
from
(
select
results.business_id
,STUFF((
select
( case when open_day = 'FRIDAY' then 'Fr' when open_day = 'MONDAY' then 'Mo' when open_day = 'TUESDAY' then 'Tu' when open_day = 'WEDNESDAY' then 'We' When open_day = 'THURSDAY' then 'Th' when open_day = 'SATURDAY' then 'Sa' else 'Su' end )
+ ' ' + open_time + '-' + close_time + '; '
from open_times
where business_id = results.business_id
for xml path(''),type).value('(./text())[1]','VARCHAR(MAX)'),1,0, '') as name_values
from open_times results
group by business_id
) innerquery
Current Output for Business 1: 'Fr 08:00-12:00; Fr 13:00-17:00; Mo 08:00-17:00'
Desired Output For Business 1: 'Fr 08:00-12:00, 13:00-17:00; Mo 08:00-17:00'

You can use STRING_AGG here. You just need to do two levels of grouping, once per day, then again for the whole business_id
SELECT
ot.business_id,
Times = STRING_AGG(CONCAT(
UPPER(LEFT(ot.open_day, 1)),
LOWER(SUBSTRING(ot.open_day, 2, 1)),
' ',
ot.Times)
, '; ')
FROM (
SELECT
ot.business_id,
ot.open_day,
Times = STRING_AGG(CONCAT(
ot.open_time,
'-',
ot.close_time),
', ')
FROM open_times ot
GROUP BY ot.business_id, ot.open_day
) ot
GROUP BY ot.business_id;
db<>fiddle

You can do this with one level of aggregation by incorporating window functions into your logic:
select ot.business_id,
stuff((select (case when seqnum = 1 then '; ' else ', ' end) +
(case when seqnum = 1 and ot2.open_day = 'FRIDAY' then 'Fr '
when seqnum = 1 and ot2.open_day = 'MONDAY' then 'Mo '
when seqnum = 1 and ot2.open_day = 'TUESDAY' then 'Tu '
when seqnum = 1 and ot2.open_day = 'WEDNESDAY' then 'We '
when seqnum = 1 and ot2.open_day = 'THURSDAY' then 'Th '
when seqnum = 1 and ot2.open_day = 'SATURDAY' then 'Sa '
when seqnum = 1 then 'Su '
else ''
end ) +
ot2.open_time + '-' + ot2.close_time
from (select ot2.*,
row_number() over (partition by ot2.open_day order by ot2.open_time) as seqnum
from open_times ot2
where ot2.business_id = ot.business_id
) ot2
order by ot2.open_day, ot2.open_time
for xml path(''),type
).value('(./text())[1]', 'VARCHAR(MAX)'), 1, 2, ''
) as name_values
from open_times ot
group by ot.business_id;
Here is a db<>fiddle.
Your version of the query is actually incorrect, because you do not have an order by in the XML subquery. The results can be in any order.
In addition, you don't just want to remove the duplicate names, but you also want to change the delimiter from ; to ,.
The idea in the above query is that the times are enumerated on each day. The delimiter is ; for the first time and , for the subsequent ones. The abbreviation is used only for the first one. I also added qualifications to all the column references, a highly recommended practice.
Note that you can simplify the case logic to:
left(ot2.open_day, 1) + lower(substring(ot2.open_day, 2, 1))

For older versions use FOR XML PATH aggregation trick. Assuming no more then 2 open intervals a day
with t as (
select business_id, left(open_day,2) + ' ' + min(open_time + '-' + close_time) +
case when min(open_time) = max(open_time) then ''
else ', ' + max(open_time + '-' + close_time) end ots
from open_times
group by business_id, open_day
)
select business_id, stuff(
(select '; ' + ots
from t t2
where t2.business_id = t1.business_id
order by left(ots,2)
for xml path(''))
, 1, 2, '') opening_hours
from t t1
group by business_id

Related

SQL Stored Procedures, Combining rows with derived variables

I've been away for sometime and need help with, what is probably a simple solution but at this time I'm stuck. I have this select statement and need to combine rows with like elements for the same department but the group by would have a very large select list, is there another way like MERGE? Here is the sql stmt I'm dealing with along with the output:
SELECT distinct sg.Description as 'Groups', si.Store_Name, a.Store_ID, si.Store_ID1 as 'Store #2', d.Department_ID as 'Dept', a.Vendor_ID,
IIF(d.Delivery_Day = 1, dbo.DayOfWeekToString(d.Order_Day),null) as 'Sun',
IIF(d.Delivery_Day = 1, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 2, dbo.DayOfWeekToString(d.Order_Day),null) as 'Mon',
IIF(d.Delivery_Day = 2, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 3, dbo.DayOfWeekToString(d.Order_Day),null) as 'Tue',
IIF(d.Delivery_Day = 3, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 4, dbo.DayOfWeekToString(d.Order_Day),null) as 'Wed',
IIF(d.Delivery_Day = 4, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 5, dbo.DayOfWeekToString(d.Order_Day),null) as 'Thu',
IIF(d.Delivery_Day = 5, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 6, dbo.DayOfWeekToString(d.Order_Day),null) as 'Fri',
IIF(d.Delivery_Day = 6, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)) ,null ) as 'time',
IIF(d.Delivery_Day = 7, dbo.DayOfWeekToString(d.Order_Day),null) as 'Sat',
IIF(d.Delivery_Day = 7, CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2)),null) as 'time'
from Delivery_Schedule_Header a Join Vendor_Info b ON (a.Vendor_ID = b.Vendor_ID) Join Delivery_Schedule_Detail d on d.UID_Delivery = a.UID
Join Store_Info si on si.Store_ID = a.Store_ID Join Store_Group sg on sg.GroupStore_UID = si.GroupStore_UID
Where a.Vendor_ID = #Vendor_Id and si.GroupStore_UID = Case when #Group_Id = -1 then si.GroupStore_UID else #Group_Id end
and the results:
Groups Store_Name Store_ID Store #2 Dept Vendor_ID Sun time Mon time Tue time Wed time Thu time Fri time Sat time
Balls Price Chopper 18 18 18 3200 70038000 NULL NULL NULL NULL NULL NULL NULL NULL Wednesday 11:00 NULL NULL NULL NULL
Balls Price Chopper 18 18 18 3200 70038000 NULL NULL NULL NULL Monday 11:00 NULL NULL NULL NULL NULL NULL NULL NULL
Balls Price Chopper 18 18 18 3200 70038000 Saturday 11:00 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
These three rows need to be combined under the same department
sorry if this is rather elementary but I've been out of this type of work for sometime now and need a refresher. Thanks
You want conitional aggregation. This works by creating a group by clause that contains all columns except those you want to merge, and then surround the conditional expression with an aggregate function (such as MIN() or MAX():
SELECT
sg.Description as Groups,
si.Store_Name,
a.Store_ID,
si.Store_ID1 as Store_2,
d.Department_ID as Dept,
a.Vendor_ID,
MAX(CASE WHEN d.Delivery_Day = 1 THEN dbo.DayOfWeekToString(d.Order_Day) END) as Sun,
MAX(CASE
WHEN d.Delivery_Day = 1
THEN CONCAT(CONVERT(varchar, d.Deadline_Hour) , ':', RIGHT('00' + CONVERT(varchar, d.Deadline_Minute), 2))
) as Sun_time,
...
FROM Delivery_Schedule_Header a
JOIN Vendor_Info b ON a.Vendor_ID = b.Vendor_ID
JOIN Delivery_Schedule_Detail d ON d.UID_Delivery = a.UID
JOIN Store_Info si ON si.Store_ID = a.Store_ID
JOIN Store_Group sg ON sg.GroupStore_UID = si.GroupStore_UID
WHERE
a.Vendor_ID = #Vendor_Id
AND si.GroupStore_UID = CASE WHEN #Group_Id = -1 THEN si.GroupStore_UID ELSE #Group_Id END
GROUP BY
sg.Description as Groups,
si.Store_Name,
a.Store_ID,
si.Store_ID1 as Store_2,
d.Department_ID as Dept,
a.Vendor_ID
Side notes:
I rewrote the IIF() expressions to use CASE (the latter is standard SQL, while the former is TSQL-specific)
single quotes should be used for litteral strings, not for column identifiers (although SQL Server tolerates this)
you have multiple columns in the resultset that have the same name (time), this is not a good practice

SQL Server : group by calculated column

I have a table with columns like this:
I want to know the average compliance rate for every question for that period.
I am passing start dates and end dates as parameters to query.
So if I want for two periods I am passing #StartDate (e.g. 1/6/17) and #EndDate (e.g. 30/6/17) for first period and #StartDate2 (1/10/17) and #EndDate2 (31/10/17) for second period.
My SQL query is:
;WITH tmpTab AS
(
SELECT
question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet,
DATENAME(DAY, #StartDate) + ' ' + DATENAME(MONTH, #StartDate) + ' ' +
DATENAME(YEAR, #StartDate) + ' To ' + DATENAME(DAY,#EndDate) + ' ' +
DATENAME(MONTH, #EndDate) + ' ' + DATENAME(YEAR, #EndDate) AS RepMonthAndYear
FROM
tableA
WHERE
startdate >= #StartDate AND endate <= #EndDate
GROUP BY
Question
UNION ALL
SELECT
question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet,
DATENAME(DAY,#StartDate2)+' '+DATENAME(MONTH,#StartDate2)+' '+DATENAME(YEAR,#StartDate2)+ ' To ' + DATENAME(DAY,#EndDate2)+' '+DATENAME(MONTH,#EndDate2)+' '+DATENAME(YEAR,#EndDate2) AS RepMonthAndYear
FROM
tableA
WHERE
startdate >= #StartDate2 AND endate <= #EndDate2
GROUP BY
Question
)
SELECT
Question, Met, NA, MetNotMet,
CASE WHEN (Met) = 0 THEN 0 ELSE ROUND(((CONVERT(FLOAT,(Met))/(MetNotMet))* 100),4) END as CompRate
FROM
tmpTab
In this SQL query, I need to group by RepMonthAndYear column also which I can not do as it is a calculated column. I get an error "invalid column".
And if I use this GROUP BY clause:
(DATENAME(DAY,#StartDate2)+' '+DATENAME(MONTH,#StartDate2)+' '+DATENAME(YEAR,#StartDate2)+ ' To ' + DATENAME(DAY,#EndDate2)+' '+DATENAME(MONTH,#EndDate2)+' '+DATENAME(YEAR,#EndDate2) )
I get this error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
How can I solve this problem?
Is there any other way to know average rate group by particular periods?
Given the following Test Data and your SQL I get what you discibe.
CREATE TABLE tableA (
question VARCHAR(50),
AnswerValue INT,
startdate DATE,
endate DATE
);
INSERT INTO tableA VALUES ('Question A', 1, '2017-06-25', '2017-06-26');
INSERT INTO tableA VALUES ('Question A', 1, '2017-06-27', '2017-06-27');
INSERT INTO tableA VALUES ('Question A', 2, '2017-06-27', '2017-06-27');
INSERT INTO tableA VALUES ('Question B', 1, '2017-06-11', '2017-06-12');
INSERT INTO tableA VALUES ('Question B', 2, '2017-06-13', '2017-06-13');
INSERT INTO tableA VALUES ('Question B', 1, '2017-06-13', '2017-06-13');
INSERT INTO tableA VALUES ('Question C', 1, '2017-06-20', '2017-06-20');
INSERT INTO tableA VALUES ('Question C', 1, '2017-06-23', '2017-06-23');
INSERT INTO tableA VALUES ('Question D', 2, '2017-06-01', '2017-06-01');
INSERT INTO tableA VALUES ('Question E', 1, '2017-10-11', '2017-10-11');
INSERT INTO tableA VALUES ('Question E', 1, '2017-10-15', '2017-10-15');
INSERT INTO tableA VALUES ('Question F', 1, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question F', 2, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question F', 2, '2017-10-20', '2017-10-20');
INSERT INTO tableA VALUES ('Question G', 1, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question H', 1, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question H', 2, '2017-10-26', '2017-10-26');
INSERT INTO tableA VALUES ('Question I', 1, '2017-10-26', '2017-10-26');
Here the outcome of your Query:
Question A 2 0 3 66,6667
Question B 2 0 3 66,6667
Question C 2 0 2 100
Question D 0 0 1 0
Question E 2 0 2 100
Question F 1 0 3 33,3333
Question G 1 0 1 100
Question H 1 0 2 50
Question I 1 0 1 100
I could simplify your SQL and have removed some code duplicates, maybe that was your intended question.
DECLARE #StartDate DATE = '2017-06-01';
DECLARE #EndDate DATE = '2017-06-30';
DECLARE #StartDate2 DATE = '2017-10-01';
DECLARE #EndDate2 DATE = '2017-10-30';
WITH
periods AS (
SELECT #StartDate as startdate,
#EndDate as enddate
UNION
SELECT #StartDate2 as startdate,
#EndDate2 as enddate
)
SELECT t.Question, t.Met, t.NA, t.MetNotMet,
CASE WHEN (t.Met) = 0 THEN 0 ELSE ROUND(((CONVERT(FLOAT,(t.Met))/(t.MetNotMet))* 100),4) END as CompRate
FROM (SELECT question,
SUM((CASE WHEN AnswerValue = 1 THEN 1 ELSE 0 END)) Met,
SUM((CASE WHEN AnswerValue = 3 THEN 1 ELSE 0 END)) NA,
SUM((CASE WHEN (ISNULL(AnswerValue,3) <> 3) THEN 1 ELSE 0 END)) MetNotMet
FROM tableA AS a
JOIN ( SELECT p.startdate,
p.enddate,
DATENAME(DAY, p.startdate) + ' ' + DATENAME(MONTH, p.startdate) + ' ' +
DATENAME(YEAR, p.startdate) + ' To ' + DATENAME(DAY, p.enddate) + ' ' +
DATENAME(MONTH, p.enddate) + ' ' + DATENAME(YEAR, p.enddate) AS RepMonthAndYear
FROM periods p ) AS p ON a.startdate >= p.startdate AND a.endate <= p.enddate
GROUP BY a.Question, p.RepMonthAndYear) AS t

SQL for islands-and-gaps: islands can overlap

I have robots with certificates. There are two kinds of certificates. For each kind of certificate (identified by Certif_ID), for each robot I need the most recent certified date-span.
Update for clarity: Date-spans that do not overlap but are contiguous are treated as a single span. See the first two records in the sample table that is at the top of the code.
Date-spans may overlap! These must be treated as a single span. This is where I'm having a problem.
in SQL Server 2012, run this code as-is to see what's happening.
BEGIN -- #certif_span
IF OBJECT_ID('TEMPDB..#certif_span') IS NOT NULL DROP TABLE #certif_span;
CREATE TABLE #certif_span
( Robot_ID CHAR(3)
, Certif_ID SMALLINT
, d_Start SMALLDATETIME
, d_End SMALLDATETIME );
INSERT INTO #certif_span VALUES ('210', '1', '2000-01-01', '2001-02-02');
INSERT INTO #certif_span VALUES ('210', '1', '2001-02-03', '2001-12-31');
INSERT INTO #certif_span VALUES ('210', '1', '2000-01-01', '2000-12-31');
INSERT INTO #certif_span VALUES ('880', '1', '2001-01-01', '2001-12-31');
INSERT INTO #certif_span VALUES ('880', '1', '2002-02-02', '2003-02-01');
INSERT INTO #certif_span VALUES ('880', '1', '2003-01-01', '2004-12-31'); -- *
INSERT INTO #certif_span VALUES ('880', '7', '2010-05-05', '2011-05-04');
INSERT INTO #certif_span VALUES ('880', '7', '2011-05-05', '2012-02-10');
INSERT INTO #certif_span VALUES ('880', '7', '2013-03-03', '2013-04-04');
INSERT INTO #certif_span VALUES ('880', '7', '2013-04-01', '2013-05-05'); -- *
-- * This line has dates that overlap with the line above
END
SELECT Robot_ID
, Certif_ID
, d_Start = FORMAT(d_Start, 'yyyy-MM-dd')
, d_End = FORMAT(d_End, 'yyyy-MM-dd')
, commentary = 'Here is the raw data'
FROM #certif_span AS cs
ORDER BY Robot_ID
, Certif_ID
, d_End
IF OBJECT_ID('TEMPDB..#prac_date_span') IS NOT NULL DROP TABLE #prac_date_span;
SELECT DISTINCT
cs.Robot_ID
, cs.Certif_ID
, cs.d_Start
, cs.d_End
INTO
--DROP TABLE --SELECT * FROM
#prac_date_span
FROM
#certif_span AS cs
GROUP BY
cs.Robot_ID
, cs.Certif_ID
, cs.d_Start
, cs.d_End
ORDER BY 1, 2, 3;
BEGIN
IF OBJECT_ID('TEMPDB..#prac_date_span_grp') IS NOT NULL
DROP TABLE #prac_date_span_grp;
WITH cte as (
SELECT
a.Robot_ID, a.Certif_ID
, a.d_Start, a.d_End
FROM
#prac_date_span a
LEFT JOIN #prac_date_span b
ON a.Robot_ID = b.Robot_ID
AND b.Certif_ID = a.Certif_ID
AND a.d_Start - 1 = b.d_End
WHERE
b.Robot_ID IS NULL
UNION ALL -----------------------------
SELECT
a.Robot_ID, a.Certif_ID
, a.d_Start, b.d_End
FROM
cte a
JOIN
#prac_date_span b
ON a.Robot_ID = b.Robot_ID
AND b.Certif_ID = a.Certif_ID
AND b.d_Start - 1 = a.d_End
)
SELECT
Robot_ID
, Certif_ID
, d_Start
, d_End = MAX(d_End)
INTO
--drop table --select * from
#prac_date_span_grp
FROM cte
GROUP BY Robot_ID, Certif_ID, d_Start
ORDER BY Robot_ID, Certif_ID;
END
SELECT
Robot_ID
, Certif_ID
, d_Start = FORMAT(d_Start, 'yyyy-MM-dd')
, d_End = FORMAT(d_End, 'yyyy-MM-dd')
, commentary = 'Here is the grouped data (flawed)'
FROM #prac_date_span_grp
SELECT
Robot_ID
, Certif_ID
, d_Start = FORMAT(MAX(d_Start), 'yyyy-MM-dd')
, d_End = FORMAT(MAX(d_End), 'yyyy-MM-dd')
, commentary = 'Final result: Start date ' +
CASE FORMAT(MAX(d_Start), 'yyyy-MM-dd')
WHEN '2003-01-01' THEN 'should be 2002-02-02'
WHEN '2013-04-01' THEN 'should be 2013-03-03'
ELSE 'good' END
FROM #prac_date_span_grp
GROUP BY Robot_ID, Certif_ID
The final result should be:
Robot_ID Certif_ID d_Start d_End
210 1 2000-01-01 2001-12-31
880 1 2002-02-02 2004-12-31
880 7 2013-03-03 2013-05-05
I've been fiddling with the date comparisons. In this bit from the cte, the -1 looks like it allows for a one-day stagger in date-spans:
AND b.Certif_ID = a.Certif_ID
AND a.d_Start - 1 = b.d_End
...
AND b.Certif_ID = a.Certif_ID
AND b.d_Start - 1 = a.d_End
I feel certain this is the point that needs fixing. I've tried changing the date compare to >=. (This requires me to deal with max recursion.) The grouping changes, but is not correct.
This is not a simple task. I hope this will answer the problem.
Declare #certif_span TABLE(Robot_ID CHAR(3), Certif_ID SMALLINT, StartDate date, EndDate date);
INSERT INTO #certif_span VALUES ('210', '1', '2000-01-01', '2001-02-02');
INSERT INTO #certif_span VALUES ('210', '1', '2001-02-03', '2001-12-31');
INSERT INTO #certif_span VALUES ('210', '1', '2000-01-01', '2000-12-31');
INSERT INTO #certif_span VALUES ('880', '1', '2001-01-01', '2001-12-31');
INSERT INTO #certif_span VALUES ('880', '1', '2002-02-02', '2003-02-01');
INSERT INTO #certif_span VALUES ('880', '1', '2003-01-01', '2004-12-31'); -- *
INSERT INTO #certif_span VALUES ('880', '7', '2010-05-05', '2011-05-04');
INSERT INTO #certif_span VALUES ('880', '7', '2011-05-05', '2012-02-10');
INSERT INTO #certif_span VALUES ('880', '7', '2013-03-03', '2013-04-04');
INSERT INTO #certif_span VALUES ('880', '7', '2013-04-01', '2013-05-05'); -- *
;with Src as(
SELECT ROW_NUMBER() Over(Partition by Robot_ID, Certif_ID order by StartDate, EndDate) as RN
,a.*
FROM #certif_span as a
)
, Islands as(
SELECT RN, Robot_ID, Certif_ID, StartDate, EndDate, 0 as islandNo, EndDate AS MovingEnd
FROM Src as a WHERE a.RN=1
UNION ALL
SELECT a.RN, a.Robot_ID, a.Certif_ID, a.StartDate, a.EndDate
, b.islandNo + CASE WHEN DATEDIFF(d, a.StartDate, b.MovingEnd)>=-1 THEN 0 ELSE 1 END as IslandNO
, CASE WHEN a.EndDate>b.MovingEnd THEN a.EndDate ELSE b.MovingEnd END as MovingEnd
FROM Src as a
INNER JOIN Islands as b on a.Robot_ID=b.Robot_ID and a.Certif_ID=b.Certif_ID and a.RN=b.RN+1
) -- SELECT * FROM Islands order by Robot_ID, Certif_ID, IslandNo
, LastIsland as(
SELECT Robot_ID, Certif_ID, islandNo, MIN(StartDate) as startDate, MAX(EndDate) as EndDate
,ROW_NUMBER() over(partition by Robot_ID, Certif_ID order by IslandNO desc) as RN
FROM Islands
Group by Robot_ID, Certif_ID, islandNo
)
SELECT Robot_ID, Certif_ID, startDate, EndDate
FROM LastIsland
where RN=1
This was a head scratcher, because it is not your typical Gaps-and-Islands, so it dawned on me to create the gaps-and-islands off the date dimension first.
Now, I do have one additional island than perhaps you were expecting. But, not matter how I look at it, it seems to hold true.
I should also note that I use a TVF (table-valued user-defined function) to create dynamic date ranges. This logic could easily be ported into a preliminary cte. A tally/calendar table would to the trick as well.
The SQL
;with cte0 as(
Select A.*,GrpSeq=RetSeq-Row_Number() over (Order by RetSeq)
From (
Select Distinct RetSeq,RetVal
From [dbo].[udf-Range-Date]((Select min(d_Start) from #certif_span),(Select max(d_End) from #certif_span),'DD',1) A
Join #certif_span B on A.RetVal between B.d_Start and B.d_End
) A
)
, cte1 as(
Select d_Start = min(A.RetVal)
,d_End = max(A.RetVal)
From cte0 A
Group By GrpSeq
)
Select Robot_ID = min(Robot_ID)
,Certif_ID = min(Certif_ID)
,A.d_Start
,A.d_End
from cte1 A
Join #certif_span B on B.d_Start Between A.d_Start and A.d_End
Group By A.d_Start,A.d_End
Returns
Robot_ID Certif_ID d_Start d_End
210 1 2000-01-01 2001-12-31
880 1 2002-02-02 2004-12-31
880 7 2010-05-05 2012-02-10 << Extra Mentioned
880 7 2013-03-03 2013-05-05
The UDF if Needed
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/

SELECT top 1 descending if DB contains current date data, else select top 1 ascending

I have a list of months that get populated into my DB. These months then get populated into a drop down list for a web Application. The client wants the current month to be the default option in the drop down list. However, the current month's data is not always available (Or ActiveFlag=Y in the database). So in that case, the client wants the default to be the first month of the year instead.
So I am testing that the drop down list and the DB table match using Selenium automation with C#. I have an SQL query that returns the Top 1 sorted in Descending order (This works to return the last active month in the year, because BroadcastMonthofYearNbr contains integers 1 through 12 month numbers):
SELECT top 1 CONCAT(RTRIM(left(M.Name, CHARINDEX(',',M.Name)-1)), ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), M.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), M.EndDate, 101), '/0', '/'), 1, 1, ''), ')')
FROM [AudienceProjections].[dbo].[BroadcastCalendarQuarterMonthMap] QMP,
AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarMonth M,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastCalendarQuarterUuid = qmp.BroadcastCalendarQuarterUuid
AND qmp.BroadcastCalendarMonthUuid = m.BroadcastCalendarMonthUuid
AND M.ActiveStatus = 'ACTIVE'
AND y.ActiveStatus = 'ACTIVE'
AND q.BroadcastQuarterType = 'STANDARD'
AND y.BroadcastCalendarYear = YEAR(GETDATE())
ORDER BY qmp.BroadcastMonthofYearNbr DESC
The query above returns this:
No Column Name
April (3/28/2016-4/24/2016)
The current month for when I am posting this question is May 5th. Notice that the above query result returns April, not May. This is because May data has not yet been imported into the DB (See below table for distinction of the data being there or not. I.e Active versus Inactive). So my query wont work in this case. I would now need a CASE THEN ELSE, or an IF EXISTS type of function, to return January if this situation occurs
Here is the Month table that you can see have May as INACTIVE:
Name StartDate EndDate ActiveStatus
January, 2016 2015-12-28 2016-01-31 ACTIVE
February, 2016 2016-02-01 2016-02-28 ACTIVE
March, 2016 2016-02-29 2016-03-27 ACTIVE
April, 2016 2016-03-28 2016-04-24 ACTIVE
May, 2016 2016-04-25 2016-05-29 INACTIVE
NOTE: I forgot about the situation of the current date being January, and if January isnt there, then the Year wouldnt be there yet either. So the condition in the query would need to take Year into account. The year table (BroadcastCalendarYear) also has an ActiveStatus column which can be used.
So if the year is also inactive, then return me a string that says "No data in DB for Year".
I assume you will be able to adapt the following to your specific schema but here is an example as order by that would work. Note you may not need and should remove some of the where statements hard to know without knowing the dataset.
Also what month should be returned if it is January of the current year and nothing is active? You may have to adapt the order by depending on that answer.
SELECT TOP 1 *
FROM
#Months
WHERE
ActiveStates = 'ACTIVE'
AND ????
ORDER BY
CASE WHEN YEAR(GETDATE()) = YearInt THEN 9999 ELSE YearInt END DESC -- Assumes you want to order by most recent to oldest and that youcould potentially have a future year loaded in the table. Othwerwuse simply use YearInt DESC
,CASE WHEN MONTH(GETDATE()) = MonthInt THEN 0 ELSE 1 END --Puts precedence on current month
,MonthInt DESC
I hope this helps.
IF OBJECT_ID(N'tempdb..#Month') IS NOT NULL
DROP TABLE #Month
CREATE TABLE #Month
(Year INT, Month VARCHAR(3), ActiveStatus CHAR(1))
INSERT INTO #Month(Year, Month, ActiveStatus)
VALUES(2016,'Jan','A')
,(2016,'Feb','A')
,(2016,'Mar','A')
,(2016,'Apr','A')
,(2016,'May','I')
;WITH x AS
(
SELECT
*,EOMONTH(CONCAT(Year,'-',Month,'-','01')) As Date
FROM #Month
)
SELECT
*
FROM X
WHERE
-- current month is active or current month is inactive then first month of the current year
Year = YEAR(GETDATE()) AND
(DATEPART(MONTH,Date)=DATEPART(MONTH,GETDATE()) AND ActiveStatus='A') OR (1=DATEPART(MONTH,Date) AND EXISTS(SELECT 1 FROM x WHERE DATEPART(MONTH,Date)=DATEPART(MONTH,GETDATE()) AND ActiveStatus='I'))
I figured it out with a long and convoluted solution, but it works.
SELECT CASE WHEN t.month = (SELECT left(M.Name, CHARINDEX(',',M.Name)-1) FROM [AudienceProjections].[dbo].[BroadcastCalendarMonth] M
WHERE StartDate <= GETDATE() AND EndDate >= GETDATE() ) THEN CONCAT(RTRIM(left(t.Name, CHARINDEX(',',t.Name)-1)), ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), t.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), t.EndDate, 101), '/0', '/'), 1, 1, ''), ')') ELSE ( SELECT CONCAT(RTRIM(left(M.Name, CHARINDEX(',',M.Name)-1)), ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), M.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), M.EndDate, 101), '/0', '/'), 1, 1, ''), ')') FROM AudienceProjections.dbo.BroadcastCalendarMonth M WHERE NAME = CONCAT( DATENAME(month,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), ', ', DATENAME(year,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) ) ) END month FROM (
SELECT top 1 left(M.Name, CHARINDEX(',',M.Name)-1) AS month,
M.Name, M.StartDate, M.EndDate
FROM [AudienceProjections].[dbo].[BroadcastCalendarQuarterMonthMap] QMP,
AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarMonth M,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastCalendarQuarterUuid = qmp.BroadcastCalendarQuarterUuid
AND qmp.BroadcastCalendarMonthUuid = m.BroadcastCalendarMonthUuid
AND M.ActiveStatus = 'ACTIVE'
AND q.BroadcastQuarterType = 'STANDARD'
AND y.ActiveStatus = 'ACTIVE'
AND y.BroadcastCalendarYear = YEAR(GETDATE())
ORDER BY qmp.BroadcastMonthofYearNbr DESC) t
Using IF EXISTS, ELSE IF EXISTS, ELSE accomplishes all 3 conditions:
IF EXISTS (
SELECT CONCAT(q.DisplayName, ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), q.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), q.EndDate, 101), '/0', '/'), 1, 1, ''), ')')
FROM AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastQuarterType = 'STANDARD'
AND y.ActiveStatus = 'ACTIVE'
AND q.ActiveStatus = 'ACTIVE'
AND y.BroadcastCalendarYear = YEAR(GETDATE())
AND Q.StartDate <= GETDATE()
and Q.EndDate >= GETDATE())
SELECT CONCAT(q.DisplayName, ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), q.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), q.EndDate, 101), '/0', '/'), 1, 1, ''), ')')
FROM AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastQuarterType = 'STANDARD'
AND y.ActiveStatus = 'ACTIVE'
AND q.ActiveStatus = 'ACTIVE'
AND y.BroadcastCalendarYear = YEAR(GETDATE())
AND Q.StartDate <= GETDATE()
and Q.EndDate >= GETDATE() ELSE IF EXISTS (
SELECT TOP 1 CONCAT(q.DisplayName, ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), q.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), q.EndDate, 101), '/0', '/'), 1, 1, ''), ')')
FROM [AudienceProjections].[dbo].[BroadcastCalendarQuarterMonthMap] QMP,
AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastCalendarQuarterUuid = qmp.BroadcastCalendarQuarterUuid
AND q.BroadcastQuarterType = 'STANDARD'
AND y.ActiveStatus = 'ACTIVE'
AND q.ActiveStatus = 'ACTIVE'
AND y.BroadcastCalendarYear = YEAR(GETDATE()) ORDER BY qmp.BroadcastMonthofYearNbr ASC ) SELECT TOP 1 CONCAT(q.DisplayName, ' (', STUFF(REPLACE('/'+CONVERT( VARCHAR(10), q.StartDate, 101), '/0', '/'), 1, 1, ''), '-', STUFF(REPLACE('/'+CONVERT(VARCHAR(10), q.EndDate, 101), '/0', '/'), 1, 1, ''), ')')
FROM [AudienceProjections].[dbo].[BroadcastCalendarQuarterMonthMap] QMP,
AudienceProjections.dbo.BroadcastCalendarYear Y,
AudienceProjections.dbo.BroadcastCalendarQuarter Q
WHERE y.BroadcastCalendarYearUuid = q.BroadcastCalendarYearUuid
AND q.BroadcastCalendarQuarterUuid = qmp.BroadcastCalendarQuarterUuid
AND q.BroadcastQuarterType = 'STANDARD'
AND y.ActiveStatus = 'ACTIVE'
AND q.ActiveStatus = 'ACTIVE'
AND y.BroadcastCalendarYear = YEAR(GETDATE())
ORDER BY qmp.BroadcastMonthofYearNbr ASC; ELSE SELECT 'No Active quarters are in the database for the current year yet';

Select count Columns group by No

Emp_No Emp_Shift Emp_Date
500 AL 1/5/2015
600 S 2/5/2015
600 H 3/5/2015
500 S 4/5/2015
500 AL 5/5/2015
600 AL 6/5/2015
I need help on this issue , HOW TO RETURN count in 3 Columns >>
EX:
Emp_No Count Al Count S Count H
500 2 1 0
600 1 1 1
this will work. you have to provide separate case statement to each condition
SQLFIDDLE for the same SQLFIDDLE
SELECT EMP_NO,
sum(CASE WHEN Emp_Shift = 'AL' THEN 1 ELSE 0 END) AS COUNT_AL,
sum(CASE WHEN Emp_Shift = 'S' THEN 1 ELSE 0 END) AS COUNT_S,
sum(CASE WHEN Emp_Shift = 'H' THEN 1 ELSE 0 END) AS COUNT_H
FROM YOUR_TABLE
GROUP BY EMP_NO;
You can try this:-
SELECT EMP_NO, COUNT(CASE
WHEN Emp_Shift = 'AL' THEN 1 ELSE 0 END AS COUNT_AL
WHEN Emp_Shift = 'S' THEN 1 ELSE 0 END AS COUNT_S
WHEN Emp_Shift = 'H' THEN 1 ELSE 0 END AS COUNT_H)
FROM YOUR_TABLE
GROUP BY EMP_NO;
Using Pivot :
declare #YOUR_TABLE TABLE
([Emp_No] int, [Emp_Shift] varchar(2), [Emp_Date] datetime)
;
INSERT INTO #YOUR_TABLE
([Emp_No], [Emp_Shift], [Emp_Date])
VALUES
(500, 'AL', '2015-01-05 00:00:00'),
(600, 'S', '2015-02-05 00:00:00'),
(600, 'H', '2015-03-05 00:00:00'),
(500, 'S', '2015-04-05 00:00:00'),
(500, 'AL', '2015-05-05 00:00:00'),
(600, 'AL', '2015-06-05 00:00:00')
;
select Emp_No,[AL] AS [Count Al],[S] As [Count S],[H] AS [Count H] from (
select Emp_No,[Emp_Shift],[Emp_Date] from #YOUR_TABLE)T
PIVOT(COUNT(Emp_Date) FOR Emp_Shift IN ([AL],[S],[H]))P
Using Pivot in Dynamic QUery :
if object_id('tempdb..#t') is not null
drop table #t
CREATE TABLE #t
([Emp_No] int, [Emp_Shift] varchar(2), [Emp_Date] datetime)
;
INSERT INTO #t
([Emp_No], [Emp_Shift], [Emp_Date])
VALUES
(500, 'AL', '2015-01-05 00:00:00'),
(600, 'S', '2015-02-05 00:00:00'),
(600, 'H', '2015-03-05 00:00:00'),
(500, 'S', '2015-04-05 00:00:00'),
(500, 'AL', '2015-05-05 00:00:00'),
(600, 'AL', '2015-06-05 00:00:00')
;
DECLARE #statement NVARCHAR(max)
,#columns NVARCHAR(max)
SELECT #columns = ISNULL(#columns + ', ', '') + N'[' + tbl.Emp_Shift + ']'
FROM (
SELECT DISTINCT [Emp_Shift]
FROM #t
) AS tbl
SELECT #statement = ' select Emp_No,[AL] AS [Count Al],[S] As [Count S],[H] AS [Count H] from (
select Emp_No,[Emp_Shift],[Emp_Date] from #t)T
PIVOT(COUNT(Emp_Date) FOR Emp_Shift IN (' + #columns + ')) as pvt'
EXEC sp_executesql #statement = #statement