SSRS create Age range - sql-server-2008

I'm trying to make a report that will display how many patients came in during a specific time frame for an age range. This is what I got so far, but the numbers its outputting are wrong, so I'm not sure what I missed. I've followed a couple of examples on here, but none have worked so far. Not sure if its cause I'm Joining to a different table or what.
select COUNT (DISTINCT MPFILE.PATIENT_NO) as 'Male 0-4'
from ENHFILE
Join MPFILE
on MPFILE.PATIENT_NO = ENHFILE.PATIENT_NO
where ENHFILE.COSITE = '300'
and ENHFILE.VISIT_PURPOSE = '2'
and MPFILE.SEX = 'M'
and (DATEDIFF(hour,MPFILE.DOB,GETDATE())/8766) > 5
and ENHFILE.ENCOUNTER_DATE between (#StartDate) and (#EndDate)
select COUNT (DISTINCT MPFILE.PATIENT_NO) as 'FeMale 0-4'
from ENHFILE
Join MPFILE
on MPFILE.PATIENT_NO = ENHFILE.PATIENT_NO
where ENHFILE.COSITE = '300'
and ENHFILE.VISIT_PURPOSE = '2'
and MPFILE.SEX = 'F'
and (DATEDIFF(hour,MPFILE.DOB,GETDATE())/8766) > 5
and ENHFILE.ENCOUNTER_DATE between (#StartDate) and (#EndDate)

Here is something that should get you what you want.
--First i just created some test data
if object_id('tempdb..#temp') is not null drop table #temp
select '8/15/1995' as DOB into #temp union all --Note this person is 21
select '8/16/1995' union all --Note this person is 21 TODAY
select '8/17/1995' union all --Note this person is 21 TOMORROW
select '4/11/1996' union all
select '5/15/1997' union all
select '9/7/2001'
--set the years old you want to use here. Create another variable if you need to use ranges
declare #yearsOld int
set #yearsOld = 21
select
convert(date,DOB) as DOB,
--This figures out how old they are by seeing if they have had a birthday
--this year and calculating accordingly. It is what is used in the filter
--I only put it here so you can see the results
CASE
WHEN CONVERT(DATE, CONVERT(VARCHAR(4), YEAR(GetDate()))+ '-'+ CONVERT(VARCHAR(2),MONTH(DOB)) + '-' + CONVERT(VARCHAR(2),DAY(DOB))) <= GETDATE() THEN DATEDIFF(yy,DOB,GETDATE())
ELSE DATEDIFF(yy,DOB,GETDATE()) -1
END AS YearsOld
from #temp
where
--here is your filter. Feel free to change the >= to what ever you want, or combine it to make it a range.
CASE
WHEN CONVERT(DATE, CONVERT(VARCHAR(4), YEAR(GetDate()))+ '-'+ CONVERT(VARCHAR(2),MONTH(DOB)) + '-' + CONVERT(VARCHAR(2),DAY(DOB))) <= GETDATE() THEN DATEDIFF(yy,DOB,GETDATE())
ELSE DATEDIFF(yy,DOB,GETDATE()) -1
END >= #yearsOld
EDIT
This is your method which doesn't account for if they have had a birthday this year. I use some test data. Notice the person born on 8/18/1995. They turn 21 tomorrow but using (DATEDIFF(hour,DOB,GETDATE())/8766) >= #yearsOld includes them when it shouldn't...
--First i just created some test data
if object_id('tempdb..#temp') is not null drop table #temp
select '8/15/1995' as DOB into #temp union all --Note this person is 21
select '8/16/1995' union all --Note this person is 21 TODAY
select '8/18/1995' union all --Note this person is 21 TOMORROW
select '4/11/1996' union all
select '5/15/1997' union all
select '9/7/2001'
--set the years old you want to use here. Create another variable if you need to use ranges
declare #yearsOld int
set #yearsOld = 21
select
convert(date,DOB) as DOB,
--This figures out how old they are by seeing if they have had a birthday
--this year and calculating accordingly. It is what is used in the filter
--I only put it here so you can see the results
CASE
WHEN CONVERT(DATE, CONVERT(VARCHAR(4), YEAR(GetDate()))+ '-'+ CONVERT(VARCHAR(2),MONTH(DOB)) + '-' + CONVERT(VARCHAR(2),DAY(DOB))) <= GETDATE() THEN DATEDIFF(yy,DOB,GETDATE())
ELSE DATEDIFF(yy,DOB,GETDATE()) -1
END AS YearsOld
from #temp
where
--here is your filter. Feel free to change the >= to what ever you want, or combine it to make it a range.
(DATEDIFF(hour,DOB,GETDATE())/8766) >= #yearsOld
RESULTS
DOB | YearsOld
1995-08-15 | 21
1995-08-16 | 21
1995-08-18 | 20 --this shouldn't be here...

Related

MYSQL - Filter consecutive not null dates

Get only the biggest date:
These are check-in and check-out records of employees, some times they do twice or more entries on the system in a row. In this sample there were two check-out in a row. Assuming these rows always gonna be ordered, in the case of check-out I would like have the biggest date, and in the case of the check-in the smallest date.
In that case I would like to have this:
The smaller date was excluded:
DEMO
Try this, in this big CASE statement I increment column by one, if checkin switches from null to not null and the other way around. Then it's enough to group by this column taking max and min of checkout and checkin respectively:
select #checkinLag := null, #rn := 0;
select max(id),
functionario,
loja,
min(checkin),
max(checkout)
from (
select case when (checkinLag is null and checkin is not null) or
(checkinLag is not null and checkin is null)
then #rn := #rn + 1 else #rn end rn,
checkin,
checkout,
loja,
id,
functionario
from (
select #checkinLag checkinLag,
#checkinLag := checkin,
checkin,
checkout,
loja,
id,
functionario
from dummyTable
order by coalesce(checkin, checkout)
) a
) a group by functionario, loja, rn
I have used subqueries, to guarantee order of evaluating expressions (assigning and using of #checkinLag), as Gordon Linoff pointed.
Demo
My solution:
Select
*
from dummyTable base
where (base.checkout is null or not exists (
select
1
from dummyTable co
where co.checkout between base.checkout and DATE_ADD(base.checkout, INTERVAL 5 SECOND)
and base.id <> co.id
and base.functionario = co.functionario
and base.loja = co.loja
)) and (base.checkin is null or not exists (
select
1
from dummyTable ci
where ci.checkin between DATE_SUB(base.checkin, INTERVAL 5 SECOND) and base.checkin
and base.id <> ci.id
and base.functionario = ci.functionario
and base.loja = ci.loja
));
you can test the query here. There is no need that the rows are orderd. I choose 5 seconds as the interval where check-in/outs should be ignored.

Find an average age range over a span of ages

I need to find the most occurrences in a 10yr age range that can be Age 2 to 22, 15 to 25, 10 to 20, etc. in a table with name & age
I've created the SQL that returns the average age:
SELECT age, count(age)
FROM member
GROUP BY age
ORDER BY COUNT(age) DESC
LIMIT 1
Thanks for your help!
Create another table ages to hold the age ranges you are interested in with a field for age_lower, age_upper and a display name age_range such as '2 to 22'
Join the tables with a WHERE clause that puts the age between the lower and upper ranges.
SELECT `age_range`, COUNT(`age`) AS age_count
FROM `member` INNER JOIN `ages`
ON age BETWEEN age_lower AND age_upper
GROUP BY age_range
ORDER BY COUNT(`age`) DESC, `age_range` ASC
SQL Fiddle
This might solve the problem. The only thing I added was a table to hold values 1..x where x is your bucket count. The #T can easily be replaced with your MySQL table name. The results are all possible sets the age falls in, for each age. Then count of how many equal sets.
--IGNORE BUILDING TEST DATA IN SQL SERVER
DECLARE #T TABLE(member INT,age INT)
DECLARE #X INT
SET #X=1
WHILE(#X<=100) BEGIN
INSERT INTO #T SELECT #X, CAST(RAND() * 100 AS INT)
SET #X=#X+1
END
DECLARE #MinAge INT=1
DECLARE #MaxAge INT=100
--YOUR SET TABLE. TO MAKE LIFE EASY YOU NEED A TABLE OF 1..X
DECLARE #SET TABLE (Value INT)
DECLARE #SET_COUNT INT =10
DECLARE #LOOP INT=1
WHILE(#LOOP<=#SET_COUNT) BEGIN
INSERT #SET SELECT #LOOP
SET #LOOP=#LOOP+1
END
SELECT
MinAge,
MaxAge,
SetCount=COUNT(CountFlag)
FROM
(
SELECT
MinAge=AgeMinusSetCount,
MaxAge=AgePlusSetCount,
CountFlag=1
FROM
(
SELECT DISTINCT
ThisAge,
AgeMinusSetCount=(AgeMinusSetCount-1) + Value,
AgePlusSetCount=CASE WHEN (AgeMinusSetCount-1) + Value + #SET_COUNT > #MaxAge THEN #MaxAge ELSE (AgeMinusSetCount-1) + Value + #SET_COUNT END
FROM
(
SELECT
ThisAge=age,
AgeMinusSetCount=CASE WHEN (age - #SET_COUNT) < #MinAge THEN #MinAge ELSE (age) - #SET_COUNT END
FROM
#T
)RANGES
LEFT OUTER JOIN (SELECT Value FROM #SET) AS FanLeft ON 1=1
)AS DETAIL
)AS Summary
GROUP BY
MinAge,
MaxAge
ORDER BY
COUNT(CountFlag) DESC

Calculate Date by number of working days from a certain Startdate

I've got two tables, a project table and a calendar table. The first containts a startdate and days required. The calendar table contains the usual date information, like date, dayofweek, and a column is workingday, which shows if the day is a saturday, sunday, or bank holiday (value = 0) or a regular workday (value = 1).
For a certain report I need write a stored procedure that calculates the predicted enddate by adding the number of estimated workddays needed.
Example:
**Projects**
Name Start_Planned Work_days_Required
Project A 02.05.2016 6
Calendar (04.05 is a bank holdiday)
Day Weekday Workingday
01.05.2016 7 0
02.05.2016 1 1
03.05.2016 2 1
04.05.2016 3 0
05.05.2016 4 1
06.05.2016 5 1
07.05.2016 6 0
08.05.2016 7 0
09.05.2016 1 1
10.05.2016 2 1
Let's say, the estimated number of days required is given as 6 (which leads to the predicted enddate of 10.05.2016). Is it possible to join the tables in a way, which allows me to put something like
select date as enddate_predicted
from calendar
join projects
where number_of_days = 6
I would post some more code, but I'm quite stuck on how where to start.
Thanks!
You could get all working days after your first date, then apply ROW_NUMBER() to get the number of days for each date:
SELECT Date, DayNum = ROW_NUMBER() OVER(ORDER BY Date)
FROM Calendar
WHERE IsWorkingDay = 1
AND Date >= #StartPlanned
Then it would just be a case of filtering for the 6th day:
DECLARE #StartPlanned DATE = '20160502',
#Days INT = 6;
SELECT Date
FROM ( SELECT Date, DayNum = ROW_NUMBER() OVER(ORDER BY Date)
FROM Calendar
WHERE WorkingDay = 1
AND Date >= #StartPlanned
) AS c
WHERE c.DayNum = #Days;
It's not part of the question, but for future proofing this is easier to acheive in SQL Server 2012+ with OFFSET/FETCH
DECLARE #StartPlanned DATE = '20160502',
#Days INT = 6;
SELECT Date
FROM dbo.Calendar
WHERE Date >= #StartPlanned
AND WorkingDay = 1
ORDER BY Date
OFFSET (#Days - 1) ROWS FETCH NEXT 1 ROWS ONLY
ADDENDUM
I missed the part earlier about having another table, and the comment about putting it into a cursor has prompted me to amend my answer. I would add a new column to your calendar table called WorkingDayRank:
ALTER TABLE dbo.Calendar ADD WorkingDayRank INT NULL;
GO
UPDATE c
SET WorkingDayRank = wdr
FROM ( SELECT Date, wdr = ROW_NUMBER() OVER(ORDER BY Date)
FROM dbo.Calendar
WHERE WorkingDay = 1
) AS c;
This can be done on the fly, but you will get better performance with it stored as a value, then your query becomes:
SELECT p.Name,
p.Start_Planned,
p.Work_days_Required,
EndDate = c2.Date
FROM Projects AS P
INNER JOIN dbo.Calendar AS c1
ON c1.Date = p.Start_Planned
INNER JOIN dbo.Calendar AS c2
ON c2.WorkingDayRank = c1.WorkingDayRank + p.Work_days_Required - 1;
This simply gets the working day rank of your start date, and finds the number of days ahead specified by the project by joining on WorkingDayRank (-1 because you want the end date inclusive of the range)
This will fail, if you ever plan to start your project on a non working day though, so a more robust solution might be:
SELECT p.Name,
p.Start_Planned,
p.Work_days_Required,
EndDate = c2.Date
FROM Projects AS P
CROSS APPLY
( SELECT TOP 1 c1.Date, c1.WorkingDayRank
FROM dbo.Calendar AS c1
WHERE c1.Date >= p.Start_Planned
AND c1.WorkingDay = 1
ORDER BY c1.Date
) AS c1
INNER JOIN dbo.Calendar AS c2
ON c2.WorkingDayRank = c1.WorkingDayRank + p.Work_days_Required - 1;
This uses CROSS APPLY to get the next working day on or after your project start date, then applies the same join as before.
This query returns a table with a predicted enddate for each project
select name,min(day) as predicted_enddate from (
select c.day,p.name from dbo.Calendar c
join dbo.Calendar c2 on c.day>=c2.day
join dbo.Projects p on p.start_planned<=c.day and p.start_planned<=c2.day
group by c.day,p.work_days_required,p.name
having sum(c2.workingday)=p.work_days_required
) a
group by name
--This gives me info about all projects
select p.projectname,p.Start_Planned ,c.date,
from calendar c
join
projects o
on c.date=dateadd(days,p.Work_days_Required,p.Start_Planned)
and c.isworkingday=1
now you can use CTE like below or wrap this in a procedure
;with cte
as
(
Select
p.projectnam
p.Start_Planned ,
c.date,datediff(days,p.Start_Planned,c.date) as nooffdays
from calendar c
join
projects o
on c.date=dateadd(days,p.Work_days_Required,p.Start_Planned)
and c.isworkingday=1
)
select * from cte where nooffdays=6
use below logic
CREATE TABLE #proj(Name varchar(50),Start_Planned date,
Work_days_Required int)
insert into #proj
values('Project A','02.05.2016',6)
CReATE TABLE #Calendar(Day date,Weekday int,Workingday bit)
insert into #Calendar
values('01.05.2016',7,0),
('02.05.2016',1,1),
('03.05.2016',2,1),
('04.05.2016',3,0),
('05.05.2016',4,1),
('06.05.2016',5,1),
('07.05.2016',6,0),
('08.05.2016',7,0),
('09.05.2016',1,1),
('10.05.2016',2,1)
DECLARE #req_day int = 3
DECLARE #date date = '02.05.2016'
--SELECT #req_day = Work_days_Required FROM #proj where Start_Planned = #date
select *,row_number() over(order by [day] desc) as cnt
from #Calendar
where Workingday = 1
and [Day] > #date
SELECT *
FROM
(
select *,row_number() over(order by [day] desc) as cnt
from #Calendar
where Workingday = 1
and [Day] > #date
)a
where cnt = #req_day

Select * from table where desired period does not overlap with existing periods

I have a table with records and a period of time for each record, like reservations for instance. So my records look like this:
Table-reservations
id room datefrom dateto
1 'one' '2015-09-07' '2015-09-12'
2 'two' '2015-08-11' '2015-09-02'
3 'three' '2015-06-11' '2015-06-14'
4 'two' '2015-07-30' '2015-08-10'
5 'four' '2015-06-01' '2015-06-23'
6 'one' '2015-03-21' '2015-03-25'
...
n 'nth' '2015-06-01' '2015-07-03'
Also there is a table with rooms containing an ID, a roomnumber and a roomtype, like this:
Table-rooms
idrooms room roomtype
1 'one' 'simple'
2 'two' 'simple'
3 'three' 'double'
...
nx 'nth' 'simple'
As you can see some rooms appear multiple times, but with different periods, because they are booked on various periods.
What I need to obtain through SQL is a list of rooms that are available in a given period of time.
So something like(pseudocode):
Select room from table where there is no reservation on that room between 2015-08-13 and 2015-08-26
How can I do this?
So I will have a fromdate and a todate and I will have to use them in a query.
Can any of you guys give me some pointers please?
Right now I use the following sql to obtain a list of rooms that are available NOW
select * from rooms
where idrooms not in
(
select idroom from rezervations where
((date(now())<=dateto and date(now())>=datefrom)or(date(now())<=dateto and date(now())<=datefrom))
)
order by room
This might be easier to understand.
Assuming you have another table for rooms.
SELECT *
FROM rooms
WHERE NOT EXISTS (SELECT id
FROM reservations
WHERE reservations.room = rooms.id
AND datefrom >= '2015-08-13'
AND dateto <= '2015-08-26')
You'll want to check that records don't exist where 'date from' is less than or equal to the end date in your range and 'date to' is greater than or equal to the start date in your range.
select t1.room
from reservations t1
where not exists (
select *
from reservations t2
where t2.room = t1.room
and t2.datefrom <= '2015-08-26'
and t2.dateto >= '2015-08-13'
)
group by room
You can try it out here: http://sqlfiddle.com/#!9/cbd59/5
I'm new to the site, so it won't let me post a comment, but I think the problem on the first answer is that the operators should be reversed.
As mentioned in a previous comment, this is only good if all of the rooms have a reservation record. If not, better to select from your rooms table like this: http://sqlfiddle.com/#!9/0b96e/1
select room
from rooms
where not exists (
select *
from reservations
where rooms.room = reservations.room
and reservations.datefrom <= '2015-08-26'
and reservations.dateto >= '2015-08-13'
)
SELECT a.room
FROM yourTable a
WHERE a.room NOT IN (
SELECT DISTINCT( b.room )
FROM yourTable b
WHERE datefrom >= '2015-08-13'
OR dateto <= '2015-08-26'
);
It should work to the best of my guesses. If it doesn't; could you please provide a sqlfiddle to check the data on?
Using this SO answer
Consider
range1 : r11 r12 [inputfromDate inputToDate]
range2 : r21 r22 [datefromColumn datetoColumn]
IF r11 <= r22 && r21 <= r12, then the ranges are overlapping.
There are 6 possible case, where two ranges can overlap. But the above two condition itself handles all the 6 possibilities.
If the above condition matches then that means, the dates are overlapping. So I have used not in to get the remaining entries.
select * from <table-name> where id not in (
select id from <table-name> where
:inputfromDate <= datetoColumn and datefromColumn <= :inputToDate
)
You might try this :
select * from rooms
where room not in(
select room from reservations
where '2015-09-16' >= datefrom
and '2015-09-16' <=dateto
and '2015-09-21' >= dateto
)
Goodluck!

Unrolling periodical events in MySQL

I've got a database with two tables, that I want to combine. One of the tables contains "incidental events", which just occur once. Next to this, I also have "periodical events". Now I want to combine these two in a view.
The incidental one simply has two columns, one called changes, the other one called date. The periodical one has three columns, changes, startDate and endDate. The difference between these two can be a maximum of 50 years, so manually typing out one case for every day is not going to work. Both views also have an AI ID. In this view I want to have a column date and a column changes.
To achieve this I want to unroll the periodical changes table, so that it shows one entry for every day in between the startDate and endDate. For instance:
incidental changes:
date | change
09/08/2015 | 5
11/08/2015 | 10
periodical changes:
startDate | endDate | change
09/08/2015 | 12/08/2015 | 7
These two I want combined into:
changes view:
date | change
09/08/2015 | 5
09/08/2015 | 7
10/08/2015 | 7
11/08/2015 | 10
11/08/2015 | 7
12/08/2015 | 7
My idea is to use something like this:
SELECT * FROM incidental_changes,(
SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE 1)
SET #maxID = (SELECT max(ID) AS max FROM periodical_changes WHERE 1)
WHILE (#id <= #maxID) DO
SET #firstDate = (SELECT startDate FROM periodical_changes WHERE id = #id)
SET #lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO
SELECT #firstDate AS date, change FROM periodical_changes WHERE id = #id
#firstDate = #firstDate + INTERVAL 1 DAY
END
#id = #id + 1
END
) WHERE 1
This gives me an error,
CREATE ALGORITHM = UNDEFINED VIEW all_periodicals AS SELECT * FROM
incidental_changes,( SET #id = (SELECT min(ID) AS min FROM
periodical_changes WHERE 1) SET #maxID = (SELECT max(ID) AS max FROM
periodical_changes WHERE 1) WHILE (#id <= #maxID) DO SET #firstDate =
(SELECT startDate FROM periodical_changes WHERE id = #id) SET
#lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO SELECT #firstDate AS date, change
FROM periodical_changes WHERE id = #id #firstDate = #firstDate +
INTERVAL 1 DAY END #id = #id + 1 END ) WHERE 1
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE
1) SET #' at line 5
and I'm guessing that if I'd manage to fix this error there'd be more. So, is there any way to do this the way I want, or do I have to look for a different approach?
EDIT:
Okay, so far I have not found a way to do this in a view or so. So instead I am now using a routine. This routine has one parameter, account INT. The definition I am using so far is as followed:
BEGIN
DECLARE periodicalID int;
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE periodicalCursor CURSOR
FOR SELECT periodicals.periodicalID FROM periodicals WHERE periodicals.accountID = account;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
CREATE TEMPORARY TABLE results LIKE incidentials;
ALTER TABLE results DROP INDEX date;
SET #periodicalID = -1;
OPEN periodicalCursor;
allPeriodicals: LOOP
FETCH periodicalCursor INTO periodicalID;
IF (v_finished) THEN
LEAVE allPeriodicals;
END IF;
SELECT periodicals.startDate,periodicals.numberOfPeriods,periodicals.period,periodicals.endDate,periodicals.money FROM periodicals WHERE periodicals.periodicalID = periodicalID AND periodicals.accountID = account INTO #startDate, #numberOfPeriods, #period,#endDate,#money;
SET #intervalStatement = "SELECT ? + INTERVAL ? ";
SET #intervalStatement = CONCAT(#intervalStatement,#period," INTO #res");
PREPARE intervalStatement FROM #intervalStatement;
WHILE #startDate <= #endDate DO
EXECUTE intervalStatement USING #startDate,#numberOfPeriods;
SET #startDate = #res;
INSERT INTO results(accountID,date,money) VALUES (account,#startDate,#money);
END WHILE;
END LOOP allPeriodicals;
INSERT INTO results(accountID,date,money) SELECT accountID,date, money FROM incidentials WHERE incidentials.accountID = account;
SELECT * FROM results ORDER BY date;
END
This poses the problem of performance though. With only one periodical entry spread over a year this query already takes about 16 seconds. So even though this approach works, I either did something wrong causing it to take this long or this is not the right way to go.
Let me presume you have a numbers table. Then you can do:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
numbers n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
For a select query, you can generate the numbers using a subquery, if you know the maximum length. Something like:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
This doesn't work in a view, however. For that, you really do need a numbers table.