sql server data resampling by specific frequency - sql-server

I have a dataset of stock price history, I want to organize to a specific frequency.
For example, the original dataset like as follow,
WITH CTE(ID ,[Datetime],[Open], [High], [Low], [Close]) AS (
select 'A','2015/11/30 23:51:00',11.0, 11.2, 11.0, 11.0 union all
select 'A','2015/11/30 23:53:00',11.0, 11.2, 10.8, 10.8 union all
select 'A','2015/11/30 23:54:00',10.8, 10.8, 10.4, 10.4 union all
select 'A','2015/11/30 23:55:00',10.4, 10.7, 10.4, 10.6 union all
select 'A','2015/11/30 23:57:00',10.7, 11.0, 10.7, 11.0 union all
select 'A','2015/11/30 23:58:00',11.0, 11.2, 10.8, 11.1 union all
select 'A','2015/11/30 23:59:00',11.1, 11.3, 11.0, 11.1 union ALL
select 'A','2015/12/01 00:00:00',11.1, 11.4, 11.1, 11.3
)
SELECT * FROM CTE
which is a non-completely continuous time-series of OHLC in minutes.
I want to create a SQL-SERVER procedure to resample the dataset at a specific frequency, like 3 mins as follow,
WITH CTE(ID ,[Datetime],[Open], [High], [Low], [Close]) AS (
select 'A','2015/11/30 23:51:00',11.0, 11.2, 10.8, 10.8 union all
select 'A','2015/11/30 23:54:00',10.8, 10.8, 10.4, 10.6 union all
select 'A','2015/11/30 23:57:00',10.7, 11.3, 10.7, 11.1 union all
select 'A','2015/12/01 00:00:00',11.1, 11.4, 11.1, 11.3
)
SELECT * FROM CTE
I don't have a good logic with great performance, any idea or reference is great!
I appreciate!
This is what I tried, bad logic, bad performance.
WITH CTE(ID ,[Datetime],[Open], [High], [Low], [Close]) AS (
select 'A','2015/11/30 23:51:00',11.0, 11.2, 11.0, 11.0 union all
select 'A','2015/11/30 23:53:00',11.0, 11.2, 10.8, 10.8 union all
select 'A','2015/11/30 23:54:00',10.8, 10.8, 10.4, 10.4 union all
select 'A','2015/11/30 23:55:00',10.4, 10.7, 10.4, 10.6 union all
select 'A','2015/11/30 23:57:00',10.7, 11.0, 10.7, 11.0 union all
select 'A','2015/11/30 23:58:00',11.0, 11.2, 10.8, 11.1 union all
select 'A','2015/11/30 23:59:00',11.1, 11.3, 11.0, 11.1 union ALL
select 'A','2015/12/01 00:00:00',11.1, 11.4, 11.1, 11.3
)
select
*
from(
select
*
, ROW_NUMBER() over (partition by ID, Datediff_group order by [Datetime]) as RowN
from(
select
ID, [Datetime], Datediff_group
, FIRST_VALUE([Open]) over (Partition by ID, Datediff_group order by [Datetime]) as [Open_grouped]
, MAX([High]) over (Partition by ID, Datediff_group) as [High_grouped]
, MIN([Low]) over (Partition by ID, Datediff_group) as [Low_grouped]
, FIRST_VALUE([Close]) over (Partition by ID, Datediff_group order by [Datetime] DESC) as [Close_grouped]
from(
select
*
, Datediff_MIN /3 as Datediff_group
from(
select
*
, DATEDIFF(minute, FIRST_VALUE([Datetime]) over (partition by ID order by [Datetime]), [Datetime]) as Datediff_MIN
from CTE
) as AAA
) as AA
) as A
) as B
where RowN = 1

You could use a recursive CTE to get the data out. Not sure how this would perform with a larger data set, and you would need to set the maximum recursion if there were many more rows.
WITH CTE(ID ,[time],[Open], [High], [Low], [Close]) AS (
select 'A','2015/11/30 23:51:00',11.0, 11.2, 11.0, 11.0 union all
select 'A','2015/11/30 23:53:00',11.0, 11.2, 10.8, 10.8 union all
select 'A','2015/11/30 23:54:00',10.8, 10.8, 10.4, 10.4 union all
select 'A','2015/11/30 23:55:00',10.4, 10.7, 10.4, 10.6 union all
select 'A','2015/11/30 23:57:00',10.7, 11.0, 10.7, 11.0 union all
select 'A','2015/11/30 23:58:00',11.0, 11.2, 10.8, 11.1 union all
select 'A','2015/11/30 23:59:00',11.1, 11.3, 11.0, 11.1 union ALL
select 'A','2015/12/01 00:00:00',11.1, 11.4, 11.1, 11.3),
ANCHOR AS (
SELECT MIN([time]) AS [time] FROM CTE),
RECURSION AS (
SELECT
c.*
FROM
CTE c
INNER JOIN ANCHOR a ON a.[time] =c.[time]
UNION ALL
SELECT
c.*
FROM
CTE c
INNER JOIN RECURSION r ON DATEDIFF(MINUTE, r.[time], c.[time]) = 3)
SELECT
*
FROM
RECURSION;

