SQL Server Sum table fields based on params - sql

I have a table containing values from 12 different months
Table
{
January INT,
February INT,
etc.
}
and I need to sum values from specific months which numbers I keep in table (the number of months to sum can vary from 1 to 12):
DECLARE #Months TABLE
(
Number INT
)
so I'll surely need a big case and CTE but I don't really know how to achieve this.

The case statement isn't that bad:
select sum((case when m.month = 1 then Jan else 0 end) +
(case when m.month = 2 then Feb else 0 end) +
. . .
(case when m.month = 12 then Dec else 0 end)
)
from atable a cross join
#Months m;
You might want a group by as well. The above will return only one row for the entire table.

Related

SQL query select returns different result than expected

I'm querying on this 2 tables:
TIMBRATURE
ASSELEMGEN
This is my query:
select convert(varchar(11),convert(date,datav),105) as data,
sum (CASE WHEN idterminale=3 and DATEPART(hour,datav)>='9' and DATEPART(hour,datav)<='16' THEN 1 ELSE 0 END) as pranzoP,
sum (CASE WHEN idterminale=3 and DATEPART(hour,datav)>='17' and DATEPART(hour,datav)<='23' THEN 1 ELSE 0 END) as cenaP,
sum (CASE WHEN idterminale=3 THEN 1 ELSE 0 END) as totaleP
from TIMBRATURE where DATAV>=#dataDa and DATAV<#primoGGmeseSuccessivo and TIMBRATURE.IDDIP
in (select iddip from ASSELEMGEN where IDELEM=1001)
group by convert(date,datav)
order by convert(date,datav)
For this purpose consider this argumets:
declare #datada as date='20230201'
declare #primoggmesesuccessivo as date='20230207'
The result I get:
Iddip is user ID, problem is when user have 2 entries for the same day, one with datav hour part between 9 and 16, the other between 17 and 23. In this case I have to count 2 for it, but my query only count it once. For example in the result above on 1th February I expect PranzoP=93 and totaleP=130.

SQL query to aggregate month in one table

I have a table with one column (TK) with multiple values, also duplicated and another one column with date.
I need to return a table with first column with distinct(TK) and the other columns like month.
I do an example into SQL FIDDLE
http://sqlfiddle.com/#!18/14cb9f/28
TK
JANUARY
open a
4
open B
4
TK
FEBRUARY
open a
4
open B
4
I need
TK
JANUARY
FEBRUARY
open a
4
4
open B
4
4
Thanks
A simple conditional aggregation should do the trick
SELECT TK
,Janary = sum( case when month(datastart)=1 then 1 else 0 end )
,February = sum( case when month(datastart)=2 then 1 else 0 end )
From TEST
Where year(datastart)=2021
Group By TK
Or you can use PIVOT
Select *
From (
Select TK
,Col = datename(month,DataStart)
,Val = 1
From TEST
Where year(datastart)=2021
) src
Pivot ( sum(Val) for Col in ([January] ,[February] ) ) pvt
There are multiple ways to do this, but avoiding sub-queries and making the syntax simple to read, this is the simplest I can get:
SELECT
TK,
SUM(
CASE WHEN DATASTART >= '2021-01-01' AND DATASTART < '2021-02-01' THEN 1 ELSE 0 END
) AS JENUARY,
SUM(
CASE WHEN DATASTART >= '2021-02-01' AND DATASTART <= '2021-02-28' THEN 1 ELSE 0 END
) AS FEBRUARY
FROM
Test
GROUP BY
TK
Check it out
http://sqlfiddle.com/#!18/14cb9f/34

Need to calculate Year wise sum in the desired output format below, but I am getting column wise data

