How do I use GROUP BY in SQL Server 2012 - sql-server

I've got two tables, one contains the list of bins and the second contains the weekdays where that bin is collected.
declare #bins table (
id int IDENTITY(1,1) PRIMARY KEY,
name nvarchar(255),
collectionCode nvarchar(255)
)
declare #collectionDays table (
id int IDENTITY(1,1) PRIMARY KEY,
weekday int,
collectionCode nvarchar(255)
)
insert into #bins (name, collectionCode) values
('Bin 1','MWF'),
('Bin 2','MWF'),
('Bin 3','ED'),
('Bin 4','ED'),
('Bin 5','ED'),
('Bin 6','ED'),
('Bin 7','ED'),
('Bin 8','ED'),
('Bin 9','ED'),
('Bin 10','MWF')
insert into #collectionDays (weekday, collectionCode) values
(0,'MWF'),
(2,'MWF'),
(4,'MWF'),
(0,'ED'),
(1,'ED'),
(2,'ED'),
(3,'ED'),
(4,'ED'),
(5,'ED'),
(6,'ED')
What I want to do is return list of all the bins with their next collection day.
I've already created this query where if returns the next collection date but it only returns the next collection date for just one bin at a time. I don't want to run this query for each bin in the database.
Here's my query
select top 1
name,
format(dateadd(day, (datediff(day, weekday, getdate()) / 7) * 7 + 7, weekday), 'dd/MM/yyyy') AS date
from #bins b
join #collectionDays c on b.collectionCode = c.collectionCode
where b.id = 1
order by date asc
If I remove top 1 and b.id = 1 condition, it'll return all the bins and next date for each weekday. If I try using group by, I get an error Column '#collectionDays.weekday' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
select
name,
format(dateadd(day, (datediff(day, weekday, getdate()) / 7) * 7 + 7, weekday), 'dd/MM/yyyy') AS date
from #bins b
join #collectionDays c on b.collectionCode = c.collectionCode
group by b.id
order by date asc
Any ideas on how I can return the next collection date for each bin in a single query?
EDIT: Updated the queries with join and other stuff

This should do it
select
name,
MIN(format(dateadd(day, (datediff(day, weekday, getdate()) / 7) * 7 + 7, weekday), 'dd/MM/yyyy')) AS [DATE]
from #bins b
join #collectionDays c on b.collectionCode = c.collectionCode
group by name
order by date asc
Basically you want to group by the name as that's one of the results you're returning, then fetch the minimum date returned for each name.
For this example name appears to be unique. In a lot of cases this is not guaranteed, so you'd want to do something like this
;with cte_nextdates as
(
select
b.id,
MIN(format(dateadd(day, (datediff(day, weekday, getdate()) / 7) * 7 + 7, weekday), 'dd/MM/yyyy')) AS [DATE]
from #bins b
join #collectionDays c on b.collectionCode = c.collectionCode
group by b.id
)
SELECT B.name,
[date]
FROM cte_nextdates ND
INNER JOIN #bins B ON B.id = ND.id
order by date asc
What this does is to group on ID rather than name.
The problem is that you can only include fields in grouped SQL queries that are either included in the group clause or passed into an aggregate function like Min or max. As Name might not be unique, we can't shouldn't group on it.
To get around that we take the result set of ID and Date returned and we join it to the Bin table to get the bin name .

Related

Repeat last value of a column, when it is empty -SSRS