This would be a lot easier if your data had a primary key (data should always have a primary key!). Then you could use 1 query to identify the first (open) and last (close) key for every row, then it's a simple query to aggregate it all to 3 minute intervals.
WITH CTE(PK, ID ,[Datetime],[Open], [High], [Low], [Close]) AS (
select 1, 'A','2015/11/30 23:51:00',11.0, 11.2, 11.0, 11.0 union all
select 2, 'A','2015/11/30 23:53:00',11.0, 11.2, 10.8, 10.8 union all
select 3, 'A','2015/11/30 23:54:00',10.8, 10.8, 10.4, 10.4 union all
select 4, 'A','2015/11/30 23:55:00',10.4, 10.7, 10.4, 10.6 union all
select 5, 'A','2015/11/30 23:57:00',10.7, 11.0, 10.7, 11.0 union all
select 6, 'A','2015/11/30 23:58:00',11.0, 11.2, 10.8, 11.1 union all
select 7, 'A','2015/11/30 23:59:00',11.1, 11.3, 11.0, 11.1 union ALL
select 8, 'A','2015/12/01 00:00:00',11.1, 11.4, 11.1, 11.3
)
,CTE2 AS (
SELECT
CTE.*,
DATEDIFF(MINUTE, '2015/11/30 23:51:00', [Datetime]) / 3 AS [Sequence] -- Note the integer division,
MIN(PK) OVER (PARTITION BY DATEDIFF(MINUTE, '2015/11/30 23:51:00', [Datetime]) / 3) AS OpenPK,
MAX(PK) OVER (PARTITION BY DATEDIFF(MINUTE, '2015/11/30 23:51:00', [Datetime]) / 3) AS ClosePK
FROM
CTE
)
SELECT
CTE2.ID,
DATEADD(minute, CTE2.[Sequence] * 3, '2015/11/30 23:51:00') as [Datetime],
cteOpen.[Open],
MAX(CTE2.[High]) AS [High],
MIN(CTE2.[Low]) AS [Low],
cteClose.[Close]
FROM
CTE2
INNER JOIN CTE cteOpen ON cteOpen.PK = CTE2.OpenPK
INNER JOIN CTE cteClose ON cteClose.PK = CTE2.ClosePK
GROUP BY
CTE2.ID,
CTE2.[Sequence],
cteOpen.[Open],
cteClose.[Close]
ORDER BY
CTE2.[Sequence]
If your data includes more than one stock then you'll have to add ID into grouping/partitioning but this should be enough to get you started.

Related

Why not grouping correctly when calculating the moving average in SQL Server

I have the following code which is used to calculate the 12-month moving average. I want to calculate the 12month moving average per ACNBR. When only a single ACNBR is used the code works fine, when I try to calculate the moving average for more than one ACNBR it doesn't work anymore. Please assist. Thank you.
-Sample data code:
CREATE TABLE #RollingTotalsExample
(
[Date] DATE
,[Value] INT
,[ACNBR] INT
,[CIS] INT
);
INSERT INTO #RollingTotalsExample
SELECT '2011-01-01',626,100,12
UNION ALL SELECT '2011-02-01',231,100,12 UNION ALL SELECT '2011-03-01',572,100,12
UNION ALL SELECT '2011-04-01',775,100,12 UNION ALL SELECT '2011-05-01',660,100,12
UNION ALL SELECT '2011-06-01',662,100,12 UNION ALL SELECT '2011-07-01',541,100,12
UNION ALL SELECT '2011-08-01',849,100,12 UNION ALL SELECT '2011-09-01',632,100,12
UNION ALL SELECT '2011-10-01',906,100,12 UNION ALL SELECT '2011-11-01',961,100,12
UNION ALL SELECT '2011-12-01',361,100,12 UNION ALL SELECT '2012-01-01',461,100,12
UNION ALL SELECT '2012-02-01',928,100,12 UNION ALL SELECT '2012-03-01',855,100,12
UNION ALL SELECT '2012-04-01',605,100,12 UNION ALL SELECT '2012-05-01',83,100,12
UNION ALL SELECT '2012-06-01',44,100,12 UNION ALL SELECT '2012-07-01',382,100,12
UNION ALL SELECT '2012-08-01',862,100,12 UNION ALL SELECT '2012-09-01',549,100,12
UNION ALL SELECT '2012-10-01',632,100,12 UNION ALL SELECT '2012-11-01',2,100,12
UNION ALL SELECT '2012-12-01',26,100,12
UNION ALL SELECT '2011-01-01',626,200,12
UNION ALL SELECT '2011-02-01',231,200,12 UNION ALL SELECT '2011-03-01',572,200,12
UNION ALL SELECT '2011-04-01',775,200,12 UNION ALL SELECT '2011-05-01',660,200,12
UNION ALL SELECT '2011-06-01',662,200,12 UNION ALL SELECT '2011-07-01',541,200,12
UNION ALL SELECT '2011-08-01',849,200,12 UNION ALL SELECT '2011-09-01',632,200,12
UNION ALL SELECT '2011-10-01',906,200,12 UNION ALL SELECT '2011-11-01',961,200,12
UNION ALL SELECT '2011-12-01',361,200,12 UNION ALL SELECT '2012-01-01',461,200,12
UNION ALL SELECT '2012-02-01',928,200,12 UNION ALL SELECT '2012-03-01',855,200,12
UNION ALL SELECT '2012-04-01',605,200,12 UNION ALL SELECT '2012-05-01',83,200,12
UNION ALL SELECT '2012-06-01',44,200,12 UNION ALL SELECT '2012-07-01',382,200,12
UNION ALL SELECT '2012-08-01',862,200,12 UNION ALL SELECT '2012-09-01',549,200,12
UNION ALL SELECT '2012-10-01',632,200,12 UNION ALL SELECT '2012-11-01',2,200,12
UNION ALL SELECT '2012-12-01',26,200,12;
-code
SELECT a.[Date]
,a.ACNBR
,Value=MAX(CASE WHEN a.[Date] = b.[Date] THEN a.Value END)
,Rolling12Months=CASE
WHEN ROW_NUMBER() OVER (ORDER BY a.[Date]) < (12)
THEN NULL
ELSE avg(b.Value)
END
FROM #RollingTotalsExample a
JOIN #RollingTotalsExample b ON b.[Date] BETWEEN DATEADD(month, -11, a.[Date]) AND a.[Date]
GROUP BY a.ACNBR,a.[Date]
ORDER BY a.ACNBR,a.[Date]
Are you looking output like this?
select *, sum(value) over(partition by acnbr order by date rows between 11 preceding and current row) from #RollingTotalsExample
Else post the expected output

