group by clause with rollup - sql-server-2005

I'm trying to use group by with rollup clause within sql server 2005 but I'm having some problem.
This is a simple dump
create table group_roll (
id int identity,
id_name int,
fname varchar(50),
surname varchar(50),
qty int
)
go
insert into group_roll (id_name,fname,surname,qty) values (1,'john','smith',10)
insert into group_roll (id_name,fname,surname,qty) values (1,'john','smith',30)
insert into group_roll (id_name,fname,surname,qty) values (2,'frank','white',5)
insert into group_roll (id_name,fname,surname,qty) values (1,'john','smith',8)
insert into group_roll (id_name,fname,surname,qty) values (2,'frank','white',10)
insert into group_roll (id_name,fname,surname,qty) values (3,'rick','black',10)
go
If I run this simple query
select id_name,fname,surname,sum(qty) as tot
from group_roll
group by id_name,fname,surname
I get
1 john smith 48
2 frank white 15
3 rick black 10
I'd like to have
1 john smith 48
2 frank white 15
3 rick black 10
Total 73
This is what I've tried to reach my goal
select
case when grouping(id_name) = 1 then 'My total' else cast(id_name as char) end as Name_id ,
fname,surname,sum(qty) as tot
from group_roll
group by id_name,fname,surname
with rollup
order by case when id_name is null then 1 else 0 end, tot desc
but my result is
1 john smith 48
1 john NULL 48
1 NULL NULL 48
2 frank white 15
2 frank NULL 15
2 NULL NULL 15
3 rick black 10
3 rick NULL 10
3 NULL NULL 10
My total NULL NULL 73
Where is my mistake?
EDIT.
I could solve my problem making
select * from (
select cast(id_name as char) as id_name,fname,surname,sum(qty) as tot
from group_roll
group by id_name,fname,surname
union
select 'Total',null,null,sum(qty) from group_roll ) as t
order by case when id_name = 'Total' then 1 else 0 end,tot desc
but I'd like to understand if rollup can solve my problem.

You cannot do it within the statement itself, however you can filter the ROLLUP set excluding the intermediate rollups, i.e. where any one but not all rows are being grouped:
select
case when grouping(id_name) = 1 then 'My total' else cast(id_name as char) end as Name_id,
fname,
surname,
sum(qty) as tot
from group_roll
group by id_name, fname, surname
with rollup
having grouping(id_name) + grouping(fname) + grouping(surname) in (0 , 3)
Or similar to your solution but referencing the original query;
;with T as (
select cast(id_name as varchar(128)) as id_name,fname,surname,sum(qty) as tot
from group_roll
group by id_name,fname,surname
) select * from T union all select 'Total:',null,null, SUM(tot) from T
FWIW SQL 2008 allows;
select
case when grouping(id_name) = 1 then 'My total' else cast(id_name as char) end as Name_id,
fname,
surname,
sum(qty) as tot
from group_roll
group by grouping sets((id_name, fname, surname), ())

Related

student passed all subject in atleast one attempt