Can we repeat last value of a column in SSRS? As in attachment, all blank rows in
the last column should be filled with the latest value 702
I used Previous, Last functions but nothing helped
That's achievable if you do this:
Step 1. For your source, you build a sql query where you group the data by Year, AbsoluteMonth, etc.
So for each Year / AbsoluteMonth pair the report has only ONE value.
Step 2. Use below formula:
=IIf(IsNothing(Sum(Fields!Amt.Value)), Last(Fields!Amt.Value, "Year"), Sum(Fields!Amt.Value))
Here "Year" is group name, and Amt - your field name, which is probably R_Pax
Step3. (optional) Sort the data if it's not naturally sorted to provide the correct last value.
Step 1 is very important. Otherwise the cell with empty value will not show the last total, it will show the last value for a month, so if month (1) has values 30, 50, 60, and month (2) doesn't have any values, then it will show 60 for month(2), month(3), etc..., not sum(30+50+60).
You better insert the remaining blank records with last value into your dataset before pass the data to report.I assume your table is matrix.
DECLARE #Today DATETIME
SET #Today = GETDATE()
DECLARE #MatrixData TABLE (
Month1 INT
, Year1 INT
, Value INT
)
INSERT INTO #MatrixData (Month1, Year1, Value)
SELECT MONTH(DATEADD(MONTH, Id * -1, #Today)) AS Date1Month, YEAR(DATEADD(MONTH, Id * -1, #Today)) AS Date1Year, Id * 10 AS Value1
FROM (
SELECT TOP 60 ROW_NUMBER() OVER (ORDER BY Id) AS Id
FROM SysObjects
) A
ORDER BY Date1Year, Date1Month
SELECT * FROM #MatrixData
-- Insert blank month of last year with last value
INSERT INTO #MatrixData (Month1, Year1, Value)
SELECT A.RunningMonth, A1.MaxYear, A1.LastValue
FROM (
SELECT TOP 12 ROW_NUMBER() OVER (ORDER BY Id) AS RunningMonth
FROM SysObjects
) A
INNER JOIN (
-- Get Last Value in #MatrixData
SELECT A.MinMonth, A.MaxMonth, A.MaxYear, A1.Value AS LastValue
FROM (
-- Get Max Month Last Year in #MatrixData
SELECT MAX(A1.Month1) AS MinMonth, A.MaxMonth, A.MaxYear
FROM (
-- Get Max Month & Max Year
SELECT MAX(Month1) AS MaxMonth, MAX(Year1) AS MaxYear
FROM #MatrixData
) A
INNER JOIN #MatrixData A1 ON A.MaxYear = A1.Year1
GROUP BY A.MaxMonth, A.MaxYear
) A
INNER JOIN #MatrixData A1 ON A.MinMonth = A1.Month1 AND A.MaxYear = A1.Year1
) A1 ON A.RunningMonth > A1.MinMonth AND A.RunningMonth <= A1.MaxMonth
SELECT * FROM #MatrixData
We can do it at SQL end and fetch data to SSRS
Steps:
Do pivot if needed
Get the data at granularity column. Here it is Absolute Month
Then use the SQL method to replcae the Nulls/ last values which are empty with the last highest value
Ref:
`select a.AbsoluteMonth,Mon
,first_value(a.S1_pax)over(partition by a.v1_p order by num ) as S_Pax
,first_value(a.S2_pax)over(partition by a.v2_p order by num ) as S2_Pax`
from
(select *
,sum(case when S1_pax is null then 0 else 1 end) over (order by num) as v1_p
,sum(case when S2_pax is null then 0 else 1 end) over (order by num) as v2_p
from X_Table
)a
And fill all places respectively. Plz refer below output
In Oracle it is done like this. SQL Server has both COALESCE and LAG functions. So this must be possible with SQL Server also. There is also another stackoverflow question similar to this. Just could not locate it.
create table mytab(n number, m number);
insert into mytab values(1,null);
insert into mytab values(2,null);
insert into mytab values(3,44949);
insert into mytab values(4,null);
insert into mytab values(5,null);
insert into mytab values(6,null);
insert into mytab values(7,null);
insert into mytab values(8,null);
insert into mytab values(9,null);
insert into mytab values(10,null);
insert into mytab values(11,74631);
insert into mytab values(12,null);
insert into mytab values(13,null);
select t.*, coalesce(m, lag(m ignore nulls) over (order by n))
from mytab t;

SQL Server contiguous dates - summarizing multiple rows into contiguous start and end date rows without CTE's, loops,...s

Is it possible to write an sql query that will summarize rows with start and end dates into rows that have contiguous start and end dates?
The constraint is that it has to be regular sql, i.e. no CTE's, loops and the like as a third party tool is used that only allows an sql statement to start with Select.
e.g.:
ID StartDate EndDate
1001, Jan-1-2018, Jan-04-2018
1002, Jan-5-2018, Jan-13-2018
1003, Jan-14-2018, Jan-18-2018
1004, Jan-25-2018, Feb-05-2018
The required output needs to be:
Jan-1-2018, Jan-18-2018
Jan-25-2018, Feb-05-2018
Thank you
You can take advantage of both window functions and the use of a concept called gaps-and-islands. In your case, contiguous dates would be the island, and the the gaps are self explanatory.
I wrote the answer below in a verbose way to help make it clear what the query is doing, but it could most likely be written in a different way that is more concise. Please see my comments in the answer explaining what each step (sub-query) does.
--Determine Final output
select min(c.StartDate) as StartDate
, max(c.EndDate) as EndDate
from (
--Assign a number to each group of Contiguous Records
select b.ID
, b.StartDate
, b.EndDate
, b.EndDatePrev
, b.IslandBegin
, sum(b.IslandBegin) over (order by b.ID asc) as IslandNbr
from (
--Determine if its Contiguous (IslandBegin = 1, means its not Contiguous with previous record)
select a.ID
, a.StartDate
, a.EndDate
, a.EndDatePrev
, case when a.EndDatePrev is NULL then 1
when datediff(d, a.EndDatePrev, a.StartDate) > 1 then 1
else 0
end as IslandBegin
from (
--Determine Prev End Date
select tt.ID
, tt.StartDate
, tt.EndDate
, lag(tt.EndDate, 1, NULL) over (order by tt.ID asc) as EndDatePrev
from dbo.Table_Name as tt
) as a
) as b
) as c
group by c.IslandNbr
order by c.IslandNbr
I hope following SQL query can help you to identify gaps and covered dates for given case
I did not use a CTE expression of a dates table function, etc
On the other hand, I used a numbers table using master..spt_values to generate the dates table as the main table of a LEFT join
You can create a numbers table or a dates table if it does not fit to your requirements
In the query, to catch changes between borders I used SQL LAG() function which enables me to compare with previous value of a column in a sorted list
select
max(startdate) as startdate,
max(enddate) as enddate
from (
select
date,
case when exist = 1 then date else null end as startdate,
case when exist = 0 then dateadd(d,-1,date) else null end as enddate,
( row_number() over (order by date) + 1) / 2 as rn
from (
select date, exist, case when exist <> (lag(exist,1,'') over (order by date)) then 1 else 0 end as changed
from (
select
d.date,
case when exists (select * from Periods where d.date between startdate and enddate) then 1 else 0 end as exist
from (
SELECT dateadd(dd,number,'20180101') date
FROM master..spt_values
WHERE Type = 'P' and dateadd(dd,number,'20180101') <= '20180228'
) d
) cte
) tbl
where changed = 1
) dates
group by rn
Here is the result

sum of hours and minutes which is in Date time datatype in sql server

How do I sum the time? Here is the SQL I have a problem with:
drop table #temp
Create TABLE #Temp (EmpID varchar(50),Inout varchar(50),Punchdate datetime2(0),rowid int, INTotal datetime ,Outtotal datetime)
declare #ttt int
--truncate table #Temp
--drop table #temp
;WITH timediff AS
( select ID, In_out,Punch_Date , ROW_NUMBER() OVER ( ORDER BY Punch_date) AS [row]
-- Create an index number ordered by time.
from tblCAPdata tbl
where ID='00007971' and In_Out!='Null Mode' and CONVERT(date,Punch_Date)=CONVERT(date,'2015-12-30 00:00:00')
)
insert Into #Temp
select *,
convert(varchar(8),dateadd(mi, ISNULL(DATEDIFF(MINUTE,
(SELECT other.Punch_Date
FROM timediff Other
WHERE other.[row] = timediff.[row]-1 and In_Out='In' and In_Out!='Out' ),
timediff.Punch_Date),0),0),108)
AS INTimedifferance,
-- convert(varchar(8),dateadd(mi,datediff(Minute, day_start, day_end),0),108)
CONVERT(varchar(8),dateadd(mi, ISNULL(DATEDIFF(MINUTE,
(SELECT other.Punch_Date
FROM timediff Other
WHERE other.[row] = timediff.[row]-1 and In_Out='Out' and In_Out!='In' ),
timediff.Punch_Date),0),0),108) AS OUTTimedifferance
FROM timediff
where NOT EXISTS (
SELECT *
FROM timediff omit
WHERE omit.In_Out = timediff.In_Out
AND omit.[row] = timediff.[row] - 1
);
select sum(INTotal) as v,sum(OuttotAL) from #Temp
The error message is:
Msg 8117, Level 16, State 1, Line 39
Operand data type datetime is invalid for sum operator.
PFA.
I need to take sum of In time and Out time which is in Date time type
This approach links the In and Out times to a single row for each employee IN punch, calculates the number of hours, then lets the last expression perform your desired calculation.
WITH TimesWithIds AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, In_Out ORDER BY Punch_Date) AS RowNum
FROM [Table] T1
), InAndOuts (
SELECT
InTimes.ID,
InTimes.Punch_Date AS PunchIn,
OutTimes.Punch_Date AS PuchOut,
CONVERT(DECIMAL(12,4), DATEDIFF(SS, InTimes.Punch_Date, OutTimes.PunchDate))/3600 AS PunchHours -- Be sure this is large enough for you
FROM TimesWithIds InTimes
INNER JOIN TimesWithIds OutTimes
ON InTimes.RowNum = OutTimes.RowNum
AND InTimes.ID = OutTimes.ID
WHERE InTimes.In_Out = 'In'
AND OutTimes.In_Out = 'Out'
)
SELECT
ID,
SUM(PunchHours) AS PunchHours
FROM InAndOuts
GROUP BY
ID
The example above calculates a decimal form of total hours by each employee. Add any filters and use your desired data format. Keep in mind you can't store the total hours as a true date/time - has to be some numeric type since after 24 hours, a TIME datatype would just reset.
You need to convert to seconds (using DATEDIFF) or some other time unit, then sum. Then you can convert back to datetime if you need to.

