Group report together, possibly with SQL? - sql

I have a table called Register which contains the following fields:
Date, AMPM, Mark.
A day can have two records for a day. Its fairly easy to select and display all the records in a list ordered by date ascending.
What I would like to do is display the data as a grid. Something along the lines of.
| Mon | Tues| Wed| Thurs| Fri | Sat
9/8/2014 | /\ | /P | /\ | L | /\ | /
Have a week beginning and then group the 5 together. I'm not even sure sql is the best option for this, but the groupby commands seem to suggest it may be able to do this.
The Data structure is as follows.
Date, AMPM, Mark
9/8/2014, AM, /
9/8/2014, PM, \
9/9/2014, AM, /
9/9/2014, PM, P
9,10,2014, AM, /
9,10,2014, PM, \
9,11,2014, PM, L
....
The mark field can contain a number of letters. P for instance means they are participating in a sporting activity. L means they were late.
Does anyone have any resources they can point me towards the right direction that would be helpful. I'm not even sure what this type of report is called and whether I should be using SQL or javascript to group this data in a presentable format. The / \ represents AM and the a PM.

The following query would get you the desired result. If you need Sunday also, you'll have to add a small condition to test for when days_after_last_Monday = 6 in the CASE statement.
select
last_Monday Week_Starting,
max(
case
when days_after_last_Monday = 0 then mark
else null
end) Mon, --if the # of days between previous Monday and reg_date is zero, then get the according mark
max(
case
when days_after_last_Monday = 1 then mark
else null
end) Tues,
max(
case
when days_after_last_Monday = 2 then mark
else null
end) Wed,
max(
case
when days_after_last_Monday = 3 then mark
else null
end) Thurs,
max(
case
when days_after_last_Monday = 4 then mark
else null
end) Fri,
max(
case
when days_after_last_Monday = 5 then mark
else null
end) Sat
from
(
select
reg_date,
last_Monday,
julianday(reg_date) - julianday(last_Monday) as days_after_last_monday, --determine the number of days between previous Monday and reg_date
mark
from
(
select
reg_date,
case
when cast (strftime('%w', reg_date) as integer) = 1 then date(reg_date, 'weekday 1')
else date(reg_date, 'weekday 1', '-7 days')
end last_monday, --determine the date of previous Monday
mark
from
(
select
reg_date,
group_concat(mark, '') mark --concatenate am and pm marks for each reg_date
from
(
SELECT
reg_date,
ampm,
mark
FROM register
order by reg_date, ampm --order by ampm so that am rows are selected before pm
)
group by reg_date
)
)
)
group by last_Monday
order by last_Monday;
SQL Fiddle demo

Related

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

Choosing MAX value by id in a view?