Consider a data below:
STUDENT_ID SUBJECT_PASSING_FLG testnumber
123 YNYNNY 1
123 YNYNYY 2
123 NNNYNN 3
456 YYYYYY 2
789 YNYNYN 1
789 NYNYNY 3
Expected Output:
STUDENT_ID SUBJECT_PASSING_FLG
123 YNYYYY
456 YYYYYY
789 YYYYYY
Each character in SUBJECT_PASSING_FLG indicate the result of each subject and test number indicate the sequence number the test was attempted.
We want to find the final result as , if the student passed in atleast one attaempt in the subject then set flag as Y else N
Since you don't mention which database you use. I try it in sql-server 2019.
I can only come up solution if you have separator in SUBJECT_PASSING_FLG string.
schema
CREATE TABLE data
(
STUDENT_ID [nvarchar](50) NOT NULL,
SUBJECT_PASSING_FLG [nvarchar](50) NOT NULL,
testnumber int NOT NULL,
);
table
insert into data values
('123', 'Y,N,Y,N,N,Y', 1),
('123', 'Y,N,Y,N,Y,Y', 2),
('123', 'N,N,N,Y,N,N', 3),
('456', 'Y,Y,Y,Y,Y,Y', 2),
('789', 'Y,N,Y,N,Y,N', 1),
('789', 'N,Y,N,Y,N,Y', 3)
sql
select
STUDENT_ID,
STRING_AGG(case when total>0 then 'Y' else 'N' end, ',') WITHIN group ( order by num asc) as SUBJECT_PASSING_FLG
from (
select STUDENT_ID, num, sum(case value when 'Y' then 1 else 0 end) as total
from (
select
STUDENT_ID,
value,
(ROW_NUMBER() OVER(PARTITION by STUDENT_ID order by STUDENT_ID asc)-1)%6+1 as num
from data
cross APPLY STRING_SPLIT(SUBJECT_PASSING_FLG, ',')
) as q
group by STUDENT_ID, num
) as p
group by STUDENT_ID
result
STUDENT_ID SUBJECT_PASSING_FLG
123 Y,N,Y,Y,Y,Y
456 Y,Y,Y,Y,Y,Y
789 Y,Y,Y,Y,Y,Y
dbfiddle
insert into dataval values
(1,'YYNY',1),
(1,'YNNY',2),
(1,'NYNY',3),
(2,'YNNY',1),
(3,'YNNY',1),
(4,'YNNY',3),
(3,'YYNY',2),
(4,'YNYY',3);
WITH cte_seq AS (
SELECT 1 AS seq
UNION ALL
SELECT seq + 1 AS seq FROM cte_seq WHERE seq < 4
), cross_cte AS (
SELECT * FROM cte_seq CROSS apply dataval
), flag_wise_status AS (
SELECT student_id,
testnumber,
Substring(subject_passing_flg, seq, 1) AS subject_status,
seq AS subject_num
FROM cross_cte
), max_flag AS (
SELECT student_id,
subject_num,
Max(subject_status) AS subject_status
FROM flag_wise_status
GROUP BY student_id, subject_num
)
SELECT student_id,
String_agg(subject_status, '') AS result
FROM max_flag
GROUP BY student_id;
Result:
1 YYNY
2 YNNY
3 YYNY
4 YNYY
dbfiddle

How to write a query to allow null in minimum function

I need to write a query to get minimum values for a column from a table and if the value is null then I want to include that row. I wrote following query but it ignores the null values. How I can modify this query to include null values in the result?
select * from TABLE where COLUMN = (select min(COLUMN) from TABLE );
If the table is like below
|ID | VALUE | NAME
101 1 John
101 null John
102 1 Bill
103 1 Tina
103 null Tina
104 null James
Result Should be
|ID | VALUE | NAME
101 1 John
102 1 Bill
103 1 Tina
104 null James
You need distinct on:
with my_table(id, value, name) as (
values
(101, 1, 'John'),
(101, null, 'John'),
(102, 1, 'Bill'),
(103, 1, 'Tina'),
(103, null, 'Tina'),
(104, null, 'James')
)
select distinct on (id) *
from my_table
order by id, value
id | value | name
-----+-------+-------
101 | 1 | John
102 | 1 | Bill
103 | 1 | Tina
104 | | James
(4 rows)
Distinct on is a fantastic feature specific for Postgres. An alternative in other RDBMS may be:
select t.id, t.value, t.name
from my_table t
join (
select id, min(value) as value
from my_table
group by id
) u on u.id = t.id and u.value is not distinct from t.value;
Note, you should use is not distinct from because value may be null.
SQL SERVER
select DISTINCT j.ID,j.VALUE,j.NAME from Table1 j
join (
select id, MIN(VALUE) VALUE from Table1
group by id
) as t
on t.ID = j.ID and (t.VALUE = j.VALUE or t.VALUE is null)
You cannot do an equals (=) for a null value, you have to check is null or so. So one simple solution is to default the null value to a number that would not otherwise be used:
select * from TABLE where coalesce(COLUMN, -9999) = (select min(coalesce(COLUMN,-9999)) from TABLE );
The coalesce function returns the first non-null value passed to it.
with c as (
select column as c
from table
order by column nulls first
limit 1
)
select *
from table cross join c
where column = c or column is null
If you want to user order by:
select t.*
from t
order by t.column asc nulls first
limit 1;
Alternatively, use rank():
select t.*
from (select t.*,
rank() over (order by col asc nulls first) as seqnum
from t
) t
where seqnum = 1;
I hope this solve your problem.
SELECT id,
CASE WHEN MIN(
CASE WHEN value IS NULL THEN 0 ELSE 1 END) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
or using COALESCE.
SELECT id,
CASE WHEN MIN(COALESCE(value, 0)) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
I am on mobile phone now, so I cannot test.