SQL - Query MAX on integer column and date specific

I am working on a report query where I need only the highest most recent row using a date range, as well as patientid, and Type_Name . The int column 'ednum' can have many rows on any particular date, i just need the row with the highest ednum for that patient in the date range.
I am using a single table attempting to drill down to show one ednum value per date (ednum value being MAX) Here is my attempt, it runs but is not giving the MAX(ednum) value but including additional rows of the same date.
MS SQL 2008
SELECT TP2.ednum, TP2.BackgroundID, TP2.Patient_No, TP2.Last_Name, TP2.Visit_Name,
TP2.SessionDT
FROM dbo.TypePatient AS TP1 INNER JOIN
(SELECT ednum, BackgroundID, CONVERT(varchar, DATE_, 101) AS SessionDT, Patient_No, Last_Name, Visit_Name
FROM dbo.TypePatient
WHERE (Visit_Name = 'Progress Note')) AS TP2 ON TP1.BackgroundID = TP2.BackgroundID AND TP1.ednum =
(SELECT TOP (100) PERCENT MAX(ednum) AS ednum
FROM dbo.TypePatient
WHERE (BackgroundID = 3304) AND (TP2.SessionDT
BETWEEN '09/20/2015' AND '09/26/2015') AND (Visit_Name = 'Progress Note')
ORDER BY TP2.SessionDT)
GROUP BY TP2.SessionDT, TP2.ednum, TP2.BackgroundID, TP2.Patient_No, TP2.Last_Name, TP2.Visit_Name, TP2.ednum
MS SQL 2008
WITH X AS
(
SELECT ednum
, BackgroundID
, Patient_No
, Last_Name
, Visit_Name
, SessionDT
, ROW_NUMBER () OVER (PARTITION BY Patient_No ORDER BY ednum DESC) rn
FROM dbo.TypePatient
WHERE Visit_Name = 'Progress Note'
AND SessionDT BETWEEN '20150920' AND '20150926'
)
SELECT * FROM X
WHERE rn = 1

