Group by count once - sql-server

My data is like below:
ClassId ClassName StudentId Subject SubjectId
-----------------------------------------------------
1 ESL 12 English 20
1 ESL 13 Science 30
1 ESL 12 Social 40
1 ESL 12 Maths 50
Required output: parameters are Subject column values
ClassId ClassName TotalStudents SubjectIds
-----------------------------------------------
1 ESL 2 20, 40, 50, 30
When one student takes multiple subjects then count student only once, so in the above data 12 is one student id takes multiple subjects so counted only once. TotalStudents value is 2 (1 from student id 12 and 1 from student id 13)
I am not looking for how to display subjectIds column value in comma separated string.
Thanks in advance

COUNT DISTINCT then use STUFF for combined the subject
declare #temp table
(ClassId int,ClassName nvarchar(max),StudentId int,Subject nvarchar(max), SubjectId int)
insert into #temp values (1,'ESL',12,'English' , 20 )
insert into #temp values (1,'ESL',13,'Science' , 30 )
insert into #temp values (1,'ESL',12,'Social ' , 40 )
insert into #temp values (1,'ESL',12,'Maths ' , 50 )
select ClassId,ClassName,COUNT(DISTINCT StudentId) CNT,
STUFF( (SELECT ',' + CAST(t1.SubjectId AS NVARCHAR)
FROM #temp t1
WHERE StudentId = t1.StudentId
FOR XML PATH('')),
1, 1, '') SubjectIdS
from #temp
GROUP BY ClassId,ClassName
OUTPUT

DISTINCT can be applied inside aggregate functions.
SELECT COUNT(DISTINCT column_name) FROM table_name;

If you don't need to display the SubjectIds, then you need to use a GROUP BY clause to group the resultset by ClassId and ClassName.
SELECT ClassId, ClassName, COUNT(distinct StudentId) as TotalStudents
FROM MyTable
GROUP BY ClassId, ClassName
See this example at SqlFiddle

Related

Not able to filter the required data

Hi i have 1 table Student where data is inserted row wise and i want to select only those student whose marks are more than 50% in all the subjects and if in any subject marks are less than 50% then it should not select that student in output and all records should be excluded for that student and there is no primary key
i tried below code :
Select * into #temp1 from Student where percent >=0.5 and group by Roll_Number
i am getting error :
is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
and if try like this :
Select * into #temp1 from Student where percent >=0.5
then i am getting students who has even in 1 subject more than 50% which is not required in output
Table structure is as follows
Student_Name Roll_Number Subject Marks Percent
Ashutosh 1234 English 40 40%
Ishan 1231 Maths 60 60%
Atul 1232 Maths 30 30%
Ashutosh 1234 MAths 70 70%
now in output it should only give
Ishan 1234 Maths 60 60%
You can use count() over() in order to indicate for every student if any row has < 50%, then use this as a filter criteria:
with s as (
select *,
Count(case when perc<0.5 then 1 end) over(partition by Student_Name) pc
from Students
)
select *
from s
where Percent>=0.5 and pc=0
You can get the desired result by using a subquery / cte and a window function in order to check if the student has at least 50% in all subjects:
DECLARE #Student TABLE(
Student_Name VARCHAR(20)
,Roll_Number int
,Subject VARCHAR(20)
,Marks int
,Perc DECIMAL(5,2)
)
INSERT INTO #Student VALUES
('Ashutosh',1234,'English',40,0.4)
,('Ishan',1231,'Maths',60,0.6)
,('Atul',1232,'Maths',30,0.3)
,('Ashutosh',1234,'Maths',70,0.7);
WITH cteFilter AS(
SELECT *, ROW_NUMBER() OVER (PARTITION BY Student_Name, Roll_Number ORDER BY Perc ASC, Marks ASC) rn
FROM #Student
)
SELECT *
FROM cteFilter
WHERE rn = 1
AND Perc >= 0.5

How to set rows value as column in SQL Server?

I have a table tblTags (Date, Tagindex, Value)
The values in the table are:
Date Tagindex Value
---------------------------------
2017-10-21 0 21
2017-10-21 1 212
2017-10-21 2 23
2017-10-21 0 34
2017-10-21 1 52
2017-10-21 2 65
I want the result as :
Date 0 1 2
-------------------------------
2017-10-21 21 212 23
2017-10-21 34 52 65
For this I wrote the followring query
select *
from
(SELECT a.Date, a.Tagindex,a.value
FROM tblTag a) as p
pivot
(max(value)
for Tagindex in ( [tblTag])
) as pvt
But I get these errors:
Msg 8114, Level 16, State 1, Line 10
Error converting data type nvarchar to int.
Msg 473, Level 16, State 1, Line 10
The incorrect value "tblTag" is supplied in the PIVOT operator.
How to solve this issue.
I think can use a query like this:
;with t as (
select *
, row_number() over (partition by [Date],[Tagindex] order by (select 0)) seq
from tblTag
)
select [Date],
max(case when [Tagindex] = 0 then [Value] end) '0',
max(case when [Tagindex] = 1 then [Value] end) '1',
max(case when [Tagindex] = 2 then [Value] end) '2'
from t
group by [Date], seq;
SQL Server Fiddle Demo
SQL Server Fiddle Demo - with pivot
Note: In above query I use row_number() function to create a sequence number for each Date and Tagindex, But the trick is in using (select 0) that is a temporary field to use in order by part, that will not trusted to return arbitrary order of inserted rows.So, if you need to achieve a trusted result set; you need to have an extra field like a datetime or an auto increment field.
Try this:
DECLARE #tblTag TABLE
(
[Date] DATE
,[TagIndex] TINYINT
,[Value] INT
);
INSERT INTO #tblTag ([Date], [TagIndex], [Value])
VALUES ('2017-10-21', 0, 21)
,('2017-10-21', 1, 212)
,('2017-10-21', 2, 23)
,('2017-10-22', 0, 34)
,('2017-10-22', 1, 52)
,('2017-10-22', 2, 65);
SELECT *
FROM #tblTag
PIVOT
(
MAX([value]) FOR [Tagindex] IN ([0], [1], [2])
) PVT;
You need to say exactly which are the PIVOT columns. If you are going to have different values for the TagIndex and you cannot hard-coded them, you need to use dynamic PIVOT.
Also, you need to be sure you have a way to group the tagIndex values in one row. For example, different date (as in my test data), ID column which is marking when a row is inserted or something else (group ID column or date added column).