How to do Sum Operation in SQl Pivot Query

Here i have a simple table Name Cust With CustName,PurshaseItem,Price.I wrote a Simple PIVOT query its Pivoting the data but i wana to show sum of the amot
Here i need Grand total
Pivot Query
[![select custName,
\[Shoes\] as Shoes,
\[Colgate\] as Colgate,
\[Cloths\] as Cloths
FROM
(select custName,Price,PurchaseItem FROM Cust
) AS PIVOTData
PIVOT(
sum(Price) FOR PurchaseItem
IN (Shoes,Colgate,Cloths)
)AS PIVOTING][1]][1]
custname Shoes Colgate GrandTotal
xyz 12 10 22
lmn 1 2 3
You can try this:
CREATE TABLE CUST (custName VARCHAR(10),
price INT,
PurchaseItem VARCHAR(10)
)
INSERT INTO CUST VALUES ('aaaa', 1,'Colgate')
INSERT INTO CUST VALUES ('aaaa', 2,'Shoes')
INSERT INTO CUST VALUES ('aaaa', 3,'Cloths')
INSERT INTO CUST VALUES ('bbbb', 4,'Colgate')
INSERT INTO CUST VALUES ('bbbb', 5,'Shoes')
INSERT INTO CUST VALUES ('bbbb', 6,'Cloths')
select *
FROM
(select custName, SUM(Price) AS Price ,
CASE WHEN GROUPING(PurchaseItem)=1 THEN 'TOT_PRICE' ELSE PurchaseItem END AS PurchaseItem
FROM Cust
group by rollup(PurchaseItem), custName
) AS PIVOTData
PIVOT(sum(Price) FOR PurchaseItem IN (Shoes,Colgate,Cloths,TOT_PRICE)) AS PIVOTING
Output:
custName Shoes Colgate Cloths TOT_PRICE
---------- ----------- ----------- ----------- -----------
aaaa 2 1 3 6
bbbb 5 4 6 15
You can just add this to your select
coalesce([Shoes], 0) + coalesce([Colgate], 0) + coalesce([Cloths], 0) as GranTotal
The coalesce is needed to avoid weird behaviours when one of the results is null.

Need to write SQL Server query to return sum of unique values (based on one column)

