SQL Server 2008 get date range from Week of year - sql

I have the below query which groups by week (Sun-Sat) and does the necessary calculation. the output obviously gives the week number of the year. As a first step I can store this data in a table, then when I want to use this data I want to convert the week number of year to the actual date range. Below is the query.
SELECT
DATEPART(WW,aa.Time) ddtt ,bb.Nd ,'Percentages' Report
,case when SUM(ZZ) = 0 then 0 else convert(decimal(18,3),SUM((CCC+PSY))*100/SUM(ZZ)) end Cond1
,case when SUM(ZZ) = 0 then 0 else convert(decimal(18,3),SUM(USN)*100/SUM(ZZ)) end Cond2
FROM db2000.dbo.Table aa join db2000.dbo.List bb on aa.Device = bb.DeviceID
where aa.Time between '2013/12/15' AND '2014/1/15 23:00' and Nd like '_s1'
group by bb.Nd ,DATEPART(WW,aa.Time)
order by ddtt
The output of this query is
ddtt Nd Report Cond1 Cond2
1 21S Percentages 94.787 63.998
1 41S Percentages 94.592 63.473
1 61S Percentages 94.356 65.845
2 21S Percentages 93.802 64.594
2 41S Percentages 94.141 65.486
2 61S Percentages 93.849 66.144
3 21S Percentages 94.572 65.940
3 41S Percentages 95.123 67.261
3 61S Percentages 95.044 67.211
51 21S Percentages 94.042 65.245
51 41S Percentages 94.857 65.847
51 61S Percentages 94.036 67.019
52 21S Percentages 94.592 65.469
52 41S Percentages 95.071 66.159
52 61S Percentages 93.932 66.989
53 21S Percentages 94.786 65.391
53 41S Percentages 95.266 66.883
53 61S Percentages 94.526 67.504
I want the column ddtt with values to represent the actual dates, for eg. 05/01/2014 - 11/01/2014. A separate query to accomplish this will be ok too.