T-SQL CTE self-reference CROSS APPLY previous row by date with gaps

I have an updatable table of date-value sequence (say dbo.sequence) in SQL Server 2014. Dates are unique.
When new updates come I want to distribute that values into different columns in a separate table (say dbo.distributed_values) by certain conditions, e.g. if previous value from dbo.sequence is less/greater than current dbo.sequence value, it gets inserted into specified column of dbo.distributed_values or becomes NULL in that column.
Here is the main idea:
;WITH
CTE_tbl (date, value, val_1, val_2, val_3)
AS (
SELECT ... FROM dbo.distributed_values -- get latest values from database
UNION ALL
SELECT
SEQ.date,
SEQ.value,
CASE
WHEN ABS (SEQ.value - prev.value) >= 0.5
THEN SEQ.value
ELSE NULL
END AS val_1,
...
FROM dbo.sequence AS SEQ
CROSS APPLY (SELECT * FROM CTE_tbl WHERE date = DATEADD(DAY, -1, SEQ.date)) AS prev
)
INSERT INTO dbo.distributed_values (...)
SELECT *
FROM CTE_tbl
ORDER BY date ASC
OPTION (MAXRECURSION 1000)
Seems it works mostly, but the dbo.sequence contains gaps, so I can not use things like date = DATEADD(DAY, -1, SEQ.date) to bind on previous row properly.
2012-01-04
2012-01-05
2012-01-06
2012-01-09
2012-01-10
2012-01-11
How to bind previous value correctly in case of date gaps?
UPD:
By the way, I can not use LAG ... OVER in WHERE clause, I tried. Could it be used here somehow?
Add another CTE and use that in your recursive CTE, something like this:
;WITH
SequenceWithPrevious AS(
SELECT *
,PrevValue = LAG(value,1,NULL) OVER (ORDER BY SEQ.date)
,Prevdate = LAG(date,1,NULL) OVER (ORDER BY SEQ.date)
FROM dbo.sequence AS SEQ
),
CTE_tbl (date, value, val_1, val_2, val_3)
AS (
SELECT ... FROM dbo.distributed_values -- get latest values from database
UNION ALL
SELECT ...
FROM SequenceWithPrevious AS SEQ
CROSS APPLY (SELECT * FROM CTE_tbl WHERE date = SEQ.PrevDate) AS prev
)
INSERT INTO dbo.distributed_values (...)
SELECT *
FROM CTE_tbl
ORDER BY date ASC
OPTION (MAXRECURSION 1000)

Resources