SQL Server 2008 Reporting: Sum of Max of group - sql

I have a table like this in the report designer:
Category: 1 2 3 4 Total
Max Amount: x y z c ?
I need to get the total of Max Amount, but expressions will not let me take Sum(Max(amount)), and the add total is disabled for this cell.
The max amount row is an expression that takes the max of each category. The source data has repeated values, so I just take the max. For example:
Category Amount
1 4.6
1 4.6
1 4.6
2 5
3 4
Other columns in the table are different, but amount will be the same so I cannot only select distinct values.

Maybe something like this:
SELECT
SUM(t1.maxAmout)
FROM
(
SELECT
MAX(t.Amout) AS maxAmout,
t.Category
FROM
yourTable AS t
GROUP BY
t.Category
) AS t1
You can also do it like this. If you are using sql server 2005+:
SELECT
pvt.[1],
pvt.[2],
pvt.[3],
(
pvt.[1]+
pvt.[2]+
pvt.[3]
) AS Total
FROM
(
SELECT
t.Category,
t.Amout
FROM
yourTable AS t
) AS SourceTable
PIVOT
(
MAX(Amout)
FOR Category IN([1],[2],[3])
) AS pvt
EDIT
If you have a 1000 categories. Then a dynamic pivot would be the best solution. So like this:
Test data
CREATE TABLE #T
(
Category INT,
Amout FLOAT
)
INSERT INTO #T
VALUES
(1,4.6),
(1,4.6),
(1,4.6),
(2,5),
(3,4)
Unique column names
DECLARE #cols VARCHAR(MAX)
DECLARE #colsTotal VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY t.Category ORDER BY t.Amout) AS RowNbr,
t.*
FROM
#T AS t
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME(Category),
QUOTENAME(Category)),
#colsTotal=COALESCE(#colsTotal + '+ISNULL('+QUOTENAME(Category)+',0)',
'ISNULL('+QUOTENAME(Category)+',0)')
FROM
CTE
WHERE
CTE.RowNbr=1
ORDER BY
Category
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
'+#cols+',
(
'+#colsTotal+'
) AS Total
FROM
(
SELECT
t.Category,
t.Amout
FROM
#T AS t
) AS SourceTable
PIVOT
(
MAX(Amout)
FOR Category IN('+#cols+')
) AS pvt'
EXECUTE(#query)

WITH
aggregate
AS
(
SELECT
category,
MAX(amount) AS max_amount
FROM
yourTable
GROUP BY
category
)
SELECT
MAX(CASE WHEN category = 1 THEN max_amount ELSE NULL END) AS [1],
MAX(CASE WHEN category = 2 THEN max_amount ELSE NULL END) AS [2],
MAX(CASE WHEN category = 3 THEN max_amount ELSE NULL END) AS [3],
MAX(CASE WHEN category = 4 THEN max_amount ELSE NULL END) AS [4],
SUM(max_amount) AS [total]
FROM
aggregate

The following returns a single value:
SELECT DISTINCT SUM(MAX(Amount)) OVER ()
FROM atable
GROUP BY Category
You could use this as a subquery calculating the value of the Total column.
Alternatively, TOP (1) could be used instead of DISTINCT (don't know why I couldn't think of it before):
SELECT TOP (1) SUM(MAX(Amount)) OVER ()
FROM atable
GROUP BY Category

Related

Recursive CTE Pivot for rolling count in SQL Server?

Basically I have a table of membership dates and status changes and I want to create a rolling count of each number of members by status for each date like:
And I want to output it like:
Not really sure on the best way to construct this query I suppose I'd have to create a recursive query to count the events at the date and then pivot the output to create separate columns for the member status?
TIA
create table MyTable(status int,st datetime, memberID int)
insert into MyTable values(1,'2022-02-01',1),
(1,'2022-03-01' ,2),
(2,'2022-04-01',1)
select [st],[1],[2]
from (select [st],memberID,status from MyTable) as SrcTable
pivot(
count(memberID) for status in ([1],[2])
) as pivottable
order by [st] asc
But it doesnt aggregate in March like I want it to
Figured it out I put the last date as get_date() since that what my problem in real-life requires but I did make use of a recursive CTE and pivot
create table MyTable(status int,st datetime, memberID int)
insert into MyTable values(1,'2022-02-01',1),
(1,'2022-03-01' ,2),
(2,'2022-04-01',1);
with cte as (
select status,st,memberID,(case when Nxt_sts is null then 0 else Nxt_sts end)
as Nxt_sts,
(case when Nxt_st is null then FORMAT(DateAdd(Month,1,getdate()), 'yyyyMM01')
else Nxt_st end) as Nxt_st
from (select
A.status,A.st,A.memberID,B.status as Nxt_sts,B.st as nxt_st
from MyTable A
left join MyTable B
on A.MemberID = B.MemberID
and A.status+1 = B.status ) A
)
, cte_2 as
(
select status,st,memberID,Nxt_sts,Nxt_st
from cte
Union All
select status,DateAdd(Month, 1 ,st),memberID,Nxt_sts,Nxt_st from cte_2
where DateAdd(Month, 1 ,st) < Nxt_st
)
select [st],[1],[2],[3]
from (select [st],memberID,status from cte_2) as SrcTable
pivot(
count(memberID) for status in ([1],[2],[3])
) as pivottable
order by [st] asc

Sql Server - Variables inside select

there's any way to declare and use variables INSIDE a select statement?
my idea is to use several times the same nested select for diferent math operations.
i'm using SQL Server 2008 R2 and here is some example:
--Example Table
declare #product_table table(id int, name varchar(20), price int, active bit)
insert into #product_table
values
(1,'productA',100,1),
(2,'productB',50,1),
(3,'productC',20,0),
(4,'productD',300,1),
(5,'productE',80,0)
select
name,
(select sum(price) from #product_table where active = 1), -- <-- Declare this as a Variable
(select sum(price) from #product_table where active = 1),
(select sum(price) from #product_table where active = 1/ price),
(select sum(price) from #product_table where active = 0)
from #product_table
You can use a CTE for this:
;WITH cte AS
(
SELECT (select sum(price) from #product_table where active = 1) AS Sum1
,(select sum(price) from #product_table where active = 0) AS Sum0
)
select
name,
Sum1,
Sum1 / price,
sum0
from #product_table
cross join cte;
The CTE will return one single row thus CROSS JOIN will just add it to the result set.
If you need computed values depending on row-values, you can reach something similar with CROSS APPLY to calculate values row-by-row and use them with a speaking name.
Just to show the principles. If your computation would include some row values, you can use cross apply to get variables:
select
name,
Sum1,
Sum1 / price,
sum0
from #product_table
cross apply
(
SELECT (select sum(price) from #product_table where active = 1) AS Sum1
,(select sum(price) from #product_table where active = 0) AS Sum0
) A
Simply your query using window functions:
select name,
sum(case when active = 1 then price end) over () as price_1,
sum(case when active = 1 then price end) over () / price as ratio,
sum(case when active = 0 then price end) over () as price_0
from #product_table;
You can do what you want using subqueries or CTEs. However, you should learn to use the best functions for what you want to do.
You cannot Declare a Variable Inside a SELECT Statement, but Instead, you can Declare it outside and use it in the SELECT. Like this :
declare #product_table table(id int, name varchar(20), price int, active bit)
DECLARE #SumPrice1 INT,#SumPrice0 INT
insert into #product_table
values
(1,'productA',100,1),
(2,'productB',50,1),
(3,'productC',20,0),
(4,'productD',300,1),
(5,'productE',80,0)
SELECT
#SumPrice1 = sum(price)
from #product_table where active = 1
SELECT
#SumPrice0 = sum(price)
from #product_table where active = 0
select
name,
#SumPrice1,
#SumPrice1,
(#SumPrice1/ price),
#SumPrice0
from #product_table
Or Like This
;WITH CTE
AS
(
SELECT
active,
SumAmt = SUM(price)
FROM #product_table
GROUP BY active
)
SELECT
PT.[Name],
[1] SumAmt,
[1] SumAmt,
[1] /price SumAmtPrice,
[0] ZeroSum
FROM #product_table PT
JOIN CTE
PIVOT
(
SUM(SumAmt)
FOR
Active IN
(
[0],[1]
)
)P
ON 1=1
Or More Simply Like This
SELECT
[name],
SUM(CASE Active WHEN 1 THEN Price ELSE 0 END) OVER(PARTITION BY Active) ActiveSum,
SUM(CASE Active WHEN 0 THEN Price ELSE 0 END) OVER(PARTITION BY Active) ZeroSum
FROM #product_table

aggregation according to different conditions on same column

I have a table #tbl like below, i need to write a query like if there are more than 3 records availble
for particular cid then avg(val of particular cid ) for particular cid should be dispalyed against each id and if there are less than
3 records availble for particular cid then avg(val of all records availble).
Please suggest.
declare #tbl table(id int, cid int, val float )
insert into #tbl
values(1,100,20),(2,100,30),(3,100,25),(4,100,31),(5,100,50),
(6,200,30),(7,200,30),(8,300,90)
Your description is not clear, but I believe you need windowed functions:
WITH cte AS (
SELECT *, COUNT(*) OVER(PARTITION BY cid) AS cnt
FROM #tbl
)
SELECT id, (SELECT AVG(val) FROM cte) AS Av
FROM cte
WHERE cnt <=3
UNION ALL
SELECT id, AVG(val) OVER(PARTITION BY cid) AS Av
FROM cte
WHERE cnt > 3
ORDER BY id;
DBFiddle Demo
EDIT:
SELECT id,
CASE WHEN COUNT(*) OVER(PARTITION BY cid) <= 3 THEN AVG(val) OVER()
ELSE AVG(val) OVER(PARTITION BY cid)
END
FROM #tbl
ORDER BY id;
DBFiddle Demo2
You can try with the following. First calculate the average for each Cid depending in it's number of occurences, then join each Cid with the Id to display all table.
;WITH CidAverages AS
(
SELECT
T.cid,
Average = CASE
WHEN COUNT(1) >= 3 THEN AVG(T.val)
ELSE (SELECT AVG(Val) FROM #tbl) END
FROM
#tbl AS T
GROUP BY
T.cid
)
SELECT
T.*,
C.Average
FROM
#tbl AS T
INNER JOIN CidAverages AS C ON T.cid = C.cid
Given the clarifications in comments, I am thinking this is the intention
declare #tbl table(id int, cid int, val float )
insert into #tbl
values(1,100,20),(2,100,30),(3,100,25),(4,100,31),(5,100,50),
(6,200,30),(7,200,30),(8,300,90);
select distinct
cid
, case
when count(*) over (partition by cid) > 3 then avg(val) over (partition by cid)
else avg (val) over (partition by 1)
end as avg
from #tbl;
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=fdf4c4457220ec64132de7452a034976
cid avg
100 31.2
200 38.25
300 38.25
There are a number of aspects of a query like this that when run at scale though are going to be pretty bad on the query plan, I'd want to test this at a larger scale and tune before using.
The description was not clear on what happened if it was exactly 3, it mentions 'more than 3' and 'less than 3' - within this code the 'more than' was used to determine which category it was in, and less than interpreted to mean 'less than or equal to 3'

SQL group by if values are close

Class| Value
-------------
A | 1
A | 2
A | 3
A | 10
B | 1
I am not sure whether it is practical to achieve this using SQL.
If the difference of values are less than 5 (or x), then group the rows (of course with the same Class)
Expected result
Class| ValueMin | ValueMax
---------------------------
A | 1 | 3
A | 10 | 10
B | 1 | 1
For fixed intervals, we can easily use "GROUP BY". But now the grouping is based on nearby row's value. So if the values are consecutive or very close, they will be "chained together".
Thank you very much
Assuming MSSQL
You are trying to group things by gaps between values. The easiest way to do this is to use the lag() function to find the gaps:
select class, min(value) as minvalue, max(value) as maxvalue
from (select class, value,
sum(IsNewGroup) over (partition by class order by value) as GroupId
from (select class, value,
(case when lag(value) over (partition by class order by value) > value - 5
then 0 else 1
end) as IsNewGroup
from t
) t
) t
group by class, groupid;
Note that this assumes SQL Server 2012 for the use of lag() and cumulative sum.
Update:
*This answer is incorrect*
Assuming the table you gave is called sd_test, the following query will give you the output you are expecting
In short, we need a way to find what was the value on the previous row. This is determined using a join on row ids. Then create a group to see if the difference is less than 5. and then it is just regular 'Group By'.
If your version of SQL Server supports windowing functions with partitioning the code would be much more readable.
SELECT
A.CLASS
,MIN(A.VALUE) AS MIN_VALUE
,MAX(A.VALUE) AS MAX_VALUE
FROM
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS A
LEFT JOIN
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS B
ON A.CLASS = B.CLASS AND A.ROW_ID=B.ROW_ID+1
GROUP BY A.CLASS,CASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END
ORDER BY A.CLASS,cASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END DESC
ps: I think the above is ANSI compliant. So should run in most SQL variants. Someone can correct me if it is not.
These give the correct result, using the fact that you must have the same number of group starts as ends and that they will both be in ascending order.
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (class char(1),Value int);
insert into #temp values ('A',1);
insert into #temp values ('A',2);
insert into #temp values ('A',3);
insert into #temp values ('A',10);
insert into #temp values ('A',13);
insert into #temp values ('A',14);
insert into #temp values ('b',7);
insert into #temp values ('b',8);
insert into #temp values ('b',9);
insert into #temp values ('b',12);
insert into #temp values ('b',22);
insert into #temp values ('b',26);
insert into #temp values ('b',67);
Method 1 Using CTE and row offsets
with cte as
(select distinct class,value,ROW_NUMBER() over ( partition by class order by value ) as R from #temp),
cte2 as
(
select
c1.class
,c1.value
,c2.R as PreviousRec
,c3.r as NextRec
from
cte c1
left join cte c2 on (c1.class = c2.class and c1.R= c2.R+1 and c1.Value < c2.value + 5)
left join cte c3 on (c1.class = c3.class and c1.R= c3.R-1 and c1.Value > c3.value - 5)
)
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where PreviousRec is null) as Starts join
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where NextRec is null) as Ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
** Method 2 Inline views using not exists **
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value < t.Value and Value > t.Value -5 )
) Starts join
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value > t.Value and Value < t.Value +5 )
) ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
In both methods I use a select distinct to begin because if you have a dulpicate entry at a group start or end things go awry without it.
Here is one way of getting the information you are after:
SELECT Under5.Class,
(
SELECT MIN(m2.Value)
FROM MyTable AS m2
WHERE m2.Value < 5
AND m2.Class = Under5.Class
) AS ValueMin,
(
SELECT MAX(m3.Value)
FROM MyTable AS m3
WHERE m3.Value < 5
AND m3.Class = Under5.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m1.Class
FROM MyTable AS m1
WHERE m1.Value < 5
) AS Under5
UNION
SELECT Over4.Class,
(
SELECT MIN(m4.Value)
FROM MyTable AS m4
WHERE m4.Value >= 5
AND m4.Class = Over4.Class
) AS ValueMin,
(
SELECT Max(m5.Value)
FROM MyTable AS m5
WHERE m5.Value >= 5
AND m5.Class = Over4.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m6.Class
FROM MyTable AS m6
WHERE m6.Value >= 5
) AS Over4

How do i convert rows into columns?

i know this can be done with pivot but dont know how to put this in query..heres my table..
Id Date Code
1 1-2-2011 Code1
2 2-2-2011 Code2
Desired table:
Id 1-2-2011 2-2-2011
1 Code1 Null
2 Null Code2
Heres something i am trying but i want to know if there is any different way..
SELECT [Id], '1-2-2011','2-2-2011'
FROM ( SELECT [Id]
, Code
FROM #r
) p PIVOT ( Code
FOR [date] IN ('1-2-2011','2-2-2011')
) AS pvt
ORDER BY [Id]
The correct PIVOT query would be something like this
declare #r table (Mobile int, Calldate datetime, Dispo varchar(10))
insert #r select
1, '2011-02-21', 'Code1' union all select
2, '2011-02-22', 'Code2'
SELECT ID, [2011-02-21], [2011-02-22]
-- SELECT * << or just use this, which includes all columns
FROM (
SELECT Id, Date, Code
FROM #r) p
PIVOT (MAX(Code) FOR Date IN ([2011-02-21], [2011-02-22])) AS pvt
ORDER BY ID
Your query used
SELECT [Id], '1-2-2011','2-2-2011'
Which includes two FIXED-VALUE strings, which means the DATA is the string '1-2-2011', not a column name. You also needed MAX(Code) or some aggregate function to use when pivoting.
Pivoting in SQL Server requires you to know the columns in advances, which you need to list out in the FOR () bit. Otherwise, you will need to look at dynamic pivoting. This is true whether you use the PIVOT operator or the MAX(CASE pattern to pivot.
The MAX(CASE) pattern
select id,
MAX(case when date = '2011-02-21' then Code end) "2011-02-21",
MAX(case when date = '2011-02-22' then Code end) "2011-02-22"
from #r
group by id