My table looks like this:
Supplier Reference Description Total
--------------------------------------------------
smiths BP657869510L NULL 42
smiths BP657869510L NULL 42
smiths BP654669510L No. 5621 13
smiths BP654669510L No. 5621 13
corrigan 15:51 Order 23542 23
corrigan 15:51 Order 23542 23
williams 14015 Block B 19
williams 14015 Block B 19
I would like to write a T-SQL query to
return the list of transactions with each supplier, eliminating duplicate entries based on the Reference column.
return the total sum of transactions with each supplier, again eliminating duplicate entries based on the Reference column.
So the results I would want to return based on the data above would be
Supplier Reference Description Total
---------------------------------------------------
smiths BP657869510L NULL 42
smiths BP654669510L No. 5621 13
corrigan 15:51 Order 23542 23
williams 14015 Block B 19
and for the second requirement:
Supplier Total
---------------------
smiths 55
corrigan 23
williams 19
Is this possible? Please note that values in other columns may differ even though the Reference column contains the same value. It doesn't matter if this occurs, I am only concerned with rows which contain a distinct or unique Reference value.
declare #tempData table
(
supplier nvarchar(20),
reference nvarchar (20),
xDescription nvarchar(20),
total int
);
insert into #tempData
select 'smiths', 'BP657869510L' ,NULL, 42 union all
select 'smiths', 'BP657869510L' ,NULL, 42 union all
select 'smiths', 'BP654669510L' ,'No. 5621', 13 union all
select 'smiths', 'BP654669510L' ,'No. 5621', 13 union all
select 'corrigan', '15:51' ,'Order 23542', 23 union all
select 'corrigan', '15:51' ,'Order 23542', 23 union all
select 'williams', '14015' ,'Block B', 19 union all
select 'williams', '14015' ,'Block B', 19
;
select distinct x.supplier,
SUM(X.total)OVER(PARTITION BY x.supplier )As Total from
(Select a.supplier,a.reference,a.xDescription,a.total from #tempData a
GROUP BY a.supplier,a.reference,a.xDescription,a.total) X
GROUP BY x.supplier,X.total
As per a comment from the OP Total is always the same for Reference, but Description can change. DISTINCT is equivalent to a GROUP BY all the columns in the SELECT
To get the first requirement a distinct is enough, if it's possible to drop the Description column
SELECT DISTINCT
Supplier
, Reference
, Total
FROM myTable
if it's not possible then a NULL, a MAX or something on the same line can be done, in the query below a NULL is returned if there are more then one values for the group, otherwise the single value is outputted
SELECT Supplier
, Reference
, Description = CASE WHEN COUNT(DISTINCT Description) > 1 THEN NULL
ELSE MAX(Description)
END
, Total
FROM myTable
GROUP BY Supplier, Reference, Total
To get the second the above query can be used as a CTE for the main query where a GROUP BY is added, in this case the Description columns is not needed so is dropped.
With dValue AS (
SELECT DISTINCT
Supplier
, Reference
, Total
FROM myTable
)
SELECT Supplier
, SUM(Total) Total
FROM dValue
GROUP BY Supplier
If you have a version of SQLServer where the CTE are not possible the first query can be used as a subquery to get the same result
SELECT Supplier
, SUM(Total) Total
FROM (SELECT DISTINCT Supplier, Reference, Total
FROM myTable) dValue
GROUP BY Supplier
Try below sql
assuming #tempData is your table name.
declare #tempData table
(
supplier nvarchar(20),
reference nvarchar (20),
xDescription nvarchar(20),
total int
);
insert into #tempData
select 'smiths', 'BP657869510L' ,NULL, 42 union all
select 'smiths', 'BP657869510L' ,NULL, 42 union all
select 'smiths', 'BP654669510L' ,'No. 5621', 13 union all
select 'smiths', 'BP654669510L' ,'No. 5621', 13 union all
select 'corrigan', '15:51' ,'Order 23542', 23 union all
select 'corrigan', '15:51' ,'Order 23542', 23 union all
select 'williams', '14015' ,'Block B', 19 union all
select 'williams', '14015' ,'Block B', 19
;
select
a.supplier
, a.reference
, a.xDescription
, a.total
from #tempData a
group by a.supplier
, a.reference
, a.xDescription
, a.total
;
/*
supplier reference xDescription total
-------------------- -------------------- -------------------- -----------
corrigan 15:51 Order 23542 23
smiths BP654669510L No. 5621 13
smiths BP657869510L NULL 42
williams 14015 Block B 19
*/
with cte as
(
select
a.supplier
, a.reference
, a.xDescription
, a.total
from #tempData a
group by a.supplier
, a.reference
, a.xDescription
, a.total
)
select
distinct c.supplier, sum(c.total) over(partition by c.supplier) as total
from cte c
;
/*
supplier total
-------------------- -----------
corrigan 23
smiths 55
williams 19
*/
UPDATE
as requested, the aim for this query is to include Separate record that has the same supplier with different description: example supplier smith
Dense_Rank() will fulfill this request (http://technet.microsoft.com/en-us/library/ms173825(v=sql.90).aspx)
with cte as
(
select
a.supplier
, a.reference
, a.xDescription
, a.total
,dense_rank() over(partition by a.supplier order by a.supplier, a.xDescription) as dRow
from #tempData a
group by a.supplier
, a.reference
, a.xDescription
, a.total
)
select
distinct c.supplier, sum(c.total) over(partition by c.supplier,drow) as total
from cte c
;
/*
supplier total
-------------------- -----------
corrigan 23
smiths 13
smiths 42
williams 19
*/
View All field
with cte as
(
select
a.supplier
, a.reference
, a.xDescription
, a.total
,dense_rank() over(partition by a.supplier order by a.supplier, a.xDescription) as dRow
from #tempData a
group by a.supplier
, a.reference
, a.xDescription
, a.total
)
select
distinct c.supplier, c.reference,c.xDescription, sum(c.total) over(partition by c.supplier,drow) as total
from cte c
;

Retrieve matching rows using join

This is a simplified version of my problem.
I have table like below
Id Name SNumber
100 XYZ 123
100 XYZ 123
101 ABC 123
103 QAZ 123
100 XYZ 971
100 XYZ 872
100 XYZ 659
102 PQR 145
102 PQR 707
103 QAZ 421
I want to count rows having Snumber as '123' ie Total column and rows having Snumber not as '123' i.e. otherTotal column
Id Name Total OtherTotal
100 XYZ 2 3
101 ABC 1 0
102 PQR 0 2
103 QAZ 1 1
What I am doing is using join
Select xx.*,otherTotal
From
( Select Id,Name,count(*) as Total
From table
Where Snumber like '123'
Group By id,name
)xx
Inner join
( Select Id,Name,count(*) as otherTotal
From table
Where Snumber not like '123'
Group By id,name
)yy
On xx.Id=yy.Id
But this will only return rows if particular Id has both Snumber as 123 and not as 123
Data returned is like below
Id Name Total OtherTotal
100 XYZ 2 3
103 QAZ 1 1
Now there is no guarntee that a particular Id will always have Snumber as 123 so I can't use Left or Right join. How to solve this quagmire ? Giggity
Try this:
SELECT id, name,
COUNT(CASE WHEN SNumber = 123 THEN 1 END) Total,
COUNT(CASE WHEN SNumber <> 123 THEN 1 END) OtherTotal
FROM t
GROUP BY id, name
ORDER BY id
Fiddle here.
select
Id, Name,
sum(case when SNumber = 123 then 1 else 0 end) as Total,
sum(case when SNumber <> 123 then 1 else 0 end) as OtherTotal
from Table1
group by Id, Name
order by Id
or
select
Id, Name,
count(*) - count(nullif(SNumber, 123)) as Total,
count(nullif(SNumber, 123)) as OtherTotal
from Table1
group by Id, Name
order by Id
sql fiddle demo
try this one.
DECLARE #TABLE TABLE (ID INT, NAME VARCHAR(40), SNUMBER INT)
INSERT INTO #TABLE
VALUES
(100 ,'XYZ', 123),
(100 ,'XYZ', 123),
(101 ,'ABC', 123),
(103 ,'QAZ', 123),
(100 ,'XYZ', 971),
(100 ,'XYZ', 872),
(100 ,'XYZ', 659),
(102 ,'PQR', 145),
(102 ,'PQR', 707),
(103 ,'QAZ', 421)
SELECT
ID,
NAME,
(
SELECT
COUNT(SNUMBER) FROM #TABLE B
WHERE
SNUMBER = '123' AND A.ID = B.ID
) AS TOTAL,
(
SELECT
COUNT(SNUMBER) FROM #TABLE B
WHERE
SNUMBER <> '123' AND A.ID = B.ID
) AS OTHERTOTAL
FROM
#TABLE A
GROUP BY ID, NAME