Converting rows to columns

I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2

SQL - Query log from Users table

Based on the following example : (it is a "QueryLog" table, this table store interactions between a user and two different products N and R):
Id Date UserID Product
--------------------------------------------------
0 2013-06-09 14:50:24.000 100 N
1 2013-06-09 15:27:23.000 100 N
2 2013-06-09 15:29:23.000 100 N
3 2013-06-17 15:31:23.000 100 N
4 2013-06-17 15:32:23.000 100 N
5 2014-05-19 15:30:23.000 250 N
6 2014-07-19 15:27:23.000 250 N
7 2014-07-19 15:27:23.000 333 R
8 2014-08-19 15:27:23.000 333 R
Expected results :
Count
-----
1
(Only UserID 250 is inside my criteria)
If one user interacts 10 times with the product in only one month, he's not in my criteria.
To resume, I am looking for :
The Number of distinct users that had interactions with product N on at least more than one month (what ever the number of interactions this user may have had during a single month)
This is the code I've tried:
select distinct v.UserID, v.mois , v.annee
from
(select c.UserID , c. mois, c.annee, COUNT(c.UserID) as frequence
from
(
SELECT
datepart(month,[DATE]) as mois,
datepart(YEAR,[DATE]) as annee ,
Username,
UserID,
Product
FROM QueryLog
where Product = 'N'
) c
group by c.UserID, c.annee, c.mois
) v
group by v.UserID, v.mois, v.annee
try this:
DECLARE #YourTable table (Id int, [Date] datetime, UserID int, Product char(1))
INSERT INTO #YourTable VALUES (0,'2013-06-09 14:50:24',100 ,'N')
,(1,'2013-06-09 15:27:23',100 ,'N')
,(2,'2013-06-09 15:29:23',100 ,'N')
,(3,'2013-06-17 15:31:23',100 ,'N')
,(4,'2013-06-17 15:32:23',100 ,'N')
,(5,'2014-05-19 15:30:23',250 ,'N')
,(6,'2014-07-19 15:27:23',250 ,'N')
,(7,'2014-07-19 15:27:23',333 ,'R')
,(8,'2014-08-19 15:27:23',333 ,'R')
;WITH MultiMonthUsers AS
(
select
UserID
FROM (select
UserID
FROM #YourTable
WHERE product='N'
GROUP BY UserID, YEAR([Date]),MONTH([Date])
)dt2
GROUP BY UserID
HAVING COUNT(*)>1
)
SELECT COUNT(*) FROM MultiMonthUsers
Depending on number of rows and indexes, this will run slow. Using YEAR([Date]),MONTH([Date]) will prevent any index usage.
I think this will do it, but I need a better dataset to test with:
SELECT COUNT(*)
FROM (
--roll all month/user records into single row
SELECT UserID, datediff(month 0, [date]) As MonthGroup
FROM QueryLog
WHERE Product='N'
GROUP BY datediff(month 0, [date]), UserId
) t
-- look for users with multiple rows
GROUP BY UserID
HAVING COUNT(UserID) > 1
Seems like there should be a way to roll this up further, to avoid the need for the nested select.

SQL-SERVER Get Result from table where Value like 1-9

I have a table WHERE there's a column that has QTY Ranges like 1-5, 6-9 etc and prices in another column. i.e
Price QTY
------------------------
Price Qty Range
----- ----------
45 1-5
35 6-9
30 10-18
Now I want to get the result from the table where Qty is 7 Therefore Price returned should be 35 ( since Qty 7 falls in range 6-9
Any help greatly appriciated
Try this :-
Declare #val int
Set #val=7
;with cte(price,startVal,endVal) as
( Select price,
parsename(replace([Qty Range],'-','.'),2),
parsename(replace([Qty Range],'-','.'),1)
from yourTable
)
Select Price from cte
where #val between startVal and endVal
Result : 35
Demo in SQL FIDDLE
If you can't redesign the table to be sane, you can use a couple of CTEs to reconstruct it as a sane table for this query:
declare #PriceRanges table (Price int,QtyRange varchar(20))
insert into #PriceRanges (Price,QtyRange) values
(45,'1-5'),
(35,'6-9'),
(30,'10-18')
declare #Search int
set #Search = 7
;with FoundDashes as (
select Price,QtyRange,CHARINDEX('-',QtyRange) as DashPos
from #PriceRanges
)
, SaneRanges as (
select Price,CONVERT(int,SUBSTRING(QtyRange,1,DashPos-1)) as LowRange,CONVERT(int,SUBSTRING(QtyRange,DashPos+1,8000)) as HighRange
from FoundDashes
)
select Price from SaneRanges where #Search between LowRange and HighRange
Produces 35 as a result.

Resources