SQL problem with 12 subquery - sql

Exists such db schema:
alt text http://img156.imageshack.us/img156/9017/2706.png
I need to write query.
For every doctor i need average cost of visit by month for 2009 year.
The result is (name_of_doctor, january, febriary, ..., december)
I know how to do this with 12 subquery. Exists another more convinient way?

Try the following. You may need to modify the date functions depending on your RDBMS. I have assumed MySQL, but the rest should be universal SQL.
SELECT doctors.name, monthly.average, monthly.month
FROM doctors JOIN (
SELECT AVG(cost), MONTH(visit_date) AS month FROM visits
WHERE YEAR(visit_date) GROUP BY MONTH(visit_date)
) AS monthly ON doctors.id = visits.id_doc
Note, this may only include months for doctors that have visits. So you may need to use IFNULL or COALESCE to clean up your output.

I would just do a normal select with a GROUP BY over the month and have your UI handle displaying it as 12 columns across. If you really need to do it though, then this should work:
SELECT
D.name,
AVG(CASE WHEN MONTH(V.visit_date) = 1 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 2 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 3 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 4 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 5 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 6 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 7 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 8 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 9 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 10 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 11 THEN V.cost ELSE NULL END),
AVG(CASE WHEN MONTH(V.visit_date) = 12 THEN V.cost ELSE NULL END)
FROM
Doctors D
INNER JOIN Visits V ON
V.id_doc = D.id AND
V.visit_date BETWEEN '2009-01-01' AND '2009-12-31'
GROUP BY
D.name
ORDER BY
D.name
You might need to change the date functions based on your RDBMS. Also, you may need to fiddle with the edge cases - if your dates have a time component it won't catch rows on 12/31.
Finally, I don't know if this changes between RDBMSs and I can't test right now, but if AVG counts a NULL as 0 cost instead of discounting them then you may need to do your own averag - SUM(CASE ... cost ... 0)/SUM(CASE ... 1 ... 0). I hope that makes sense.

You can do it easily as below - but it will list each months cost in a separate line .If you want the costs on the same line you can use a pivot statement .This is for SQL 2008 if you neede pivoting we can do it.If there is a performance problem use a range scan on date rather than use datepart
select d.name,datepart(month,v.visit_date) as month,
avg(v.cost) as avgcost
from visits as v inner join
doctors as d on v.id_doc=d.id
and datepart(year,v.visit_date)=2010
group by d.name,datepart(month,v.visit_date)

Related

SQL case operation

Im fairly new with sql, and been trying to solve a problem where you have a table information about orders. In this case, Im trying to use the case operation to get a monthly report on orders, so I should have a column which states the year,another one which states the month, and then I should have columns for days 1-20,21-22,23-24 and above 25. Im trying to use the case operation to get the amount of orders that happened on those days.
I tried the following query :
SELECT
DATEPART(YEAR,date) AS year,DATEPART(MONTH,date) AS month,
COUNT(CASE WHEN DATEPART(DAY,date) BETWEEN 1 AND 20 THEN order ELSE 0 END) AS D1_D20,
COUNT(CASE WHEN DATEPART(DAY,date) BETWEEN 21 AND 22 THEN order ELSE 0 END) AS D21_D22,
COUNT(CASE WHEN DATEPART(DAY,date) BETWEEN 23 AND 24 THEN order ELSE 0 END) AS D23_D24,
COUNT(CASE WHEN DATEPART(DAY,date) > 25 THEN order ELSE 0 END) AS D25_END
FROM ORDERS
GROUP BY DATEPART(YEAR,date),DATEPART(MONTH,date)
Obviously the problem with that query is that, now I just get the total number of orders for each of the days, I know I should count the orders, but dont know the syntax. Help would be greatly appreciated!
Use SUM():
SELECT
DATEPART(YEAR, date) AS year, DATEPART(MONTH, date) AS month,
SUM(CASE WHEN DATEPART(DAY,date) BETWEEN 1 AND 20 THEN 1 ELSE 0 END) AS D1_D20,
SUM(CASE WHEN DATEPART(DAY,date) BETWEEN 21 AND 22 THEN 1 ELSE 0 END) AS D21_D22,
SUM(CASE WHEN DATEPART(DAY,date) BETWEEN 23 AND 24 THEN 1 ELSE 0 END) AS D23_D24,
SUM(CASE WHEN DATEPART(DAY,date) > 25 THEN 1 ELSE 0 END) AS D25_END
FROM ORDERS
GROUP BY DATEPART(YEAR, date), DATEPART(MONTH, date);
I would recommend using the functions DAY(), YEAR(), and MONTH() because they are simpler to type.
By the way, you can use COUNT() if you remove the ELSE clause. Your particular problem is that COUNT(0) = COUNT(1) because COUNT() counts non-NULL values. I prefer SUM() because it is more intuitive in this respect.