I just need to know that how can I get the row wise- year totals.
Here is my code and need to get the below expected result :
SELECT /*+parallel(v,32) +parallel(mm,32) +parallel(c,32) */
'BUS' VEH_TYPE,
MM.MAKE_MO,
SUM (CASE WHEN SUBSTR (V.YEAR_Val, 1, 4) = '1990' THEN 1 ELSE 0 END)
AS FY1990,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1991' THEN 1 ELSE 0 END)
AS FY1991,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1992' THEN 1 ELSE 0 END)
AS FY1992,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1993' THEN 1 ELSE 0 END)
AS FY1993,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1994' THEN 1 ELSE 0 END)
AS FY1994,
SUM (CASE WHEN SUBSTR (V.YEAR_VAL, 1, 4) = '1995' THEN 1 ELSE 0 END)
AS FY1995,
COUNT (MM.MAKE_MO) Total_Count
FROM VEHICLE V, MAKE mm, CATEGORY c
WHERE C.MAKE_MO = mm.MAKE_MO
AND c.CATEGORY_CD = v.CATEGORY_CD
AND V.p_MONTH BETWEEN '199001' AND '199501' --using this as data is huge
AND C.TYPE_CODE=01
AND MM.VEH_CODE='B'
GROUP BY 'BUS,'MM.MAKE_MO, V.YEAR_Val
ORDER BY V.YEAR_Val;
This is the Expected Result :
VEH TYPE YEAR MODEL TOTAL
------- ----- --------
Bus 1990 7,808,658
Bus 1991 5,474,809
Bus 1992 54,839,221
Bus 1993 54,680,000
Bus 1994 15,000,000
Bus 1995 17,899,668
This was way too long for a comment. After unwinding this query, it appears that you are simply trying to get a count of 'Bus' vehicles by model year (with ALL vehicles in the VEHICLE table are being labeled 'Bus'). If that's the case, you can simplify your query a lot.
SELECT VEH_TYPE
, theYear AS YEAR_MODEL
, COUNT(*) AS TOTAL
FROM (
SELECT 'BUS' AS VEH_TYPE
, SUBSTR(V.YEAR_Val,1,4) AS theYear
FROM VEHICLE V
INNER JOIN CATEGORY c ON v.CATEGORY_CD = c.CATEGORY_CD
AND C.TYPE_CODE='01'
INNER JOIN MAKE mm ON c.MAKE_MO = mm.MAKE_MO
AND MM.VEH_CODE='B'
WHERE V.p_MONTH BETWEEN '199001' AND '199501'
) s1
WHERE theYear IN ('1990','1991','1992','1993','1994','1995')
GROUP BY theYear
You may not need the WHERE clause if your vehicle years are only 1990-1995. And your JOINed tables don't appear to be doing anything other than limiting your VEHICLE table if there are no CATEGORY_CD matches in VEHICLE and CATEGORY, and if there are no MAKE_MO matches in MAKE and CATEGORY. If you were filtering for a specific column from CATEGORY or MAKE, or you had orphaned records in VEHICLE, then you'd want to add those JOINs back in. Otherwise, I don't think they're really doing anything.
Note that SUBSTR(V.YEAR_Val,1,4) will exclude any index you have on V.YEAR_Val, and if that's a column you need regularly, or if there are a lot of VEHICLE records, you may need to split that value into its own column.

How to count every half hour?