I have created a simple view based on a few columns in our database
ALTER VIEW [BI].[v_RCVLI_Test] AS
Select distinct
Borger.CPRnrKort as CPR,
(...)
IndsatsDetaljer.VisitationId as VisitationsId,
Indsats.KatalogNavn as IndsatsNavn,
(case
when
(
Indsats.Model = 'SMDB2 Tilbudsmodel' or
Indsats.Model = 'SMDB2 Samtalemodel' or
Indsats.Model = 'Tilbudsmodel' or
Indsats.Model = 'NAB Tilbudsmodel'
)
then IndsatsDetaljer.ServicePeriodeStart
else IndsatsDetaljer.Ikrafttraedelsesdato
end
) as StartDato,
(case
when
(
Indsats.Model = 'SMDB2 Tilbudsmodel' or
Indsats.Model = 'SMDB2 Samtalemodel' or
Indsats.Model = 'Tilbudsmodel'
)
then (case when IndsatsDetaljer.VisitationSlut = '9999-12-31' then convert(varchar(10), getdate(), 23) else IndsatsDetaljer.VisitationSlut end)
when
Indsats.Model = 'NAB Tilbudsmodel'
then (case when IndsatsDetaljer.NABehandlingSlutDato = '9999-12-31' then convert(varchar(10), getdate(), 23) else IndsatsDetaljer.NABehandlingSlutDato end)
else (case when IndsatsDetaljer.VisitationSlut = '9999-12-31' then convert(varchar(10), getdate(), 23) else IndsatsDetaljer.VisitationSlut end)
end
) as StopDato,
Refusion.Handlekommune as Handlekommune,
replace(Refusion.Betalingskommune, 'Ukendt', 'Kendt') Betalingskommune
from nexus2.Fact_VisiteretTid as Fact
join nexus2.Dim_Paragraf Paragraf
on Fact.DW_SK_Paragraf = Paragraf.DW_SK_Paragraf
join nexus2.Dim_Indsats Indsats
on Fact.DW_SK_Indsats = Indsats.DW_SK_Indsats (...)
The cases for StartDato and StopDato are there because those dates come from different columns. I've converted the date '9999-12-31' to the the current date because we'll be doing some time calculations later on, and it's just more convenient.
CPR is the id of a person, VisitationsId is the id for the service the person received.
In theory, There should only be one StartDato and one StopDato per VisitationsId, but because of a glitch in the documentation system, we sometimes get TWO StopDato: one is the correct, and one is '9999-12-31' (now converted to current date).
So I need to group by VisitationsId and then just take the MIN value of StopDato, but I'm kind of unsure how to go about doing that?
CPR
VisitationsId
StartDato
StopDato
Something Else
123
56
2019-01-01
2019-12-12
Something
123
56
2019-01-01
9999-12-31
Something
123
58
2019-01-01
2019-12-14
Something
345
59
2018-11-01
9999-12-31
Something
345
55
2017-01-02
2017-11-12
Something
345
55
2017-01-02
9999-12-31
Something
In the above table I need to remove lines 2 and 6, because the VisitationsId is identical to the previous row, but they diverge on StopDato.
Using a group by anywhere in the query gives me an error on another (seemingly random) column telling me that the column is:
invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any suggestions on how I can go about doing this?
Add a filter which tests for this condition?
with cte (
{your current query}
)
select *
from cte T
where not (
StopDato = '9999-12-31'
and exists (
select 1
from cte T1
where T1.VisitationsId = T.VisitationsId
and StopDato != '9999-12-31'
)
);
And you look like you are converting StopDato to a varchar which is bad - you should treat dates as dates until you need to display them.

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

Dates by quarter

I would like to count number of birthdays by quarters in SQL Server
i.e. Between Jan 1 - March 31, April 1 - June 30, July 1 - Sep 30 & Oct 1 - Dec 31.
Please help me with the Date function in Sql Server to do this.
I pass BDate as DATETIME . I want to use this BDate to populate 4 fields of type int with counts for number of birthdays in each quarter.
Thanks.
As long as your "BDate" column is datetime, you can achieve this quite easily using this query:
SELECT DATEPART(QUARTER,BDATE), COUNT(*)
FROM TABLE1
GROUP BY DATEPART(QUARTER,BDATE)
ORDER BY DATEPART(QUARTER,BDATE) ASC
Here's a working fiddle using some random data: http://sqlfiddle.com/#!6/7734b/1
Data set up:
create table test (
d1 varchar(10),
d2 datetime
);
insert into test (d1,d2) values ('2015-01-28','2015-01-28');
insert into test (d1,d2) values ('2015-02-13','2015-02-13');
insert into test (d1,d2) values ('2015-07-19','2015-07-19');
insert into test (d1,d2) values ('2015-11-04','2015-11-04');
If you just want to get counts for each of the quarters present in your data:
select DATEPART(QUARTER, d2), count(*)
from test
group by DATEPART(QUARTER, d2);
You can use d1 or d2 (SQL Server will handle the varchar or datetime properly).
If you want to include all four quarters, even if they're not present in your data:
select qs.q, count(t.d2) as c
from (
SELECT 1 as q
UNION ALL
SELECT 2 as q
UNION ALL
SELECT 3 as q
UNION ALL
SELECT 4 as q) qs
left join test t
on qs.q = DATEPART(QUARTER, t.d2)
group by qs.q;
Again, you can use either d1 or d2, it doesn't matter. The "qs" query just gets the numbers 1 to 4, then that is outer joined to the table with the birth dates.
Please try this:
--replace '19000102' with your int column
select SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (1,2,3)
THEN 1
ELSE 0
END) as 'Q1',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (4,5,6)
THEN 1
ELSE 0
END) as 'Q2',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (7,8,9)
THEN 1
ELSE 0
END) as 'Q3',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (10,11,12)
THEN 1
ELSE 0
END) as 'Q4'

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;