Number of entries per month - sql

I have something similar to the following situation, probably not the best example but somewhat similar to what I really have. Let's say I have 4 tables that due to certain circumstances I can't change.
CREATE TABLE [Hospital] (
[HospitalID] INT IDENTITY NOT NULL,
[HospitalName] VARCHAR(MAX) NOT NULL
);
CREATE TABLE [Doctors] (
[DoctorID] INT IDENTITY NOT NULL ,
[HospitalID] INT NOT NULL,
[DoctorName] VARCHAR(MAX) NOT NULL
);
CREATE TABLE [Patient] (
[PatientID] INT IDENTITY NOT NULL ,
[DoctorID] INT NOT NULL
);
CREATE TABLE [PatientAppointment] (
[PatientID] INT IDENTITY NOT NULL,
[Date] DATETIME NOT NULL
);
I want to write a stored procedure that get's a year and a month as parameters and it is supposed to return [HospitalName] , [DoctorsName] and the number of patient appointments for that time period.
This is what I have now and I am stuck
CREATE PROCEDURE [dbo].[Procedure]
#year INT ,
#month INT
AS
SELECT COUNT([Date]) AS NumberOfAppointments FROM [PatientAppointment]
WHERE MONTH([Date]) = #month AND YEAR([Date]) = #year
SELECT [Hospital].HospitalName , [Doctors].DoctorName FROM [Doctors]
INNER JOIN [Hospital] ON [Hospital].HospitalID = [Doctors].DoctorID
RETURN 0
I can't figure out how to extract the info I need and I am limited to one stored procedure.

try this:
CREATE PROCEDURE [dbo].[Procedure]
#year INT ,
#month INT
AS
BEGIN
SELECT h.HospitalId, d.DoctorName, count(p.PatientId) as NumberOfAppointments
FROM Hospital h
INNER JOIN Doctors d
ON h.HospitalId = d.HospitalId
INNER JOIN Patient p
ON d.DoctorId = p.DoctorId
INNER JOIN PatientAppointment ap
ON p.PatientId = ap.PatientId
AND MONTH(ap.[Date]) = #month
AND YEAR(ap.[Date]) = #year
GROUP BY h.HospitalId, d.DoctorName
END