Sum Based on Grouping and Order

I have raw data similar to below ...
WO OP WC Time
1 10 Band Saw 2.0
1 15 Band Saw 5.0
1 17 Band Saw 10.0
1 20 CNC Lathe 6.0
1 22 Band Saw 102.0
1 30 Inspection 33.0
2 10 Band Saw 1.5
2 20 CNC Lathe 6.00
2 20 CNC Lathe 2.00
2 30 CNC Punch 0.5
2 40 Manual Ops 1.25
2 50 Inspection 0.00
I need it to Sum and Group like below. ie summing the Time for the WO/WC column until the WC changes, not a total for the overall WO. Hope I have explained that clearly enough (probably not)
We need to support SQL Server 2005.
WO WC Time
1 Band Saw 17.0
1 CNC Lathe 6.0
1 Band Saw 102.0
1 Inspection 33.0
2 Band Saw 1.5
2 CNC Lathe 8.00
2 CNC Punch 0.5
2 Manual Ops 1.25
2 Inspection 0.00
What about something like this. I build a row_number over the order you defined (WO and OP), Then, I build another row_number over each WC sorted by WO and OP. now you have two row_numbers. One over the entire set, and one over each WC. Now, when you subtract the latter from the former, you form groups wherever there is only 1 row difference, Each time there is MORe than one row, you get a new grouping. No recursion necessary.
;with t (WO, OP, WC, Time) as
(
select 1, 10, 'Band Saw', 2.0
union all select 1, 15, 'Band Saw', 5.0
union all select 1, 17, 'Band Saw', 10.0
union all select 1, 20, 'CNC Lathe', 6.0
union all select 1, 22, 'Band Saw', 102.0
union all select 1, 30, 'Inspection', 33.0
union all select 2, 10, 'Band Saw', 1.5
union all select 2, 20, 'CNC Lathe', 6.00
union all select 2, 20, 'CNC Lathe', 2.00
union all select 2, 30, 'CNC Punch', 0.5
union all select 2, 40, 'Manual Ops', 1.25
union all select 2, 50, 'Inspection', 0.00
), rn as
(
select
grp= row_number() over (order by WO, op) - row_number() over (partition by wo, wc order by wo, op),
*
from t
)
select grp, wo, wc, sum(time)
from rn
group by grp, wo, wc
Edited to use one cte. Also fixed the partition in the second order by. This outperforms an rcte with the data provided and will probably scale better because it doesnt have to recurse an arbitrary number of times.
Use a recursive CTE:
;WITH
cte1 AS
(
SELECT WO, OP, WC, Time,
ROW_NUMBER() OVER (ORDER BY WO, Op) AS RowNumber
FROM MyTable
),
cte2 AS
(
SELECT WO, OP, WC, Time, RowNumber,
1 AS GroupID
FROM cte1
WHERE RowNumber = 1
UNION ALL
SELECT cte1.WO, cte1.OP, cte1.WC, cte1.Time, cte1.RowNumber,
CASE
WHEN cte1.WC = cte2.WC THEN cte2.GroupID
ELSE cte2.GroupID + 1
END AS GroupID
FROM cte1
INNER JOIN cte2 ON cte1.RowNumber = cte2.RowNumber + 1
)
SELECT WO, WC, SUM(Time) As TotalTime
FROM cte2
GROUP BY GroupID, WO, WC
OPTION (MAXRECURSION 0)
I don't have SQL Server 2005 around to test but it should work. If you want more details, query cte1 and cte2.
Thanks to Xedni who supplied me with the bulk of the answer. I had to add the Max(op) and the sort to his supplied solution.
;with t (WO, OP, WC, Time) as
(
select 1, 10, 'Band Saw', 2.0
union all select 1, 15, 'Band Saw', 5.0
union all select 1, 17, 'Band Saw', 10.0
union all select 1, 20, 'CNC Lathe', 6.0
union all select 1, 22, 'Band Saw', 102.0
union all select 1, 30, 'Inspection', 33.0
union all select 2, 10, 'Band Saw', 1.5
union all select 2, 20, 'CNC Lathe', 6.00
union all select 2, 20, 'CNC Lathe', 2.00
union all select 2, 30, 'CNC Punch', 0.5
union all select 2, 40, 'Manual Ops', 1.25
union all select 2, 50, 'Inspection', 0.00
), rn as
(
select
grp= row_number() over (order by WO, op) - row_number() over (partition by wo, wc order by wo, op),
*
from t
)
select grp, wo, MAX(op) AS MaxOp, wc, sum(time)
from rn
group by grp, wo, wc
ORDER BY wo, MaxOp