Query to show all months and show values where there are data for the corresponding months

I have a query and it shows the months where there is corresponding data. However, I would like to show all of the months in the year and have the months where there are no data shown as zero.
There is my SQL Statement:
SELECT DATENAME(MONTH, hb_Disputes.OPENED) AS MonthValue,
COUNT(CASE WHEN REV_CLS = 2 THEN 1 END) AS SmallCommercialIndust,
COUNT(CASE WHEN REV_CLS <> 2 THEN 1 END) AS Residential
FROM hb_Disputes
WHERE (hb_Disputes.ASSGNTO = 'E099255') AND (YEAR(hb_Disputes.OPENED) = YEAR(GETDATE()))
GROUP BY hb_Disputes.OPENED
And this is my output:
I also have a table name MonthName that shows all of the months in a year and I know I may need to use this to accomplish what I'm trying to achieve but I'm not sure how to get there:
If you have data in the table for all months, but the where clause is filtering it out, then the simplest method is to extend the conditional aggregation:
SELECT DATENAME(MONTH, d.OPENED) AS MonthValue,
SUM(CASE WHEN d.ASSGNTO = 'E099255' AND d.REV_CLS = 2 THEN 1 ELSE 0 END) AS SmallCommercialIndust,
SUM(CASE WHEN d.ASSGNTO = 'E099255' AND d.REV_CLS <> 2 THEN 1 ELSE 0 END) AS Residential
FROM hb_Disputes d
WHERE YEAR(d.OPENED) = YEAR(GETDATE())
GROUP BY DATENAME(MONTH, d.OPENED)
ORDER BY MIN(d.OPENED);
Note: This does not fix the issue in all cases. It should just be a simple way to modify your query -- and will often work.

Condition SUM in SQL

I want to calculate con_Amount field & want to put total to D01,D02,....D31 on the condition of Con_PubDate.
if Con_PubDate field Day = 1 then I want to put the total to D01,if Con_PubDate field Day = 2 then I want to put the total to D02,likewise upto Day = 31 then I want to put the total to D31
I try following code in stored procedure but it had an error. Some one please help me to solve this problem.
SELECT Con_Cnt_Code,
Con_Dst_Code,
Con_Cor_Code,
SUM(CASE
WHEN DATEPART(DAY,Con_PubDate) = 1 THEN con_Amount
ELSE
SUM(CASE
WHEN DATEPART(DAY,Con_PubDate) = 2 THEN con_Amount
ELSE
SUM(CASE
WHEN DATEPART(DAY,Con_PubDate) = 31 THEN con_Amount
END) AS D31
END) AS D02
END) AS D01
FROM Contributions
GROUP BY Con_Cnt_Code,Con_Dst_Code,Con_Cor_Code
You probably want each conditional sum of a CASE expression as a separate item in your select list, something like this:
SELECT
Con_Cnt_Code,
Con_Dst_Code,
Con_Cor_Code,
SUM(CASE WHEN DATEPART(DAY,Con_PubDate) = 1 THEN con_Amount END) AS D01,
SUM(CASE WHEN DATEPART(DAY,Con_PubDate) = 2 THEN con_Amount END) AS D02,
-- and possibly other days as well
SUM(CASE WHEN DATEPART(DAY,Con_PubDate) = 31 THEN con_Amount END) AS D31
FROM Contributions
GROUP BY
Con_Cnt_Code, Con_Dst_Code, Con_Cor_Code;
By default, SUM will ignore NULL values, so you might not need an ELSE condition in your CASE expressions. If you add one, you may default the amount to sum to zero.

SQL Pivoting With rollup (or a row totals)