I have a query that its counting every hour, using a pivot table.
How would it be possible to get the count for every 30 minutes?
for example 8:00-8:29,8:30-8:59,9:00-9:29 etc. until 5:00
SELECT CONVERT(varchar(8),start_date,1) AS 'Day',
SUM(CASE WHEN DATEPART(hour,start_date) = 8 THEN 1 ELSE 0 END) as eight ,
SUM(CASE WHEN DATEPART(hour,start_date) = 9 THEN 1 ELSE 0 END) AS nine,
SUM(CASE WHEN DATEPART(hour,start_date) = 10 THEN 1 ELSE 0 END) AS ten,
SUM(CASE WHEN DATEPART(hour,start_date) = 11 THEN 1 ELSE 0 END) AS eleven,
SUM(CASE WHEN DATEPART(hour,start_date) = 12 THEN 1 ELSE 0 END) AS twelve,
SUM(CASE WHEN DATEPART(hour,start_date) = 13 THEN 1 ELSE 0 END) AS one_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 14 THEN 1 ELSE 0 END) AS two_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 15 THEN 1 ELSE 0 END) AS three_clock,
SUM(CASE WHEN DATEPART(hour,start_date) = 16 THEN 1 ELSE 0 END) AS four_clock
FROM test
where user_id is not null
GROUP BY CONVERT(varchar(8),start_date,1)
ORDER BY CONVERT(varchar(8),start_date,1)
I use sql server 2012 (version Microsoft SQL Server Management Studio 11.0.3128.0)
Try using iif as below:
SELECT CONVERT(varchar(8),start_date,1) AS 'Day', SUM(iif(DATEPART(hour,start_date) = 8 and
DATEPART(minute,start_date) >= 0 and
DATEPART(minute,start_date) =< 29,1,0)) as eight_tirty
FROM test where user_id is not null GROUP BY
CONVERT(varchar(8),start_date,1) ORDER BY
CONVERT(varchar(8),start_date,1)
To get counts by day and half hour, something like this should work.
SELECT day, half_hour, count(1) AS half_hour_count
FROM (
SELECT
CAST(start_date AS date) AS day,
DATEPART(hh, start_date)
+ 0.5*(DATEPART(n,start_date)/30) AS half_hour
FROM test
WHERE user_id IS NOT NULL
) qry
GROUP BY day, half_hour
ORDER BY day, half_hour;
Formatting the result could be done later.
You need a few things, and then this query just falls together.
First, assuming you need multiple dates, you're going to want what's known as a Calendar Table (hands down, probably the most useful analysis table).
Next, you're going to want either an existing Numbers table if you have one, or just generate the first on the fly:
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 24 * 2)
SELECT m
FROM Halfs
(recursive CTE - generates a table with a list of numbers starting at 0).
These two tables will provide the basis for a range query based on the timestamps in your main table. This will make it very easy for the optimizer to bucket rows for whatever aggregation you're doing. That's done by CROSS JOINing the two tables together in a subquery, as well as adding a couple of other derived columns:
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 24 * 2)
SELECT calendarDate, m, rangeStart, rangeEnd
FROM (SELECT Calendar.calendarDate, Halfs.m rangeGroup,
DATEADD(minutes, m * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeStart,
DATEADD(minutes, (m + 1) * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeEnd
FROM Calendar
CROSS JOIN Halfs
WHERE Calendar.calendarDate >= CAST('20160823' AS DATE)
AND Calendar.calendarDate < CAST('20160830' AS DATE)
-- OR whatever your date range actually is.
) Range
ORDER BY rangeStart
(note that, if the range of dates is sufficiently large, it may be beneficial to save this off as a temporary table with indicies. For small tables and datasets, the performance gain isn't likely to be noticeable)
Now that we have our ranges, it's trivial to get our groups, and pivot the table.
Oh, and SQL Server has a specific operator for PIVOTing.
WITH Halfs AS (SELECT CAST(0 AS INT) m
UNION ALL
SELECT m + 1
FROM Halfs
WHERE m < 3 * 2)
-- Intentionally limiting range for example only
SELECT calendarDate AS day, [0], [1], [2], [3], [4], [5], [6]
-- If you're displaying "nice" names,
-- do it at this point, or in the reporting application
FROM (SELECT Range.calendarDate, Range.rangeGroup
FROM (SELECT Calendar.calendarDate, Halfs.m rangeGroup,
DATEADD(minutes, m * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeStart,
DATEADD(minutes, (m + 1) * 30, CAST(Calendar.calendarDate AS DATETIME2) rangeEnd
FROM Calendar
CROSS JOIN Halfs
WHERE Calendar.calendarDate >= CAST('20160823' AS DATE)
AND Calendar.calendarDate < CAST('20160830' AS DATE)
-- OR whatever your date range actually is.
) Range
LEFT JOIN Test
ON Test.user_id IS NOT NULL
AND Test.start_date >= Range.rangeStart
AND Test.start_date < Range.rangeEnd
) AS DataTable
PIVOT (COUNT(*)
FOR Range.rangeGroup IN ([0], [1], [2], [3], [4], [5], [6])) AS PT
-- Only covers the first 6 groups,
-- or the first three hours.
ORDER BY day
The pivot should take care of the getting individual columns, and COUNT will automatically resolve null rows. Should be all you need.

Help with cross join creating a week view calendar

Hi all i have a table called bookings like this ( Ive bolded the columns to align)
CustID VenueID BookingDt Session
45 44 2010-03-20 00:00:00.000 PM
45 44 2010-03-27 00:00:00.000 PM
45 44 2009-10-18 00:00:00.000 PM
45 44 2009-10-24 00:00:00.000 PM
I have another table called Venues
oID oLocation oPitch
1 Left Park Rugby
2 Right Park Rugby
The tables are inter joined by Venues.oID=bookings .CustID
i want to make a table such as this
X Column = week days
Y column = locations
oID oSun oMon oTue oWed oThu oFri oSat
1 x x x x
2 x x x x x x x
I believe i have to do a cross join with the data from the bookings Database
Eg
select distinct v.olocation , b.BookingDt from oVenue V
cross join tblBookings B
Where B.VenueID=V.oID
and DATEPART( wk, b.BookingDt )='44'
and DATEPART( yy, b.BookingDt )='2009'
But this does oID and Date, i want it to do check to see if that date is there, if so place a x in its place other wise place a '' in its place.
Not sure the best way to proceed.
Any help is muchly appreciated.
Thanks in advance
Since you already restricted the week and year in your query, this is how to display it:
select
v.olocation,
max(case DATEPART(weekday, b.BookingDt) When 1 then 'x' else '' end) Sun,
max(case DATEPART(weekday, b.BookingDt) When 2 then 'x' else '' end) Mon,
max(case DATEPART(weekday, b.BookingDt) When 3 then 'x' else '' end) Tue,
max(case DATEPART(weekday, b.BookingDt) When 4 then 'x' else '' end) Wed,
max(case DATEPART(weekday, b.BookingDt) When 5 then 'x' else '' end) Thu,
max(case DATEPART(weekday, b.BookingDt) When 6 then 'x' else '' end) Fri,
max(case DATEPART(weekday, b.BookingDt) When 7 then 'x' else '' end) Sat
from
(
select distinct v.olocation , b.BookingDt
from oVenue V
LEFT JOIN tblBookings B on B.VenueID=V.oID
and DATEPART( wk, b.BookingDt )='44'
and DATEPART( yy, b.BookingDt )='2009'
) selweek
group by v.olocation
How the data is displayed should be a front-end issue, not a database issue. I wouldn't concentrate on things like putting "x" in a specific spot. Return the data that your application needs to fill in your calendar and have the front-end do that.
That said, in order to create results like what you're looking for, you're missing a set of data - the set of calendar days. You can do this with a temporary table, a CTE, or a permanent table in your database, but you basically need a table that gives you all of the days in question as a resultset. You can then LEFT OUTER JOIN from that table to your bookings table and use CASE to fill in values based on whether or not a matched bookings row was found.