Times / Rotas on SQL - sql

Hello I am trying to show a rota in sql but now stuck
I have tried a few things but seem way off the mark!
Essentially my data returned is
Name | Role | RotaDate | StartTime | EndTime
Joe Bloggs |Cleaner |2017-01-09 00:00:00.000|1900-01-01 19:00:00.000|1900-01-02 07:15:00.000
Joe Bloggs |Cleaner |2017-01-11 00:00:00.000|1900-01-01 19:00:00.000|1900-01-02 07:15:00.000
So this is a shift on 9th Jan from 7pm - 7:15am. There are other dates / times etc but this is just a snippet.
I would like it returned as the following (assuming he has shifts on the 11th as well for example
Name-------|Role-------| 09/01----------| 10/01 | 11/01
Joe Bloggs | Cleaner | 19:00-07:15 | |19:00-0715
Any help would be appreciated (both this and the layout!
My attempt was
select name, role, date,convert(datetime, r.timeFrom+' - '+convert(datetime,r.timeTo) as Shift
max (CASE when r.rotadate='2017-01-09' THEN timeFrom else 0 END) as '2017-01-09',
max (CASE when r.rotadate='2017-01-10' THEN timeFrom else 0 END) as '2017-01-10'
from rota
where rota date between '2017-01-09' and '2017-01-13'

Firstly i created sample data and converted the data in to required format
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
;With cte(Name,[Role],RotaDate, StartTime ,EndTime )
AS
(
SELECT 'Joe Bloggs','Cleaner' ,'2017-01-09 00:00:00.000','1900-01-01 19:00:00.000','1900-01-02 07:15:00.000' Union all
SELECT 'Joe Bloggs','Cleaner' ,'2017-01-11 00:00:00.000','1900-01-01 19:00:00.000','1900-01-02 07:15:00.000'
)
SELECT NAME
,[Role]
,LEFT(RotaDate, 10) AS RotaDate
,StartTime
,SUBSTRING(EndTime, CHARINDEX(' ', EndTime, 1) + 1, LEN(EndTime)) AS EndTime
,STUFF((
SELECT DISTINCT '- ' + CAST(SUBSTRING(StartTime, CHARINDEX(' ', StartTime, 1) + 1, 5) AS VARCHAR(19))
+ '-' + CAST(SUBSTRING(EndTime, CHARINDEX(' ', EndTime, 1) + 1, 5) AS VARCHAR(19))
FROM cte
FOR XML PATH('')
), 1, 1, '') AS StartTimeENDTime
INTO #temp
FROM cte
Then using pivot we get desired result
SELECT NAME
,[Role]
,ISNULL([2017-01-09],'') AS [09/01]
,ISNULL([2017-01-10],'') AS [10/01]
,ISNULL([2017-01-11],'') AS [11/01]
FROM (
SELECT *
FROM #temp
) AS Src
PIVOT(MIN(StartTimeENDTime) FOR RotaDate IN (
[2017-01-09]
,[2017-01-10]
,[2017-01-11]
)) AS PVT
Result
NAME Role 09/01 10/01 11/01
-----------------------------------------------------------
Joe Bloggs Cleaner 19:00-07:15 19:00-07:15

Related

SQL Group Count

I have a table like this
Date County Location
2020-01-01 abc west
2020-01-02 abc north
2020-02-01 xzy west
2020-02-02 xzy east
2020-02-03 xyz east
Can we group and count so it can become
County jan feb
abc 2
xyz 3
Location jan feb
west 1
north 1
west 1
east 2
Thank you
Try this as the base query and then write a pivot query based on this query result as shown in the demo link.
For your reference FROM - Using PIVOT and UNPIVOT.
Select country
, FORMAT([date], 'MMMM') as Month
, count(*) as Tot
from YourTable
group by country, FORMAT([date], 'MMMM')
Pivot query needed an aggregate function. Here is the complete query.
create table YourTable
([Date] Date
, Country varchar(20)
, Location varchar(20))
insert into YourTable values
('2020-01-01', 'abc', 'west'),
('2020-01-02', 'abc', 'north'),
('2020-02-01', 'xzy', 'west'),
('2020-02-02', 'xzy', 'east'),
('2020-02-03', 'xyz', 'east')
Select * into #temp from(
Select country
, FORMAT([date], 'MMMM') as Month
, count(*) as Tot
from YourTable
group by country, FORMAT([date], 'MMMM')
)a
--Select * from #temp
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Month)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT country, ' + #cols + ' from
(
select Country
, Month
, Tot
from #temp
) x
pivot
(
max(Tot)
for Month in (' + #cols + ')
) p '
execute(#query)
Live db<>fiddle demo.
Use conditional aggregation along with grouping sets:
select county, location,
sum(case when date >= '2020-01-01' and date < '2020-02-01' then 1 end) as jan,
sum(case when date >= '2020-02-01' and date < '2020-03-01' then 1 end) as feb
from t
group by grouping sets ( (country), (location) );

How to convert columns to rows in SQL Server without pivot and value is dynamic

I have some values in rows like :
Month | Product | SalesQty
-------+---------+---------
Jan-17 | ABC | 3
Feb-17 | ABC | 6
Apr-17 | ABC | 19
But i want to show the some values in columns like:
Model| Apr-17 | May-17 | Jun-17 | Jul-17
ABC 1 2 12 0
BCS 212 12 12 112
Months must be generated dynamically. Static month will not help me in this situation.
Why not Use pivot? it is simpler than other solutions like case expression:
SELECT *
FROM table
PIVOT
(
SUM(SalesQty)
FOR Month IN([Apr-17] ,[May-17], [Jun-17], [Jul-17])
) AS p;
To do it dynamically you can use the same query with dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +QUOTENAME(CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))))
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = ' SELECT *
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
PIVOT
(
SUM(SalesQty)
FOR Month IN( ' + #cols + ' )
) AS p';
execute(#query);
dynamic demo
If you don't want to use PIVOT then you can use CASE expression like this:
SELECT product,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jan17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jun17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jul17
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
GROUP BY Product;
Then to do this dynamically, you just need to replace the case expression part to by dynamic in the cols names variable.

SQL Statement to Get The Minimum DateTime from 2 String FIelds

I've got a bit of a messy table on my hands that has two fields, a date field and a time field that are both strings. What I need to do is get the minimum date from those fields, or just the record itself if there is no date/time attached to it. Here's some sample data:
ID First Last Date Time
1 Joe Smith 2013-09-06 04:00
1 Joe Smith 2013-09-06 02:00
2 Jack Jones
3 John Jack 2013-09-05 06:00
3 John Jack 2013-09-15 15:00
What I would want from a query is to get the following:
ID First Last Date Time
1 Joe Smith 2013-09-06 02:00
2 Jack Jones
3 John Jack 2013-09-05 06:00
The min date/time for ID 1 and 3 and then just ID 2 back because he doesn't have a date/time. I cam up with the following query that gives me ID's 1 and 3 exactly as I would want them:
SELECT *
FROM test as t
where
cast(t.date + ' ' + t.time as Datetime ) = (select top 1 cast(p.date + ' ' + p.time as Datetime ) as dtime from test as p where t.ID = p.ID order by dtime)
But it doesn't return row number 2 at all. I imagine there's a better way to go about doing this. Any ideas?
You can do this with row_number():
select ID, First, Last, Date, Time
from (select t.*,
row_number() over (partition by id order by date, time) as seqnum
from test t
) t
where seqnum = 1;
Although storing dates and times as strings is not recommended, you at least do it right. The values use the ISO standard format (or close enough) so alphabetic sorting is the same as date/time sorting.
Assuming [Date] and [Time] are the types I think they are, and not strings:
SELECT ID,[First],[Last],[Date],[Time] FROM
(
SELECT ID,[First],[Last],[Date],[Time],rn = ROW_NUMBER()
OVER (PARTITION BY ID ORDER BY [Date], [Time])
FROM dbo.test
) AS t WHERE rn = 1;
Example:
DECLARE #x TABLE
(
ID INT,
[First] VARCHAR(32),
[Last] VARCHAR(32),
[Date] DATE,
[Time] TIME(0)
);
INSERT #x VALUES
(1,'Joe ','Smith','2013-09-06','04:00'),
(1,'Joe ','Smith','2013-09-06','02:00'),
(2,'Jack','Jones',NULL, NULL ),
(3,'John','Jack ','2013-09-05','06:00'),
(3,'John','Jack ','2013-09-15','15:00');
SELECT ID,[First],[Last],[Date],[Time] FROM
(
SELECT ID, [First],[Last],[Date],[Time],rn = ROW_NUMBER()
OVER (PARTITION BY ID ORDER BY [Date], [Time])
FROM #x
) AS x WHERE rn = 1;
Results:
ID First Last Date Time
-- ----- ----- ---------- --------
1 Joe Smith 2013-09-06 02:00:00
2 Jack Jones NULL NULL
3 John Jack 2013-09-05 06:00:00
Try:
SELECT
*
FROM
test as t
WHERE
CAST(t.date + ' ' + t.time as Datetime) =
(
select top 1 cast(p.date + ' ' + p.time as Datetime ) as dtime
from test as p
where t.ID = p.ID
order by dtime
)
OR (t.date='' AND t.time='')

SQL Pivot data by time

I'm trying to get a pivot of my data, based on the timestamp. I want to group them into half-hour "buckets". For example, with the data below:
CREATE TABLE #test (
Employee nvarchar(20) NOT NULL
,[SaleTime] time NOT NULL
,Amount float NOT NULL
)
INSERT INTO #test VALUES
('A', '08:10', '100.50')
,('A', '12:20', '758.23')
,('A', '11:59', '592.11')
,('B', '12:00', '95.00')
,('B', '09:01', '29.10')
,('B', '09:04', '53.22')
,('C', '11:23', '55.77')
,('C', '10:40', '128.00')
I would like the result to be something like
Time | A | B | C |
-----------------------------------------------------------------
08:00 - 08:30 | 100.5 | | |
08:30 - 09:00 | | | |
09:00 - 09:30 | | 82.32 | |
09:30 - 10:00 | | | |
10:00 - 10:30 | | | |
10:30 - 11:00 | | | 128.00 |
11:00 - 11:30 | | | 55.77 |
11:30 - 12:00 | 592.11 | | |
12:00 - 12:30 | 758.23 | 95.00 | |
12:30 - 13:00 | | | |
-----------------------------------------------------------------
Do I have to create an empty table with the timeslots in order to do this? Is there a method of doing this without using CASE WHEN?
Thanks!
Maybe I'm try to solve a non-existent problem but I'd like to offer an alternative solution which is dynamic in respect to the number of employees (it adds columns for extra employees) and sums the amounts sold in a time slot per employee if there's more than one sale in that slot; if you don't aggregate the amount you'll end up with multiple rows for every time slot where someone has sold more than one time.
The time slot generation is borrowed from the excellent solution in Nenads answer.
First the the test data with a couple of extra rows to illustrate the difference:
DROP TABLE #test;
CREATE TABLE #test (
Employee nvarchar(20) NOT NULL
,[SaleTime] time NOT NULL
,Amount float NOT NULL
)
INSERT INTO #test VALUES
('A', '08:10', '100.50')
,('A', '12:20', '758.23')
,('A', '11:59', '592.11')
,('B', '12:00', '95.00')
,('B', '09:01', '29.10')
,('B', '09:04', '53.22')
,('C', '11:23', '55.77')
,('C', '10:40', '128.00')
,('D', '09:40', '28.00')
,('E', '11:40', '50.00')
,('E', '11:35', '20.00')
The query build a SQL statement dynamically and executes it with the EXECUTE statement:
DECLARE #Headers VARCHAR(MAX)
SELECT #Headers = COALESCE(#Headers + ',[' + Employee + ']', '[' + Employee + ']')
FROM (SELECT DISTINCT Employee FROM #test) Emp
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'
WITH CTE_TimeSlots AS
(
SELECT CAST(''8:00'' AS TIME) AS StartTime, CAST(''8:30'' AS TIME) AS EndTime
UNION ALL
SELECT DATEADD(MI, 30, StartTime), DATEADD(MI, 30, EndTime)
FROM CTE_TimeSlots
WHERE StartTime <= ''12:00''
)
SELECT * FROM (
SELECT CONVERT(NVARCHAR(10),StartTime) + '' - '' + CONVERT(NVARCHAR(10),EndTime) AS [Time], Amount, Employee
FROM CTE_TimeSlots t
LEFT JOIN #test d ON d.SaleTime >= StartTime AND d.SaleTime < EndTime
) innerQuery
PIVOT (SUM(Amount) FOR Employee IN (' + #Headers + ')
) AS PivotTable
'
--PRINT #SQL -- Uncomment to see the query which will be run
EXECUTE(#SQL)
You can use recursive CTE to create your time slots 'on the fly' and then join it to your data. There is a way to avoid using CASE, you can use PIVOT command instead, but I think this is much simpler:
WITH CTE_TimeSlots AS
(
SELECT CAST('8:00' AS TIME) AS StartTime, CAST('8:30' AS TIME) AS EndTime
UNION ALL
SELECT DATEADD(MI, 30, StartTime), DATEADD(MI, 30, EndTime)
FROM CTE_TimeSlots
WHERE StartTime <= '12:00'
)
SELECT CONVERT(NVARCHAR(10),StartTime) + ' - ' + CONVERT(NVARCHAR(10),EndTime) AS [Time]
, CASE WHEN Employee = 'A' THEN Amount END AS A
, CASE WHEN Employee = 'B' THEN Amount END AS B
, CASE WHEN Employee = 'C' THEN Amount END AS C
FROM CTE_TimeSlots t
LEFT JOIN #test d ON d.SaleTime >= StartTime AND d.SaleTime < EndTime
SQLFiddle DEMO

How to apply pivot in below scenarios

I have below table
Name Month Year Count
----------------------------
xxx 12 2012 24
xxx 1 2013 42
xxx 2 2013 23
yyy 12 2012 34
yyy 1 2013 12
yyy 2 2013 54
I would like to convert it into below format,
Name Dec-12 Jan-13 Feb-13
--------------------------------
xxx 24 42 23
yyy 34 12 54
How to apply pivot?
Since you are using SQL Server there are several ways that you can pivot the data from rows into columns.
If your values are limited or you have a known number of values, then you can hard-code the values with a static pivot:
select name, [Dec_12], [Jan_13], [Feb_13]
from
(
select name,
left(datename(month, dateadd(month, month, 0) -1), 3) +'_'+right(cast(year as varchar(4)), 2) MY,
[count]
from yourtable
) src
pivot
(
sum(count)
for my in ([Dec_12], [Jan_13], [Feb_13])
) piv;
See SQL Fiddle with Demo.
Now, if you have an unknown number of values, then you will need to implement dynamic SQL to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(my)
from
(
select left(datename(month, dateadd(month, month, 0) -1), 3) +'_'+right(cast(year as varchar(4)), 2) my,
CAST(
CAST(year AS VARCHAR(4)) +
RIGHT('0' + CAST(month AS VARCHAR(2)), 2) +
'01'
AS DATETIME) fulldate
from yourtable
) t
group by my, fulldate
order by fulldate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name, ' + #cols + '
from
(
select name,
left(datename(month, dateadd(month, month, 0) -1), 3) +''_''+right(cast(year as varchar(4)), 2) MY,
[count]
from yourtable
) x
pivot
(
sum(count)
for my in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
This difference with this and the static version is if you need an unknown number of dates or want this to automatically update with new dates when they are available, this will return the new data without changing the code.
The result of both queries is:
| NAME | DEC_12 | JAN_13 | FEB_13 |
-----------------------------------
| xxx | 24 | 42 | 23 |
| yyy | 34 | 12 | 54 |
Try this:
WITH CTE
AS
(
SELECT
Name,
CAST(Month AS VARCHAR(2)) + '-' + CAST(Year AS VARCHAR(4)) AS MonthYear,
[Count]
FROM tablename
)
SELECT
Name,
[12-2012] AS 'Dec-12',
[1-2013] AS 'Jan-13',
[2-2013] AS 'Feb-13'
FROM CTE
PIVOT
(
MAX([Count])
FOR MonthYear IN([12-2012],
[1-2013],
[2-2013])
) AS p;
SQL Fiddle Demo
SELECT t.name
, MAX(CASE
WHEN t.month=12 AND t.year = 2012
THEN count
ELSE NULL
END) AS "Dec_12"
, MAX(CASE
WHEN t.month=1 AND t.year = 2013
THEN count
ELSE NULL
END) AS "Jan_13"
, MAX(CASE
WHEN t.month=2 AND t.year = 2013
THEN count
ELSE NULL
END) AS "Feb_13"
FROM table t
GROUP BY t.name
;