Convert Historical Local Time to UTC Time in SQL Server

I am confronting an SQL Server database which contains many DateTime values stored in local time. (Yes, this is unfortunate!) We have perhaps 5-10 years of data, which means that the UTC offset for this location will change depending on the time of year if the region in question observes Daylight Savings Time, and of course the schedule on which that change occurred may also change, as for example it did in the United States (where most of these data originate) back in 2007.
My objective is to convert these DateTimes to UTC time at the SQL level. Short of loading the entire Olson or TZ Database and querying it, does anyone have a technique for converting an historical local timestamp to a UTC time? [If it helps, conveniently, we happen to have the latitude and longitude for each row as well (could be used to identify timezone.]
Note: for a row written in real time, the trick of DATEDIFF(Hour, Getutcdate(), GETDATE()) AS UtcOffset works fine, of course. The problem is applying this retroactively to dates that occurred on either side of the Daylight Savings Time "barrier".
You can use AT TIME ZONE to convert to UTC. SQL knows about the switches to daylight savings so it will account for it. You just have to figure out the timezone (using the latitude and longitude, as you said).
You can get all timezones from here:
SELECT * FROM sys.time_zone_info
So the solution will be something like this:
First, add a column to your table with timezone (which you find out using the latitude and longitude).
Then update your (newly added) UTC date column with AT TIME ZONE, for example:
-- some sample data to play with
CREATE TABLE #YourTable
(
LocalDateTime DATETIME,
[UtcDateTime] DATETIMEOFFSET,
TimeZoneName VARCHAR(100)
);
INSERT INTO #YourTable
(
LocalDateTime,
TimeZoneName
)
VALUES
('20150101', 'Alaskan Standard Time'),
('20150101', 'US Mountain Standard Time'),
('20190701', 'Alaskan Standard Time'),
('20190701', 'US Mountain Standard Time');
-- convert to UTC
UPDATE #YourTable
SET [UtcDateTime] = LocalDateTime AT TIME ZONE TimeZoneName AT TIME ZONE 'UTC';
-- check results
SELECT * FROM #YourTable;
This is based on a previous answer by Chris Barlow, at
SQL Server - Convert date field to UTC
This is a solution component in the form of a SQL Server 2008 view that includes a daylight savings (DST) rules approach for historical data conversion.
(No lat/long data needed.)
You can use this view to create your custom solution referencing for update, your local table columns that might need to be converted, like dbo.mytable.created_date.
Some notes on using the view are referenced below, of interest is the section "EXAMPLE USAGE - FOR HISTORICAL DATA CONVERSION":
--
-- DATETIME VS. DATETIMEOFFSET
--
-- WHERE, t = '2016-12-13 04:32:00'
--
declare
#Sydney DATETIME
set
#Sydney = '2016-12-13 04:32:00'
select
Sydney = #Sydney
declare
#Sydney_UTC DATETIMEOFFSET
set
#Sydney_UTC = '2016-12-13 04:32:00.6427663 +10:00'
select
Sydney_UTC = #Sydney_UTC
declare
#NewYork DATETIME
set
#NewYork = '2016-12-13 04:32:00:34'
select
NewYork = #NewYork
declare
#NewYork_UTC DATETIMEOFFSET
set
#NewYork_UTC = '2016-12-13 04:32:00.6427663 -04:00'
select
NewYork_UTC = #NewYork_UTC
select
DATEDIFF(hh, #Sydney, #NewYork) as DIFF_DATETIME
select
DATEDIFF(hh, #Sydney_UTC, #NewYork_UTC) as DIFF_DATETIMEOFFSET
--
-- LOCAL UTC OFFSET FOR REAL-TIME DATA TODAY
--
select
DATEDIFF( Hour, GETUTCDATE(), GETDATE() ) AS UtcOffset
--
-- LOCAL UTC DATE FOR REAL-TIME DATA TODAY - EASTERN STANDARD EXAMPLE
--
select
convert( datetimeoffset( 5 ), GETDATE(), 120 )
--
-- EXAMPLE USAGE -
--
select
*
from
vw_datetime__dst__timezone
--
-- EXAMPLE USAGE - FOR HISTORICAL DATA CONVERSION - EASTERN STANDARD
--
select
created_date,
isnull( dst.zone, 'NO TZ' ) as zone,
isnull(
case
when created_date >= dstlow and
created_date < dsthigh
then dst.daylight
else dst.standard
end,
'NO OFFSET'
) as zone_offsettime,
TODATETIMEOFFSET(
created_date,
case
when created_date >= dstlow and
created_date < dsthigh
then dst.daylight
else dst.standard
end
) as zone_time,
SWITCHOFFSET(
TODATETIMEOFFSET(
created_date,
case
when created_date >= dstlow and
created_date < dsthigh
then dst.daylight
else dst.standard
end
),
'+00:00' -- parameterize?
) as utc_time
from
(
select GETDATE() as created_date
union
select SYSDATETIMEOFFSET() as created_date
union
select '2017-01-01 15:20:24.653' as created_date
) DYNAMIC_temp_table
left outer join vw_datetime__dst__timezone dst on
created_date between yrstart and yrend and
dst.zone = 'ET'
order by
created_date
-- Here is the view SQL:
drop view
vw_datetime__dst__timezone
go
create view
vw_datetime__dst__timezone
as
select
yr,
zone,
standard,
daylight,
rulename,
strule,
edrule,
yrstart,
yrend,
dateadd(day, (stdowref + stweekadd), stmonthref) dstlow,
dateadd(day, (eddowref + edweekadd), edmonthref) dsthigh
from (
select
yrs.yr,
timezone.zone,
timezone.standard,
timezone.daylight,
timezone.rulename,
dst_rule.strule,
dst_rule.edrule,
yrs.yr + '-01-01 00:00:00' yrstart,
yrs.yr + '-12-31 23:59:59' yrend,
yrs.yr + dst_rule.stdtpart + ' ' + dst_rule.cngtime stmonthref,
yrs.yr + dst_rule.eddtpart + ' ' + dst_rule.cngtime edmonthref,
case
when dst_rule.strule in ('1', '2', '3')
then
case
when datepart(dw, yrs.yr + dst_rule.stdtpart) = '1'
then 0
else 8 - datepart(dw, yrs.yr + dst_rule.stdtpart)
end
else (datepart(dw, yrs.yr + dst_rule.stdtpart) - 1) * -1
end as stdowref,
case
when dst_rule.edrule in ('1', '2', '3')
then
case
when datepart(dw, yrs.yr + dst_rule.eddtpart) = '1'
then 0
else 8 - datepart(dw, yrs.yr + dst_rule.eddtpart)
end
else (datepart(dw, yrs.yr + dst_rule.eddtpart) - 1) * -1
end as eddowref,
datename(dw, yrs.yr + dst_rule.stdtpart) as stdow,
datename(dw, yrs.yr + dst_rule.eddtpart) as eddow,
case
when dst_rule.strule in ('1', '2', '3')
then (7 * CAST(dst_rule.strule AS Integer)) - 7
else 0
end as stweekadd,
case
when dst_rule.edrule in ('1', '2', '3')
then (7 * CAST(dst_rule.edrule AS Integer)) - 7
else 0
end as edweekadd
from (
select '1900' yr
union select '1901' yr
union select '1902' yr
union select '1903' yr
union select '1904' yr
union select '1905' yr
union select '1906' yr
union select '1907' yr
union select '1908' yr
union select '1909' yr
union select '1910' yr
union select '1911' yr
union select '1912' yr
union select '1913' yr
union select '1914' yr
union select '1915' yr
union select '1916' yr
union select '1917' yr
union select '1918' yr
union select '1919' yr
union select '1920' yr
union select '1921' yr
union select '1922' yr
union select '1923' yr
union select '1924' yr
union select '1925' yr
union select '1926' yr
union select '1927' yr
union select '1928' yr
union select '1929' yr
union select '1930' yr
union select '1931' yr
union select '1932' yr
union select '1933' yr
union select '1934' yr
union select '1935' yr
union select '1936' yr
union select '1937' yr
union select '1938' yr
union select '1939' yr
union select '1940' yr
union select '1941' yr
union select '1942' yr
union select '1943' yr
union select '1944' yr
union select '1945' yr
union select '1946' yr
union select '1947' yr
union select '1948' yr
union select '1949' yr
union select '1950' yr
union select '1951' yr
union select '1952' yr
union select '1953' yr
union select '1954' yr
union select '1955' yr
union select '1956' yr
union select '1957' yr
union select '1958' yr
union select '1959' yr
union select '1960' yr
union select '1961' yr
union select '1962' yr
union select '1963' yr
union select '1964' yr
union select '1965' yr
union select '1966' yr
union select '1967' yr
union select '1968' yr
union select '1969' yr
union select '1970' yr
union select '1971' yr
union select '1972' yr
union select '1973' yr
union select '1974' yr
union select '1975' yr
union select '1976' yr
union select '1977' yr
union select '1978' yr
union select '1979' yr
union select '1980' yr
union select '1981' yr
union select '1982' yr
union select '1983' yr
union select '1984' yr
union select '1985' yr
union select '1986' yr
union select '1987' yr
union select '1988' yr
union select '1989' yr
union select '1990' yr
union select '1991' yr
union select '1992' yr
union select '1993' yr
union select '1994' yr
union select '1995' yr
union select '1996' yr
union select '1997' yr
union select '1998' yr
union select '1999' yr
union select '2000' yr
union select '2001' yr
union select '2002' yr
union select '2003' yr
union select '2004' yr
union select '2005' yr
union select '2006' yr -- OLD US RULES
union select '2007' yr
union select '2008' yr
union select '2009' yr
union select '2010' yr
union select '2011' yr
union select '2012' yr
union select '2013' yr
union select '2014' yr
union select '2015' yr
union select '2016' yr
union select '2017' yr
union select '2018' yr
union select '2018' yr
union select '2020' yr
union select '2021' yr
union select '2022' yr
union select '2023' yr
union select '2024' yr
union select '2025' yr
union select '2026' yr
union select '2027' yr
union select '2028' yr
union select '2029' yr
union select '2030' yr
union select '2031' yr
union select '2032' yr
union select '2033' yr
union select '2034' yr
union select '2035' yr
union select '2036' yr
union select '2037' yr
union select '2038' yr
union select '2039' yr
union select '2040' yr
union select '2041' yr
union select '2042' yr
union select '2043' yr
union select '2044' yr
union select '2045' yr
union select '2046' yr
union select '2047' yr
union select '2048' yr
union select '2049' yr
union select '2050' yr
union select '2051' yr
union select '2052' yr
union select '2053' yr
union select '2054' yr
union select '2055' yr
union select '2056' yr
union select '2057' yr
union select '2058' yr
union select '2059' yr
union select '2060' yr
union select '2061' yr
union select '2062' yr
union select '2063' yr
union select '2064' yr
union select '2065' yr
union select '2066' yr
union select '2067' yr
union select '2068' yr
union select '2069' yr
union select '2070' yr
union select '2071' yr
union select '2072' yr
union select '2073' yr
union select '2074' yr
union select '2075' yr
union select '2076' yr
union select '2077' yr
union select '2078' yr
union select '2079' yr
union select '2080' yr
union select '2081' yr
union select '2082' yr
union select '2083' yr
union select '2084' yr
union select '2085' yr
union select '2086' yr
union select '2087' yr
union select '2088' yr
union select '2089' yr
union select '2090' yr
union select '2091' yr
union select '2092' yr
union select '2093' yr
union select '2094' yr
union select '2095' yr
union select '2096' yr
union select '2097' yr
union select '2098' yr
union select '2099' yr
) yrs
cross join (
-- Dynamic, hardcoded table of timezone-based, daylight savings time (DST) rules
-- -- TIMEZONE
select 'UTC' zone, '+00:00' standard, '+01:00' daylight, 'UTC' rulename -- UTC - STAGING ONLY - this line is not accurate
union select 'CET' zone, '+01:00' standard, '+02:00' daylight, 'EU' rulename -- Centeral Europe
union select 'ET' zone, '-05:00' standard, '-04:00' daylight, 'US' rulename -- Eastern Time
union select 'CT' zone, '-06:00' standard, '-05:00' daylight, 'US' rulename -- Central Time
union select 'MT' zone, '-07:00' standard, '-06:00' daylight, 'US' rulename -- Mountain Time
union select 'PT' zone, '-08:00' standard, '-07:00' daylight, 'US' rulename -- Pacific Time
) timezone
join (
-- Dynamic, hardcoded table of country-based, daylight savings time (DST) rules
select 'UTC' rulename, 'L' strule, '-03-31' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2099 lastyr, '01:00:00' cngtime
-- Country - Europe
union select 'EU' rulename, 'L' strule, '-03-31' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2099 lastyr, '01:00:00' cngtime
-- Country - US
union select 'US' rulename, '1' strule, '-04-01' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2006 lastyr, '02:00:00' cngtime
union select 'US' rulename, '2' strule, '-03-01' stdtpart, '1' edrule, '-11-01' eddtpart, 2007 firstyr, 2099 lastyr, '02:00:00' cngtime
) dst_rule on
dst_rule.rulename = timezone.rulename and
datepart( year, yrs.yr ) between firstyr and lastyr
) dst_dates
go
I used the following to convert from local Eastern time to UTC (hence the fixed values of 4 and 5 in the function). If you have pre-2007 values, then you would in fact need to modify the udf_IsInDST below to accomodate that as well.
CREATE FUNCTION [dbo].[udf_ConvertTimeLocalToUTC](#dt DATETIME)
RETURNS DATETIME
AS
BEGIN
SET #dt = DATEADD(HOUR, CASE WHEN [dbo].udf_IsInDST(#dt) = 1 THEN 4 ELSE 5 END, #dt)
RETURN #dt
END
GO
CREATE FUNCTION [dbo].[udf_IsInDST](#dt DATETIME)
RETURNS BIT
AS
BEGIN
DECLARE #returnValue BIT = 0
DECLARE #mm INT = DATEPART(MONTH, #dt)
DECLARE #dd INT = DATEPART(DAY, #dt)
DECLARE #dow INT = DATEPART(dw, #dt) -- 1 = sun
DECLARE #hr INT = DATEPART(HOUR, #dt)
SET #returnValue =
CASE WHEN #mm > 3 AND #mm < 11 THEN 1
WHEN #mm = 3 THEN
CASE WHEN #dd < 8 THEN 0
WHEN #dd >= 8 AND #dd <= 14 THEN (CASE WHEN #dow = 1 THEN (CASE WHEN #hr >= 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN #dd - #dow >= 7 THEN 1 ELSE 0 END) END)
ELSE 1
END
WHEN #mm = 11 THEN
CASE WHEN #dd < 7 THEN (CASE WHEN #dow = 1 THEN (CASE WHEN #hr < 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN #dow > #dd THEN 1 ELSE 0 END) END)
ELSE 0
END
ELSE 0
END;
RETURN #returnValue
END
GO
I've used 2 methods in the past.
The first was to create a .Net CLR that takes a datetime and timezone and returns the UTC datetime value which was stored with the data.
The second solution was only required to work for a limited number of time zones and involved creating a table consisting of time zone ID, date from, date to and the correct UTC offset for dates in the past and 20 years in the future. From there it is simple to join and apply the correct offset.

TSQL to get DISTINCT records ordered and then apply row numbers

I asked a question regarding processing lockouts in a software activation scenario where older activations are locked out when newer activations are processed.
SQL Server Stored Procedure to dump oldest X records when new records added
I ran into a problem and hopefully someone can help. Let's assume the Activations table has the following columns:
CustomerName, ProductName, KeyCode, ActivationDate
I need to get unique CustomerName, ProductName, and KeyCode data as you could activate the same machine multiple times. The result set needs to be ordered by ActivationDate DESC so I can work with the data in the order activated. So in my scenario I may allow the last two activations to work, all prior to get logged into a Lockouts table so they are locked out with the new activations being recorded.
How can I get a unique/distinct resultset ordered and THEN apply row numbers so I can iterate the resultset and discard the latest activations and work with the older activations to lock them out?
Thank you.
I think this is what you're looking for:
You want all the rows with row number that are not the max ActivationDate
DECLARE #tbl TABLE
(
CustomerName VARCHAR(20),
ProductName VARCHAR(20),
KeyCode INT,
ActivationDate DATETIME
)
INSERT INTO #tbl
SELECT 'cmp1', 'game', 28734, GETDATE() -1 UNION ALL
SELECT 'cmp1', 'game', 28734, GETDATE() -1.5 UNION ALL
SELECT 'cmp1', 'game', 28734, GETDATE() -1.2 UNION ALL
SELECT 'cmp1', 'game', 28734, GETDATE() -1.8 UNION ALL
SELECT 'cmp1', 'game', 28734, GETDATE() UNION ALL
SELECT 'cmp1', 'game', 28734, GETDATE() -17 UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() -1 UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() -1.5 UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() -1.2 UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() -1.8 UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() UNION ALL
SELECT 'cmp2', 'game', 28736, GETDATE() -17
SELECT ROW_NUMBER() OVER(ORDER BY ActivationDate DESC) RowNumber,
CustomerName,
ProductName,
KeyCode,
ActivationDate
FROM #tbl workTable
WHERE ActivationDate !=
(
SELECT MAX(ActivationDate)
FROM #tbl checkTable
WHERE workTable.CustomerName = checkTable.CustomerName
AND workTable.ProductName = checkTable.ProductName
AND workTable.KeyCode = checkTable.KeyCode
)
RowNumber CustomerName ProductName KeyCode ActivationDate
1 cmp1 game 28734 2011-02-24 08:40:45.790
2 cmp2 game 28736 2011-02-24 08:40:45.790
3 cmp2 game 28736 2011-02-24 03:52:45.793
4 cmp1 game 28734 2011-02-24 03:52:45.793
5 cmp1 game 28734 2011-02-23 20:40:45.790
6 cmp2 game 28736 2011-02-23 20:40:45.790
7 cmp2 game 28736 2011-02-23 13:28:45.790
8 cmp1 game 28734 2011-02-23 13:28:45.790
9 cmp1 game 28734 2011-02-08 08:40:45.790
10 cmp2 game 28736 2011-02-08 08:40:45.790
If you want more than one...
SELECT ROW_NUMBER() OVER(ORDER BY ActivationDate DESC) RowNumber,
CustomerName,
ProductName,
KeyCode,
ActivationDate
FROM #tbl workTable
WHERE NOT ActivationDate IN
(
SELECT TOP 2 ActivationDate
FROM #tbl checkTable
WHERE workTable.CustomerName = checkTable.CustomerName
AND workTable.ProductName = checkTable.ProductName
AND workTable.KeyCode = checkTable.KeyCode
ORDER BY ActivationDate DESC
)
I think this would do what you want:
select t.CustomerName, t.ProductName, t.KeyCode, t.MaxDate,
row_number() over (order by t.MaxDate desc) as RowNum
from (select CustomerName, ProductName, KeyCode, max(ActivationDate) as MaxDate
from Activations
group by CustomerName, ProductName, KeyCode) t
It could also be written using a CTE, which would look like:
;with cteMaxDate as (
select CustomerName, ProductName, KeyCode, max(ActivationDate) as MaxDate
from Activations
group by CustomerName, ProductName, KeyCode
)
select t.CustomerName, t.ProductName, t.KeyCode, t.MaxDate,
row_number() over (order by t.MaxDate desc) as RowNum
from cteMaxDate t

Find top sales people

I want to find Top and botton 10% sales people.How can I do this using SQL 2005 or 2008?
DECLARE #Sales TABLE
(
SalesPersonID varchar(10), TotalSales int
)
INSERT #Sales
SELECT 1, 200 UNION ALL
SELECT 2, 300 UNION ALL
SELECT 7, 300 UNION ALL
SELECT 4, 100 UNION ALL
SELECT 5, 600 UNION ALL
SELECT 5, 600 UNION ALL
SELECT 2, 200 UNION ALL
SELECT 5, 620 UNION ALL
SELECT 4, 611 UNION ALL
SELECT 3, 650 UNION ALL
SELECT 7, 611 UNION ALL
SELECT 9, 650 UNION ALL
SELECT 3, 555 UNION ALL
SELECT 9, 755 UNION ALL
SELECT 8, 650 UNION ALL
SELECT 3, 620 UNION ALL
SELECT 5, 633 UNION ALL
SELECT 6, 720
GO
Also If i add department, then how can i write same query to find top 10% and bottom 10% in each department? I please want both queries.
TOP 10 %
select top 10 percent SalesPersonID, sum(TotalSales)
from Sales
order by sum(TotalSales)
group by SalesPersonID
BOTTOM 10 %
select top 10 percent SalesPersonID, sum(TotalSales)
from Sales
order by sum(TotalSales) desc
group by SalesPersonID
--Top 10%
SELECT TOP 10 PERCENT SalesPersonID, SUM(TotalSales) FROM #Sales
GROUP BY SalesPersonID
ORDER BY SUM(TotalSales) ASC
--Bottom 10%
SELECT TOP 10 PERCENT SalesPersonID, SUM(TotalSales) FROM #Sales
GROUP BY SalesPersonID
ORDER BY SUM(TotalSales) DESC
If you added a column Department varchar(20) for example:
--By Dept
SELECT TOP 10 PERCENT Department, SUM(TotalSales) FROM #Sales
GROUP BY Department
ORDER BY SUM(TotalSales) ASC/DESC //(Whichever one you want)
cte version:
DECLARE #Sales TABLE (SalesPersonID varchar(10), TotalSales int)INSERT #Sales
SELECT 1, 200 UNION ALL
SELECT 2, 300 UNION ALL
SELECT 7, 300 UNION ALL
SELECT 4, 100 UNION ALL
SELECT 5, 600 UNION ALL
SELECT 5, 600 UNION ALL
SELECT 2, 200 UNION ALL
SELECT 5, 620 UNION ALL
SELECT 4, 611 UNION ALL
SELECT 3, 650 UNION ALL
SELECT 7, 611 UNION ALL
SELECT 9, 650 UNION ALL
SELECT 3, 555 UNION ALL
SELECT 9, 755 UNION ALL
SELECT 8, 650 UNION ALL
SELECT 3, 620 UNION ALL
SELECT 5, 633 UNION ALL
SELECT 6, 720
;with a as
(
select SalesPersonID, sum(TotalSales) as Total
from #Sales
group by SalesPersonID
)
select coalesce(a.SalesPersonID, b.SalesPersonID) as SalesPersonID, coalesce(a.Total,b.Total) as Total
from a a
full outer join a b
on a.SalesPersonID=b.SalesPersonID
where a.SalesPersonID in (select top 10 percent SalesPersonID from a order by Total desc)
or b.SalesPersonID in (select top 10 percent SalesPersonID from a order by Total)
order by a.Total desc

Resources