I have the following query with some g/l accounts and sum totals by month:
Select Month, [41100],[42000],[45100],[42200],[42300],[42400],[45200],
[45205]
from ( Select ACC_0 , (CONVERT(NUMERIC(12,2),(AMTLED_0))) as Amount , MONTH(ACCDAT_0) as Month
from x3v6.CICPROD.GACCENTRYD with(nolock)
where YEAR(ACCDAT_0) = YEAR(GETDATE())
) as s1
PIVOT (SUM(Amount) FOR ACC_0 IN ([41100],[42000],[45100],[42200],[42300],[42400],[45200],
[45205])) as pivot1
Which produces the following result set:
Month 41100 42000 45100 42200 42300 42400 45200 45205
1 3857806.91 19987.61 49876.84 49078.59 2173.63 NULL 375.00 68.52
2 4459775.79 5145.69 64442.41 58102.00 2684.40 NULL 230.00 NULL
3 4311142.03 8594.31 44220.72 33850.08 3116.75 141.00 1917.50 NULL
4 4413788.57 5613.67 58038.20 55359.25 4398.67 NULL 4796.38 132.00
5 4251083.15 4372.07 48488.03 53592.00 2869.86 127.00 110.00 128.00
6 4353075.16 9705.83 53925.37 64104.00 2304.65 2822.78 153.41 NULL
7 4549485.41 10054.92 61607.99 65136.00 1531.66 186.30 265.50 NULL
8 4239075.39 16917.10 43012.02 51591.25 1538.01 1690.91 350.00 NULL
9 4331439.41 39248.15 56368.41 74928.00 1858.82 694.32 160.00 66.00
10 3673909.02 12283.42 38928.66 28608.00 NULL NULL 120.00 NULL
I would like to get a TOTAL tally by each ROW in a column to the right. How would I accomplish this with the PIVOT syntax? I tried using a grouping sets, but it didn't work for me.
Version is SQL Server 2012.
Thanks
One simple way, would be to not use PIVOT, and instead, use the SUM aggregate with CASE expressions.
SELECT Month,
SUM(CASE WHEN ACC_0 = '41100' THEN Amount END) AS [41100],
SUM(CASE WHEN ACC_0 = '42000' THEN Amount END) AS [42000],
SUM(CASE WHEN ACC_0 = '45100' THEN Amount END) AS [45100],
SUM(CASE WHEN ACC_0 = '42200' THEN Amount END) AS [42200],
SUM(CASE WHEN ACC_0 = '42300' THEN Amount END) AS [42300],
SUM(CASE WHEN ACC_0 = '42400' THEN Amount END) AS [42400],
SUM(CASE WHEN ACC_0 = '45200' THEN Amount END) AS [45200],
SUM(CASE WHEN ACC_0 = '45205' THEN Amount END) AS [45205],
SUM(Amount) AS [Total]
FROM (SELECT ACC_0,
(CONVERT(NUMERIC(12,2),(AMTLED_0))) AS Amount,
MONTH(ACCDAT_0) AS Month
FROM x3v6.CICPROD.GACCENTRYD WITH (NOLOCK)
WHERE YEAR(ACCDAT_0) = YEAR(GETDATE())
-- only get the ACC_0 values you need so Total is correct
AND ACC_0 IN ('41100','42000','45100','42200','42300','42400','45200','45205')
) AS s1
GROUP BY Month

SQL - Dividing by Sum by Group

I am just learning SQL and have run into a problem creating a custom report. I am working with school attendance data. I want to create a report that gives membership days and number of days for each absence type.
I have successfully created a report for these separately.
Membership Days (calculated by counting days school was in session between the student's entry date and the current date. Membership days does not exist as a field on its own)
SELECT sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp THEN cd.INSESSION ELSE 0 END), s.LASTFIRST
FROM CALENDAR_DAY cd,STUDENTS s
WHERE cd.SCHOOLID = 405
GROUP BY s.LASTFIRST
Count per absence type
SELECT s.STUDENT_NUMBER, s.LASTFIRST,SUM(CASE WHEN a.ATTENDANCE_CODEID = 2 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 4 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 3 THEN 1 ELSE 0 END),SUM(CASE WHEN a.ATTENDANCE_CODEID = 51 THEN 1 ELSE 0 END)
FROM ATTENDANCE a
INNER join STUDENTS s
ON a.STUDENTID = s.ID
WHERE a.att_date between '%param1%' and '%param2%'
GROUP BY s.STUDENT_NUMBER, s.LASTFIRST
The problem is that if I try to put these in the same report, the membership days are multiplied by the number of times the student appears in the attendance table due to joining student and attendance. My thought on a solution was to then divide this line
sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp THEN cd.INSESSION ELSE 0 END)
by the number of times the student showed up in the attendance table to counteract the student information existing on every line. I can't figure out how to do that. I don't know much about these types of problems, so hopefully I've just gone off on the wrong track and there is an easy solution. Thanks.
Your problem is a common problem -- trying to summarize along two dimensions at the same time without using a subquery. You want to do this query with two aggregation subqueries. Something like this:
SELECT *
FROM (SELECT sum(case when cd.DATE_VALUE >= s.ENTRYDATE and cd.DATE_VALUE <= current_timestamp
THEN cd.INSESSION
ELSE 0
END), s. STUDENT_NUMBER
FROM CALENDAR_DAY cd CROSS JOIN
STUDENTS s
WHERE cd.SCHOOLID = 405
GROUP BY s.STUDENT_NUMBER
) sc JOIN
(SELECT s.STUDENT_NUMBER, s.LASTFIRST,
SUM(CASE WHEN a.ATTENDANCE_CODEID = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 4 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN a.ATTENDANCE_CODEID = 51 THEN 1 ELSE 0 END)
FROM ATTENDANCE a INNER join
STUDENTS s
ON a.STUDENTID = s.ID
WHERE a.att_date between '%param1%' and '%param2%'
GROUP BY s.STUDENT_NUMBER, s.LASTFIRST
) sa
on sc.STUDENT_NUMBER = sa.STUDENT_NUMBER;