My answer is very similar to the one posted by Joachim but there is one VERY significant difference. This code is SARGable where the original query and the fine example already posted are not. Since you say you can't change the table structure I will refrain from suggesting changes....although you could greatly improve on these structures.
Create Procedure dbo.SomeBetterName
(
#Date date
) AS
select h.HospitalName
, d.DoctorName
, COUNT(pa.[Date]) as NumAppointments
from Hospital h
join Doctors d on h.HospitalID = d.HospitalID
join Patient p on p.DoctorID = d.DoctorID
join PatientAppointment pa on pa.PatientID = p.PatientID
where pa.Date >= dateadd(month, datediff(month, 0, #Date), 0)
and pa.Date < dateadd(month, datediff(month, 0, #Date) + 1, 0)
group by h.HospitalName
, d.DoctorName

Related

SQL query to check availability on an event table

I have a table of events with the columns Room Number and Date
I want to check availability in a certain room so I tried creating a temp calendar table just to compare, but I know I'm doing something wrong with my left join so I need some pointers to help me achieve my goal: only show the dates where there is no event in the specific room.
This is my query so far:
IF OBJECT_ID('tempdb..#calendarTable') IS NOT NULL
DROP TABLE #calendarTable;
GO
CREATE TABLE #calendarTable (
"DayID" INT
,"DayDate" DATE
,PRIMARY KEY (DayID)
)
DECLARE #startDate AS DATE
DECLARE #baseDate DATE
,#offSet INT
,#numberOfLoops INT
SET #startDate = GETDATE() /*'20150101'*/
SET #baseDate = #startDate
SET #offSet = 0
SET #numberOfLoops = DATEDIFF(DAY, #baseDate, DATEADD(MONTH, 24, #baseDate)) /*365*/
WHILE (#offSet < #numberOfLoops)
BEGIN
INSERT INTO #calendarTable (
"DayID"
,"DayDate"
)
SELECT (#offSet + 1)
,DATEADD(DAY, #offSet, #baseDate)
SET #offSet = #offSet + 1
END
SELECT TOP (500)
DayDate
,a.Arrangement_Number
,a.Activity
,a.Room_Number
FROM #calendarTable
left outer join [Events] as a ON DayDate = (convert(varchar(10), a.[Date],120))
where Room_Number = 53
order by DayDate asc

Stored Procedure not inserting records

I have a stored procedure that attempts to insert records from a function into a table. When I run the stored procedure, it completes successfully, but does not insert any records into the table. If I highlight the Insert code, it works. I am quite new to all this and would appreciate any help please.
The Stored Procedure is:
ALTER PROCEDURE [dbo].[DataQualityDashboard_UpdatePart1]
AS
DECLARE #StartDate date
DECLARE #EndDate date
SET #StartDate = '2017-01-01'
SET #EndDate = '2017-01-31'
Insert into [iCS.Warehouse3].[dbo].[DashboardData]
Select
MetricID
, FirstDateofMonth
, Numerator
, Denominator
From [iCS.Warehouse3].[KPI].[DataQualityEthnicityOutpatientsWCFT]
(#StartDate, #EndDate)
Commit;
The Function it calls upon is [iCS.Warehouse3].[KPI].[DataQualityEthnicityOutpatientsWCFT], which is below.
ALTER FUNCTION [KPI].[DataQualityEthnicityOutpatientsWCFT]
(
#StartDate date
, #EndDate date
)
RETURNS TABLE
AS
RETURN
(
WITH EthnicityOutpatients (XCN, EthnicGroup, FirstDateOfMonth, MetricID)
AS
(
SELECT
o.XCN
,p.EthnicGroup
,DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0) as FirstDateOfMonth
,1 as MetricID
FROM [iCS.Warehouse3].[OP].[Appointments] o
Inner Join [iCS.Warehouse3].[lk].[Patient] p
on o.[XCN] = p.[XCN]
Inner Join [iCS.Warehouse3].[lk].[Calendar] cal
on o.AppointmentDate = cal.TheDate
Inner Join [iCS.Warehouse3].[dbo].[LocationLK] l
on o.ClinicLocation = l.Location
WHERE o.AppointmentDate >= #StartDate and o.AppointmentDate <= #EndDate
and o.AppointmentStatus = 'A'
and o.Specialty not in ('NR', 'NPH')
and l.WCFTorSatellite = 'WCFT'
)
SELECT
FirstDateOfMonth
, MetricID
, COUNT(CASE WHEN EthnicGroup is null THEN 1 END) as Numerator
, COUNT (XCN) as Denominator
FROM EthnicityOutpatients
GROUP BY
FirstDateOfMonth
, MetricID
)
The table it should be inserting records into is [iCS.Warehouse3].[dbo].[DashboardData].
It has four fields: MetricID, Date, Numerator and Denominator.
Any help would be greatly appreciated please.
Thanks in advance, Andrew.

Showing Data for those Dates which are not present in Database Table

I am trying show attendance record for employees in particular Branch, let Say "Pune Branch".
While Showing Weekly Records, It only shows the record of dates for which there is an entry in database table. I also want to show record for those Date on which there is no any employee present.
Suppose In Log_TB there are 2 employee record present for 28 Apr 2015 out of 2 Employees, Then It Shows
And If No records for that Date then it Returns nothing.
I want result Like
Here is my Query
Declare
#Date date = GETDATE(),
#date1 date,
#Total int
set #date1 = (CONVERT(date,DATEADD(DD,-6,#Date),108));
select #Total = Count(User_TB.User_Id)
from User_TB join Branch_TB
on User_TB.User_Branch = Branch_TB.Branch_Name
where User_TB.User_Branch = 'Pune';
select convert(varchar,Log_TB.Date,106) AS Date,
CONVERT(CHAR(3),DATENAME(weekday,Log_TB.Date)) AS Day,
#Total AS Total,COUNT(Log_TB.User_Id) Present,
(#Total-COUNT(Log_TB.User_Id)) AS Absent
From Log_TB join User_TB on Log_TB.User_Id = User_TB.User_Id
where Log_TB.Date <= (CONVERT(date,#Date,108))
and Log_TB.Date >= #date1 and User_TB.User_Branch = 'Pune'
group by Log_TB.Date;
My table definition
CREATE TABLE [dbo].[Log_TB]
(
[User_Id] [varchar](50) NOT NULL,
[First_Login] [time](0) NULL,
[Logout] [time](0) NULL,
[Date] [date] NULL,
[Working_Hrs] [time](0) NULL,
[extra_Int] [int] NOT NULL,
[extra_String] [varchar](50) NULL
)
What you need is a Numbers or a Date table. You can generate it using something like this
DECLARE #date1 DATE = '20150401',#Date DATE= GETDATE();
;WITH CTE AS
(
SELECT #date1 as datecol
UNION ALL SELECT DATEADD(day,1,datecol) FROM CTE WHERE DATEADD(day,1,datecol) <= #Date
)
SELECT * FROM CTE
In your query, you would use it like this
Declare
#Date date = GETDATE(),
#date1 date,
#Total int
set #date1 = (CONVERT(date,DATEADD(DD,-6,#Date),108));
select #Total = Count(User_TB.User_Id)
from User_TB join Branch_TB
on User_TB.User_Branch = Branch_TB.Branch_Name
where User_TB.User_Branch = 'Pune';
DECLARE #date1 DATE = '20150401',#Date DATE= GETDATE();
;WITH CTE AS
(
SELECT #date1 as datecol
UNION ALL SELECT DATEADD(day,1,datecol) FROM CTE WHERE DATEADD(day,1,datecol) <= #Date
)
select convert(varchar,CTE.datecol,106) AS Date,
CONVERT(CHAR(3),DATENAME(weekday,CTE.datecol)) AS Day,
#Total AS Total,COUNT(User_TB.User_Id) Present,
(#Total-COUNT(User_TB.User_Id)) AS Absent
From CTE LEFT JOIN Log_TB ON Log_TB.Log_TB.Date = CTE.datecol
LEFT join User_TB on Log_TB.User_Id = User_TB.User_Id
and User_TB.User_Branch = 'Pune'
group by CTE.datecol;
Declare #date datetime -- its a starting date
Set #Date = '1-Apr-2015'
Declare #table table (date datetime)
-- inserts data from starting date till getdate
while #date <> convert(datetime,convert(varchar(10),getdate(),101),101)
begin
insert into #table
select #date aa
select #date = #date+1
end
select * from #table
Just join #table and your data with left join
Select *
from #table left join (Your data) with condition date.

Return only records with 6 instances where all values in one column are the same for each row

I am building a query and got stuck at a point where I am unsure something I am trying to achieve is even possible.
I have build a query which returns results like below:
with the below query:
declare #stDate varchar(10) = '1988-01-01';
declare #fromDate varchar(10) = '2014-06-01';
declare #toDate varchar(10) = '2014-11-30';
select PART.PARTNAME as 'PART NUMBER'
, PART.PARTDES as 'DESCRIPTION'
, ( CONVERT(date, DATEADD(day, WARHSCOST.CURDATE/1440, #stDate))) as 'DATE'
, ( CONVERT(float, WARHSCOST.BALANCE ) / 1000 ) as 'BALANCE'
, ( CONVERT(decimal(19,2), WARHSCOST.COST) ) as 'VALUE'
from WARHSCOST
inner join PART on ( PART.PART = WARHSCOST.PART )
inner join WAREHOUSES on ( WAREHOUSES.WARHS = WARHSCOST.WARHS )
where ( CONVERT(date, DATEADD(day, WARHSCOST.CURDATE/1440, #stDate)) between #fromDate and #toDate )
and ( WAREHOUSES.WARHSNAME = 'Fin' )
and ( PART.PARTNAME NOT LIKE 'ZZ%' )
and ( WARHSCOST.COST > 0 )
group by PART.PARTNAME
, PART.PARTDES
, WARHSCOST.CURDATE
, WARHSCOST.BALANCE
, WARHSCOST.COST
having count(*) > 0
order by 1 , 4
but I wish to restrict the results to only those
I need to add one more filter/criteria but I am totally lost in how to even go about it:
show only PARTS which have the same balance for all 6 months (as shown in the second screenshot)
Any help is much appreciated as it would save me a lot of time iterating through the current recordset just to eliminate the records that don't have the same balance for all 6 months.
If any more information is required from me please just let me know and i will do my best to update the question.
One methodology: load the data you are pulling into a temp table, then analyze and pull what meets your criteria from the temp table. In SQL 2005 and up, you can do all this with CTEs (common table expressions). Without the underlying tables I cannot check the syntax, but the concept here is solid.
Your original query is all but untouched in the first cte below. I've added some --comments, just because.
declare #stDate varchar(10) = '1988-01-01';
declare #fromDate varchar(10) = '2014-06-01';
declare #toDate varchar(10) = '2014-11-30';
WITH cteBase (PartName, PartDes, PartDate, PartBal, PartCost)
as (
-- CTE pulls out the "basic" data
select PART.PARTNAME -- as 'PART NUMBER'
, PART.PARTDES -- as 'DESCRIPTION'
, ( CONVERT(date, DATEADD(day, WARHSCOST.CURDATE/1440, #stDate))) -- as 'DATE'
, ( CONVERT(float, WARHSCOST.BALANCE ) / 1000 ) -- as 'BALANCE'
, ( CONVERT(decimal(19,2), WARHSCOST.COST) ) -- as 'VALUE'
from WARHSCOST
inner join PART on ( PART.PART = WARHSCOST.PART )
inner join WAREHOUSES on ( WAREHOUSES.WARHS = WARHSCOST.WARHS )
where ( CONVERT(date, DATEADD(day, WARHSCOST.CURDATE/1440, #stDate)) between #fromDate and #toDate )
and ( WAREHOUSES.WARHSNAME = 'Fin' )
and ( PART.PARTNAME NOT LIKE 'ZZ%' )
and ( WARHSCOST.COST > 0 )
group by PART.PARTNAME
, PART.PARTDES
, WARHSCOST.CURDATE
, WARHSCOST.BALANCE
, WARHSCOST.COST
-- having count(*) > 0 -- This doesn't do anything, if count(*) were 0 there would be no row
--order by 1 , 4
)
, cteSix (PartName, PartBal)
as (
-- cte that identifies what from the first cte qualifies
select PartName, PartBal
from cteBase
group by PartName, PartBal
having count(*) = 6
)
SELECT
ba.PartName as [PART NUMBER] -- Embedded space
,ba.PartDes as DESCRIPTION
,ba.PartDate as [DATE] -- Reserved word
,ba.PartBal as BALANCE
,ba.PartCost as VALUE
from cteBase ba
inner join cteSix six
on six.PartName = ba.PartName
order by ba.PartName, ba.PartBal
Also, if WARHSCOST.CURDATE is a date datatype (as opposed to string or integer), you shouldn't need to CONVERT it to date.
How about adding
having count(distinct CONVERT(date, DATEADD(day, WARHSCOST.CURDATE/1440, #stDate))) = 6
and count(distinct WARHSCOST.BALANCE) = 1

Get UserID from RosterResource Table

I have 5 tables: Location, Charge, Resource, Roster and SpecialRoster.
CREATE TABLE Location(
LocationID int identity,
StartDate datetime,
DaysInRoster int)
CREATE TABLE Charge(
ChargeID int identity,
TotalAmount money,
ChargeDate datetime,
UserID int)
CREATE TABLE [Resource](
ResourceID int identity,
ResourceName varchar(100))
CREATE TABLE Roster(
RosterDay int,
ResourceID int,
StartDate datetime,
EndDate datetime,
UserID int)
CREATE TABLE SpecialRoster(
RosterDate datetime,
ResourceID int,
UserID int)
I need to construct a report which shows the sum of Charge.TotalAmount on different days, by ResourceID.
Charge only has a UserID, but the rules are fairly simple:
If the date and ResourceID is in SpecialRoster, the UserID is taken from SpecialRoster
If not, the UserID for the Resource and the RosterDay for the date is taken from the Roster current at that time
Basically, I need to map ResourceID, through UserID from Roster or SpecialRoster, to Charge.
The RosterDay is the current day of the roster, and can be calculated from location, where the RosterDay = DateDiff(day, Location.RosterStartDate, GetDate()) % Location.DaysInRoster
that is, the modulus of the difference between the start of the entire roster at that location, with the number of days in a roster period.
I have written a Function to do this:
CREATE FUNCTION dbo.GetRosteredUser
(
#ResourceID int,
#Date datetime,
#LocationID int
)
RETURNS int
AS
BEGIN
DECLARE #UserID int
DECLARE #RosterOffset int
Select #UserID = UserID from SpecialRoster Where ResourceID = #ResourceID and RosterDate = #Date
If #UserID is NOT NULL
return #UserID
else
Select #RosterOffset = DATEDIFF(day, StartDate, #Date) % DaysInRoster
from Location Where LocationID = #LocationID
Select #UserID = UserID from Roster Where ResourceID = #ResourceID and RosterDay = #RosterOffset
and ( #Date between StartDate and EndDate or #Date > StartDate and EndDate is NULL)
return #UserID
END
GO
But the function isn't great as it is slow, and only allows the report to be run for a single day at a time:
Select a.ResourceID, Name, a.UserID, Sum(c.TotalAmount ) as AmountCharged
from Charge c
left outer join (
Select ResourceID, Name,
dbo.GetUserByResourceDate(ResourceID, '1/FEB/2013', 1) as UserID
from [Resource]) a
on c.UserID = a.UserID
Where ChargeDate between '1/FEB/2013' and '2/FEB/2013'
group by a.ResourceID, Name, a.UserID
Is there a better way to do this? A User will want to run this to compare productivity of a resource over a period of time. eg:
Date ResourceID Name UserID TotalAmount
01/FEB/2013 1 Sales1 22 $1024
02/FEB/2013 1 Sales1 11 $1454
03/FEB/2013 1 Sales1 14 $1900
04/FEB/2013 1 Sales1 23 $3045
Perhaps, something like this might be of help:
SELECT
re.ResourceID,
re.ResourceName,
ch.UserID,
AmountCharged = SUM(ch.TotalAmount)
FROM
Location lo
CROSS JOIN Charge ch
CROSS APPLY (
SELECT DATEDIFF(DAY, lo.StartDate, ch.ChargeDate) % lo.DaysInRoster
) x (RosterDay)
INNER JOIN
[Resource] re
LEFT JOIN Roster ro
ON
ro.RosterDay = x.RosterDay
AND ch.ChargeDate BETWEEN ro.StartDate
AND ro.RecourceID = re.ResourceID
LEFT JOIN SpecialRoster sr
ON
sr.RosterDate = ch.ChargeDate
AND sr.RecourceID = re.ResourceID
ON
ch.UserID = ISNULL(sr.UserID, ro.UserID)
GROUP BY
re.ResourceID,
re.ResourceName,
ch.UserID
WHERE
lo.LocationID = #LocationID
AND ch.ChargeDate BETWEEN ...
;
In the above script, it is assumed that all datetime values are in fact only dates.
Also, I was somewhat surprised not to found a connection between Location and any of the other tables specified. If there is one and you simply forgot to mention it, please let me know if you need any help with incorporating the necessary condition(s) into the script.