To get the date from sunday to saturday given the week number you can use
SELECT dateadd(dd, -datepart(wk, '2014-01-08') - 1
, dateadd(ww, #weeknum, '2014-01-01'))
, dateadd(dd, -datepart(wk, '2014-01-08') - 2
, dateadd(ww, #weeknum + 1, '2014-01-01'))
where #weeknum is the week number, for the day of the week I used datepart(wk, '2014-01-08') because using the first of January will always return 1, regardless of the real day of week.
You query will become
SELECT DATEADD(dd, -DATEPART(wk, '2014-01-08') - 1
, DATEADD(ww, DATEPART(WW,aa.Time), '2014-01-01'))
, DATEADD(dd, -DATEPART(wk, '2014-01-08') - 2
, DATEADD(ww, DATEPART(WW,aa.Time) + 1, '2014-01-01'))
, bb.Nd
, 'Percentages' Report
, CASE WHEN SUM(ZZ) = 0 THEN 0
ELSE convert(decimal(18,3),SUM((CCC+PSY))*100/SUM(ZZ))
END Cond1
, CASE WHEN SUM(ZZ) = 0 THEN 0
ELSE convert(decimal(18,3),SUM(USN)*100/SUM(ZZ))
END Cond2
FROM db2000.dbo.Table aa
JOIN db2000.dbo.List bb ON aa.Device = bb.DeviceID
WHERE aa.Time BETWEEN '2013/12/15' AND '2014/1/15 23:00' AND Nd LIKE '_s1'
GROUP BY bb.Nd ,DATEPART(WW,aa.Time)
ORDER BY ddtt
or something similar if you want to join the two date in a string.

Related

SQL query MIN value in a range

I am collecting min and max pagecount values within an interval (quarterly) from printers stored in a MS SQL db.
SELECT
p_printerName AS Printer
, DATEPART(q, pmh_pollDate) AS Quartal
, DATEPART(yy, pmh_pollDate) AS Year
, MIN(pmh_pageCount) AS first
, MAX(pmh_pageCount) AS last
, MIN(pmh_pageCountMono) AS first_Mono
, MAX(pmh_pageCountMono) AS last_Mono
, MIN(pmh_pageCountColor) AS first_Color
, MAX(pmh_pageCountColor) AS last_Color
FROM [group] INNER JOIN printerGroup ON g_groupID = pg_groupID
INNER JOIN printer ON pg_printerID = p_printerID
INNER JOIN printerMeterHistory ON p_printerID = pmh_printerID
WHERE
(p_printerName LIKE 'WBMP015')
GROUP BY
g_groupName
, p_printerName
, DATEPART(yy, pmh_pollDate)
, DATEPART(q, pmh_pollDate)
The table with printer data look something like this (shortened):
p_printerName pmh_pollDate pmh_pageCount pmh_pageCountMono pmh_pageCountColor
printer1 01.10.2022 12:32 12273 7826 4447
printer1 02.10.2022 12:32 12274 7826 4448
printer1 08.10.2022 12:32 12275 7826 4449
printer1 15.10.2022 12:32 12276 7826 4450
printer1 31.10.2022 12:32 12278 7826 4452
In this example, the pagecount for printer1 in mono would be 0 and for color 5 (calculation is done in PowerQuery)
If the values in table rows incrementing like this, the result of the calculation is correct.
Printer Quartal Year first last first_Mono last_Mono first_Color last_Color
printer1 4 2022 12273 12278 7826 7826 4447 4452
Now sometimes the records showing an incorrect value (value is -1, instead):
printer1 20.10.2022 12:32 12276 -1 4450
In this case the result of pagecount for printer1 mono would be 7827 what is wrong.
Printer Quartal Year first last first_Mono last_Mono first_Color last_Color
printer1 4 2022 12273 12278 -1 7826 4447 4452
The reason for the -1 value is related how the pagecount is retrieved from the printer and unfortunately this cannot be fixed.
I need some help to modify the query finding the first (MIN) and last (MAX) records (mono, color, all) related to the beginning and end of the interval (quarterly) where the value is not -1
Use having clause:
...
GROUP BY
g_groupName
, p_printerName
, DATEPART(yy, pmh_pollDate)
, DATEPART(q, pmh_pollDate)
HAVING
(MIN(pmh_pageCountMono) <> -1)
AND (MAX(pmh_pageCountColor <> -1))
To get the NEXT min value you can do that:
Make query in a temporary table (#tmp) without any grouping, just to get all the rows.
Delete from temporary table (#tmp) records with -1 on columns of interest.
Rerun the query with "group by" over the temporary table #tmp.

SQL - SUMIF substitute?

This question is best asked using an example - if I have daily data (in this case, daily Domestic Box Office for the movie Elvis), how can I sum only the weekend values?
If the data looks like this:
Date
DBO
6/24/2022
12755467
6/25/2022
9929779
6/26/2022
8526333
6/27/2022
4253038
6/28/2022
5267391
6/29/2022
4010762
6/30/2022
3577241
7/1/2022
5320812
7/2/2022
6841224
7/3/2022
6290576
7/4/2022
4248679
7/5/2022
3639110
7/6/2022
3002182
7/7/2022
2460108
7/8/2022
3326066
7/9/2022
4324040
7/10/2022
3530965
I'd like to be able to get results that look like this:
Weekend
DBO Sum
1
31211579
2
18452612
3
11181071
Also - not sure how tricky this would be but would love to include percent change v. last weekend.
Weekend
DBO Sum
% Change
1
31211579
2
18452612
-41%
3
11181071
-39%
I tried this with CASE WHEN but I got the results in different columns, which was not what I was looking for.
SELECT
,SUM(CASE
WHEN DATE BETWEEN '2022-06-24' AND '2022-06-26' THEN index
ELSE 0
END) AS Weekend1
,SUM(CASE
WHEN DATE BETWEEN '2022-07-01' AND '2022-07-03' THEN index
ELSE 0
END) AS Weekend2
,SUM(CASE
WHEN DATE BETWEEN '2022-07-08' AND '2022-07-10' THEN index
ELSE 0
END) AS Weekend3
FROM Elvis
I would start by filtering the data on week-end days only. Then we can group by week to get the index sum ; the last step is to use window functions to compare each week-end with the previous one:
select iso_week,
row_number() over(order by iso_week) weekend_number,
sum(index) as dbo_sum,
( sum(index) - lag(sum(index) over(order by iso_week) )
/ nullif(lag(sum(index)) over(order by iso_week), 0) as ratio_change
from (
select e.*, extract(isoweek from date) iso_week
from elvis e
where extract(dayofweek from date) in (1, 7)
) e
group by iso_week
order by iso_week
Consider below
select *,
round(100 * safe_divide(dbo_sum - lag(dbo_sum) over(order by week), lag(dbo_sum) over(order by week)), 2) change_percent
from (
select extract(week from date + 2) week, sum(dbo) dbo_sum
from your_table
where extract(dayofweek from date + 2) in (1, 2, 3)
group by week
)
if applied to sample data in your question - output is

Dynamically SELECT a column based on value of row at first of month

The following query:
SELECT Confirmed, Interim, Declared, Date
FROM Interest_Hist
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
ORDER BY Date ASC
Returns the following sample data:
Confirmed Interim Declared Date
Y 0.314 0.0788 2019-08-01
0.317 0 2019-08-02
...
0.245 0 2019-08-31
0.222 0.219 2019-09-01
0.198 0 2019-09-02
...
Y 0.50 0.454 2019-12-01
0.51 0 2019-12-02
0.52 0 2019-12-03
0.53 0 2019-12-04
Where on the first of the month, Confirmed = Y, I need to return the Declared column for that month.
Note, Confirmed = Y will only exist on the first of the month. That column is blank in all other cases
Otherwise, I need to return each Interim column for the month.
Thus far, I have been able to return the SUM of either column, but not the individual values.
SELECT
CASE WHEN SUM(CASE WHEN IRc_Confirmed = 'Y' THEN 1 ELSE 0 END) = 0
THEN Interim
ELSE Declared
END AS Rate
FROM Fund_Interest
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
GROUP BY
DATEADD(month, DATEDIFF(month, 0, Date), 0), Interim, Declared
ORDER BY
DATEADD(month, DATEDIFF(month, 0, Date), 0)
The expected output given the data at the top is as follows
0.0788
0
...
0
0.222
0.198
...
0.454
0
0
0
Find all the year months where the first day is Y:
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
This gives you a list of years and months where there is a Y on the first eg Aug 2019
Now make it a cte and left join it back to your data on year and month, and where the join succeeds return declared else interim:
WITH x AS(
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
)
SELECT
CASE WHEN x.yr IS NOT NULL THEN f.declared ELSE f.interim END AS something
FROM
fund_interest f
LEFT OUTER JOIN x ON x.yr = year(f.[date]) AND x.mo = month(f.[date])
All of the rows of different days from Aug 2019 and Dec 2019 will succeed in the join. They will have a NOT NULL yr value and hence the declared will show. For all Sep 2019 rows there is no match in the join (Sep 2019 is not returned by the query in the CTE), yr is null, interim shows instead
For a better idea of what is going on do a SELECT *
If you want to use just a single column the EOMONTH function could be used to return a consistent date every month. Replace MONTH with EOMONTH. Remove calls to YEAR from the query
Do not use reserved words like DATE as column names, by the way
You can use a CTE to group by month and year and then join to your original table (Interest_Hist) on the month and year parts of your date field. You can then select the Interim or Declared value using a simple case statement
;WITH CTE AS
(
SELECT DATEPART(month, DateFld) Mnth, DATEPART(year, DateFld) Yr,
MAX(Confirmed) ConfirmedVal
FROM Interest_Hist
GROUP BY DATEPART(month, DateFld), DATEPART(year, DateFld)
)
SELECT
CASE WHEN c.ConfirmedVal= 'Y' THEN interest.Declared ELSE interest.Interim END
FROM Interest_Hist interest
INNER JOIN CTE c ON
DATEPART(month, interest.DateFld) = c.Mnth AND
DATEPART(year, interest.DateFld) = c.Yr
You can see the query in action here
This took me way longer than it probably should have.
SELECT IIF( MAX(Confirmed) OVER(PARTITION BY CONVERT(VARCHAR(6), Date, 112)) = 'Y', Declared, Interim) Interest_Rate
FROM Interest_Hist
WHERE DateBETWEEN '01-AUG-2019' AND '04-DEC-2019'
ORDER BY Date

Want specific years between date joined and now

I am trying to get the members of a company that qualify for 'EMERITUS' status.
To qualify, one must be a member for 35 years from the date joined 'JOIN_DATE' and must be >=65 years of age to qualify 'BIRTH_DATE'. I want to see >= 2015 under the 'EMERITUS' column. Does this query make sense?
SELECT
N.ID, N.FULL_NAME, N.MEMBER_TYPE,
N.JOIN_DATE,DA.BIRTH_DATE,
(SELECT CASE
WHEN DATEDIFF(YEAR,N.JOIN_DATE,GETDATE()) + 35 > DATEDIFF(YEAR,DA.BIRTH_DATE,GETDATE()) + 65
THEN CONVERT(VARCHAR(4),YEAR(N.JOIN_DATE) + 35)
WHEN DATEDIFF(YEAR,N.JOIN_DATE,GETDATE()) + 35 < DATEDIFF(YEAR,DA.BIRTH_DATE,GETDATE()) + 65
THEN CONVERT(VARCHAR(4),YEAR(DA.BIRTH_DATE) + 65)
ELSE NULL
END) AS 'EMERITUS'
Based upon the comments above it looks like you are on the right track.
Using the below SQL (with example in SQLFiddle listed) you should be able to get the year they will be EMERITUS and the number of years until EMERITUS.
select N_sub.*
,case when DATEDIFF(d,GETDATE(),N_sub.EMERITUS)/365.0 > 0
then DATEDIFF(d,GETDATE(),N_sub.EMERITUS)/365.0
else 0
end YEARS_UNTIL_EMERITUS
from(select N."ID"
,N.FULL_NAME
,N.MEMBER_TYPE
,N.JOIN_DATE
,N.BIRTH_DATE
, (select case
when DATEDIFF (d,N.JOIN_DATE, GETDATE ())/365 + 35 > DATEDIFF(d,N.BIRTH_DATE, GETDATE ())/365 + 65
then CONVERT(VARCHAR(10),DATEADD(year,35,N.BIRTH_DATE),110)
when DATEDIFF (d,N.JOIN_DATE, GETDATE ())/365 + 35 < DATEDIFF(d,N.BIRTH_DATE, GETDATE ())/365 + 65
then CONVERT(VARCHAR(10),DATEADD(year,65,N.BIRTH_DATE),110)
else null
end) AS 'EMERITUS'
from N
) N_sub
SQL Fiddle: http://sqlfiddle.com/#!6/e464cc/7
With this query it is a bit better than just raw comparing the years as it goes by # of days divided by 365. Logic could be added to account for leap years. The results will show the date they get Emeritus and the number of years until they would get it. 0 if == 0 or negative.

PIVOT SQL Server Assistance

Given the following table structure:
CrimeID | No_Of_Crimes | CrimeDate | Violence | Robbery | ASB
1 1 22/02/2011 Y Y N
2 3 18/02/2011 Y N N
3 3 23/02/2011 N N Y
4 2 16/02/2011 N N Y
5 1 17/02/2011 N N Y
Is there a chance of producing a result set that looks like this with T-SQL?
Category | This Week | Last Week
Violence 1 3
Robbery 1 0
ASB 3 1
Where last week shuld be a data less than '20/02/2011' and this week should be greater than or equal to '20/02/2011'
I'm not looking for someone to code this out for me, though a code snippet would be handy :), just some advice on whether this is possible, and how i should go about it with SQL Server.
For info, i'm currently performing all this aggregation using LINQ on the web server, but this requires 19MB being sent over the network every time this request is made. (The table has lots of categories, and > 150,000 rows). I want to make the DB do all the work and only send a small amount of data over the network
Many thanks
EDIT removed incorrect sql for clarity
EDIT Forget the above try the below
select *
from (
select wk, crime, SUM(number) number
from (
select case when datepart(week, crimedate) = datepart(week, GETDATE()) then 'This Week'
when datepart(week, crimedate) = datepart(week, GETDATE())-1 then 'Last Week'
else 'OLDER' end as wk,
crimedate,
case when violence ='Y' then no_of_crimes else 0 end as violence,
case when robbery ='Y' then no_of_crimes else 0 end as robbery,
case when asb ='Y' then no_of_crimes else 0 end as asb
from crimetable) as src
UNPIVOT
(number for crime in
(violence, robbery, asb)) as pivtab
group by wk, crime
) z
PIVOT
( sum(number)
for wk in ([This Week], [Last Week])
) as pivtab
Late to the party, but a solution with an optimal query plan:
Sample data
create table crimes(
CrimeID int, No_Of_Crimes int, CrimeDate datetime,
Violence char(1), Robbery char(1), ASB char(1));
insert crimes
select 1,1,'20110221','Y','Y','N' union all
select 2,3,'20110218','Y','N','N' union all
select 3,3,'20110223','N','N','Y' union all
select 4,2,'20110216','N','N','Y' union all
select 5,1,'20110217','N','N','Y';
Make more data - about 10240 rows in total in addition to the 5 above, each 5 being 2 weeks prior to the previous 5. Also create an index that will help on crimedate.
insert crimes
select crimeId+number*5, no_of_Crimes, DATEADD(wk,-number*2,crimedate),
violence, robbery, asb
from crimes, master..spt_values
where type='P'
create index ix_crimedate on crimes(crimedate)
From here on, check output of each to see where this is going. Check also the execution plan.
Standard Unpivot to break the categories.
select CrimeID, No_Of_Crimes, CrimeDate, Category, YesNo
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
where YesNo='Y'
Notes:
The filter on YesNo is actually applied AFTER unpivoting. You can comment it out to see.
Unpivot again, but this time select data only for last week and this week.
select CrimeID, No_Of_Crimes, Category,
Week = sign(datediff(d,CrimeDate,w.firstDayThisWeek)+0.1)
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
cross join (select DATEADD(wk, DateDiff(wk, 0, getdate()), 0)) w(firstDayThisWeek)
where YesNo='Y'
and CrimeDate >= w.firstDayThisWeek -7
and CrimeDate < w.firstDayThisWeek +7
Notes:
(select DATEADD(wk, DateDiff(wk, 0, getdate()), 0)) w(firstDayThisWeek) makes a single-column table where the column contains the pivotal date for this query, being the first day of the current week (using DATEFIRST setting)
The filter on CrimeDate is actually applied on the BASE TABLE prior to unpivoting. Check plan
Sign() just breaks the data into 3 buckets (-1/0/+1). Adding +0.1 ensures that there are only two buckets -1 and +1.
The final query, pivoting by this/last week
select Category, isnull([1],0) ThisWeek, isnull([-1],0) LastWeek
from
(
select Category, No_Of_Crimes,
Week = sign(datediff(d,w.firstDayThisWeek,CrimeDate)+0.1)
from crimes
unpivot (YesNo for Category in (Violence,Robbery,ASB)) upv
cross join (select DATEADD(wk, DateDiff(wk, 0, getdate()), -1)) w(firstDayThisWeek)
where YesNo='Y'
and CrimeDate >= w.firstDayThisWeek -7
and CrimeDate < w.firstDayThisWeek +7
) p
pivot (sum(No_Of_Crimes) for Week in ([-1],[1])) pv
order by Category Desc
Output
Category ThisWeek LastWeek
--------- ----------- -----------
Violence 1 3
Robbery 1 0
ASB 3 3
I would try this:
declare #FirstDayOfThisWeek date = '20110220';
select cat.category,
ThisWeek = sum(case when cat.CrimeDate >= #FirstDayOfThisWeek
then crt.No_of_crimes else 0 end),
LastWeek = sum(case when cat.CrimeDate >= #FirstDayOfThisWeek
then 0 else crt.No_of_crimes end)
from crimetable crt
cross apply (values
('Violence', crt.Violence),
('Robbery', crt.Robbery),
('ASB', crt.ASB))
cat (category, incategory)
where cat.incategory = 'Y'
and crt.CrimeDate >= #FirstDayOfThisWeek-7
group by cat.category;