Show multiple rows and columns using subquery under SELECT clause - sql-server

I want to show the number of clerks and managers in each department.
Here is my code:
SELECT DISTINCT
deptno AS 'DEPARTMENT NUMBER',
(SELECT COUNT(*)
FROM EMP
WHERE JOB = 'CLERK'
GROUP BY DEPTNO) AS 'NUMBER OF CLERKS',
(SELECT COUNT(*)
FROM EMP
WHERE JOB = 'MANAGER'
GROUP BY DEPTNO) AS 'NUMBER OF MANAGER'
FROM
EMP
GROUP BY
deptno
Below is the error returned:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I wanted to show the result like this:
+-------------------+------------------+---------------------+
| DEPARTMENT NUMBER | NUMBER OF CLERKS | NUUMBER OF MANAGERS |
+-------------------+------------------+---------------------+
| 10 | 1 | 1 |
| 20 | 2 | 1 |
| 30 | 1 | 1 |
+-------------------+------------------+---------------------+

This kind of calculation is usually easiest to do using sum + case with something like this:
SELECT
deptno,
sum (case when JOB = 'CLERK' then 1 else 0 end) AS [NUMBER OF CLERKS],
sum (case when JOB = 'MANAGER' then 1 else 0 end) AS [NUMBER OF MANAGERS]
FROM
EMP
GROUP BY
deptno

you can also do it using PIVOT
DECLARE #EMP table (Dep_No INT, Job VARCHAR(20))
INSERT INTO #EMP
VALUES (10,'clerk'),
(10,'clerk'),
(10,'clerk'),
(10,'Manager'),
(20,'clerk'),
(20,'clerk'),
(20,'clerk'),
(20,'clerk'),
(20,'clerk'),
(20,'clerk'),
(20,'Manager'),
(20,'Manager'),
(30,'clerk'),
(20,'Manager'),
(30,'clerk')
SELECT Dep_No , [clerk] Number_Of_Clerks ,[Manager] Number_of_Managers
FROM #EMP
PIVOT
(
COUNT(Job) FOR Job in ([clerk], [Manager])
) W

Related

SQL Server how to sum max for specific category?

Got a problem when constructing a analysis SQL using SQL Server
The raw data as below
GameID | UsrRegID | Score_User
281 | 1 | 1
281 | 1 | 2
281 | 1 | 3
282 | 1 | 0
282 | 1 | 0
282 | 1 | 1
283 | 1 | 2
283 | 1 | 3
Below is the expect output result:
Distinct_Count_GameID | UsrRegID | Score_User
3 | 1 | 7
The logic for calculating the Score_user as below:
Sum(Max(Score_user) for each GemeID)
So the result need to be 3+1+3=7.
Can using the pure SQL to get the above expecting output?
I think we need to aggregate twice here. One option uses ROW_NUMBER:
WITH cte AS (
SELECT GameID, UsrRegID, Score_User,
ROW_NUMBER() OVER (PARTITION BY GameID, UsrRegID ORDER BY Score_User DESC) rn
FROM yourTable
)
SELECT
UsrRegID,
COUNT(DISTINCT GameID) AS Distinct_Count_GameID,
SUM(Score_User) AS Score_User
FROM cte
WHERE rn = 1
GROUP BY
UsrRegID;
You can't do an aggregate of an aggregate on the same SELECT, you can chain them together with CTE or subqueries.
;WITH Maxs AS
(
SELECT
T.GameID,
T.UsrRegID,
MaxScore = MAX(T.Score_User)
FROM
YourTable AS T
GROUP BY
T.GameID,
T.UsrRegID
)
SELECT
M.UsrRegID,
Distinct_Count_GameID = COUNT(DISTINCT(M.GameID)),
Score_User = SUM(M.MaxScore)
FROM
Maxs AS M
GROUP BY
M.UsrRegID
You can also try like following.
SELECT Count(DISTINCT [rgameid]) Distinct_Count_GameID,
Count(DISTINCT [usrregid]) UsrRegID,
(SELECT Sum(M)
FROM (SELECT Max([score_user]) M
FROM [TableName]
GROUP BY [rgameid])t) AS Score_User
FROM [TableName]
DEMO
First find maximum value of score for each GameId and UsrRegID and then find SUM() for the column, Score_User and group it by the columns, GameID and UsrRegID using GROUP BY clause.
Query
select count(distinct [t].[GameID]) as [GameID], [t].[UsrRegID],
sum([t].[Score_User]) as [Score_User] from(
select [GameID], [UsrRegID], max([Score_User]) as [Score_User]
from [your_table_name]
group by [GameID], [UsrRegID]
) as [t]
group by [t].[UsrRegID];
Or, give a row number based on the descending order of score value and group by GameID and UsrRegID. Then find the count of distinct GameId and sum of maximum score.
Query
;with cte as(
select [rn] = row_number() over(
partition by [GameID], [UsrRegID]
order by [Score_User] desc
), *
from [your_table_name]
)
select count(distinct [GameID]) as [GameID], [UsrRegID],
sum([Score_User]) as [Score_User] from cte
where [rn] = 1
group by [UsrRegID];
Aggregates and a COUNT(Distinct GameID):
declare #raw as table (GameID int, UsrRegID int, Score_user int)
insert into #raw values (281, 1, 1)
,(281, 1, 2)
,(281, 1, 3)
,(282, 1, 0)
,(282, 1, 0)
,(282, 1, 1)
,(283, 1, 2)
,(283, 1, 3)
select count(distinct GameID) as Distinct_Count_GameID, UsrRegID, sum(max_score_user)
from
(
select GameID
, UsrRegID
, max(score_user) as max_score_user
from #raw
group by GameID, UsrRegID
) a
group by a.UsrRegID

Select First, Max, and Last non-null value per group

Trying to select, per group, the first and last values (chronologically) as well as the max value. I had written a query that works fine except it does not handle the NULL values. I need it to ignore NULL values.
Here's an example:
DECLARE #T table (
LabName VARCHAR(20)
, CreatedOn date
, LabValue int
)
INSERT INTO #T
( LabName,CreatedOn,LabValue )
VALUES
('Creatinine', '2016-01-01', NULL)
, ('Creatinine', '2016-02-01', 15)
, ('Creatinine', '2016-03-01', 20)
, ('Creatinine', '2016-04-01', 19)
, ('SGOT (ST)', '2016-01-01', 25)
, ('SGOT (ST)', '2016-02-01', 31)
, ('SGOT (ST)', '2016-03-01', 25)
, ('SGOT (ST)', '2016-04-01', NULL)
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM #T
) AS T
It was working fine until I realized some labs aren't run on some dates. Once I put some NULLs into the test data, the results for First and Last will include them.
Here is the result I get:
+------------+------------+----------+-----------+
| LabName | FirstValue | MaxValue | LastValue |
+------------+------------+----------+-----------+
| Creatinine | NULL | 20 | 19 |
| SGOT (ST) | 25 | 31 | NULL |
+------------+------------+----------+-----------+
Here is the result I want:
+------------+------------+----------+-----------+
| LabName | FirstValue | MaxValue | LastValue |
+------------+------------+----------+-----------+
| Creatinine | 15 | 20 | 19 |
| SGOT (ST) | 25 | 31 | 25 |
+------------+------------+----------+-----------+
Use conditional aggregation with ROW_NUMBER():
SELECT LabName,
MAX(CASE WHEN seqnum_asc = 1 THEN LabValue END) as FirstValue,
MAX(LabValue) as MaxValue,
MAX(CASE WHEN seqnum_desc = 1 THEN LabValue END) as LastValue
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY LabName
ORDER BY (CASE WHEN LabValue IS NOT NULL THEN 1 ELSE 2 END),
CreatedOn
) as seqnum_asc,
ROW_NUMBER() OVER (PARTITION BY LabName
ORDER BY (CASE WHEN LabValue IS NOT NULL THEN 1 ELSE 2 END),
CreatedOn DESC
) as seqnum_desc
FROM #T t
) T
GROUP BY LabName;
As you said there are 13 such columns where you need to check not null values.
I think you should first filter all not null values using CTE,then using CTE you can write your actual query.CTE will reduce your result set and applying window function on reduce resultset will give better performance.
BTW,13 such columns appear t be bad DB design.you may have to 100 query in future.
IMHO, DISTINCT often indicate bad DB design than query.
;With CTE as
(-- try to reduce resultset if possible
SELECT * FROM #T
WHERE LabValue IS NOT NULL
)
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM CTE
) AS T
Your database is handling NULL values properly.
First value for Creatinine is actually null and last value for SGOT (ST) is null as well.
If you wish to discard rows with null values just add it in the WHERE clause:
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM #T
WHERE LabValue IS NOT NULL
) AS T;

Group by a set of Values (Check-In or Check-Out)

I have a simple table which records people clocking-in and clocking out like so.
Id | EmployeeNumber | InOutId | InOutDateTime
-----------------------------------------------------
1 | 505 | IN | 2015-03-24 08:32:42:000
1 | 506 | IN | 2015-03-24 08:35:47:000
1 | 507 | IN | 2015-03-24 08:46:12:000
1 | 505 | OUT | 2015-03-24 16:59:00:000
1 | 506 | OUT | 2015-03-24 17:05:00:000
I want to show the total people currently IN and those currently OUT. In other words:
- Total IN means those that do not have a corresponding OUT for that given day. - Total OUT means those that do have an IN and an OUT for that given day.
So, based on my table above, I want to get the following results:
TotalCurrentlyIn | TotalCurrentlyOut
-----------------------------------------
1 | 2
This is what I have so far:
DECLARE #d date;
set #d = cast('2015-03-24 15:02:42.000' as date)
select EmployeeNumber, InOutId, InOutDateTime from MyAttendance
where
InOutDateTime >= DATEADD(day, DATEDIFF(day, 0, #d), 0)
and InOutDateTime < DATEADD(day, DATEDIFF(day, 0, #d) +1, 0)
order by
EmployeeNumber, InOutId
I need to be able to sum and group by - any ideas?
try,
DECLARE #d date;
set #d = cast('2015-03-24 15:02:42.000' as date)
;with cte as(
select t.EmployeeNumber,t.InOutId as in1,
t1.InOutId out1,t.InOutDateTime from #t t
left join (select EmployeeNumber,InOutId,InOutDateTime from #t
where InOutId='OUT' and cast(InOutDateTime as date)=cast(#d as date) ) t1
on t.EmployeeNumber=t1.EmployeeNumber and
cast(t.InOutDateTime as date)=cast(t1.InOutDateTime as date)
where t.InOutId='IN' and cast(t.InOutDateTime as date)=cast(#d as date))
select count(in1) Totalin,count(out1) Totalout, sum(case when out1 is null then 1 else 0 end) TotalCurrentlyIn
,count(out1) TotalCurrentlyOut from cte
data
declare #t table (Id int,EmployeeNumber int, InOutId varchar(3), InOutDateTime datetime)
insert into #t(Id, EmployeeNumber,InOutId, InOutDateTime) values
(1 , 505 , 'IN' , '2015-03-24 08:32:42:000'),
(1 , 506 , 'IN' , '2015-03-24 08:35:47:000'),
(1 , 507 , 'IN' , '2015-03-24 08:46:12:000'),
(1 , 505 , 'OUT' , '2015-03-24 16:59:00:000'),
(1 , 506 , 'OUT' , '2015-03-24 17:05:00:000')
CheckIn = 1 and CheckOut = 2 so you need to check last entry of all the uses.
Select EmployeeId, ActionType, Max(ActionDateTime)
From AttendanceLog
Where
ActionDateTime >= DATEADD(day, DATEDIFF(day, 0, #d), 0)
and ActionDateTime < DATEADD(day, DATEDIFF(day, 0, #d) +1, 0)
Group by
EmployeeId, ActionType
Order by
EmployeeId,ActionType
If I understand the question. you need to know how much person is in the office right now:
the first query return the max date for any employee, than you join it with the actionType
select
EmployeeId , max(ActionDateTime) as MaxActionDateTime into #temptable
from table
group by EmployeeId
select count (EmployeeId), ActionType
from table inner join #temptable
on table.EmployerId == #temptable.EmployerId
and table.ActionDateTime == #temptable.MaxActionDateTime
group by ActionType
Using a windowing function you can get the last action for every employee and count those
With data As (
Select id, EmployeeNumber, InOutId
, lastAction = ROW_NUMBER() OVER (PARTITION BY EmployeeNumber
ORDER BY InOutDateTime DESC)
From table1
)
Select Count(CASE InOutId WHEN 'IN' THEN 1 END) TotalCurrentlyIn
, Count(CASE InOutId WHEN 'OUT' THEN 1 END) TotalCurrentlyOut
From data
Where lastAction = 1

Hide rows when column values are duplicate

I have a table named "letters" with two columns looking like this:
case_nr | date
--------+-----------------------
1 | 2015-06-13 12:45:04
1 | NULL
2 | 2015-06-11 12:45:09
3 | 2015-06-12 17:41:49
3 | 2015-06-13 18:42:99
case_nr 1 have printed 2 letters but only one was sent
I want to filter all cases where all letters was sent (have a date)
So in this case result should be:
2
3
You can use DISTINCT with NOT IN:
SELECT DISTINCT case_nr
FROM TableName
WHERE case_nr NOT IN
(SELECT case_nr FROM TableName WHERE [date] IS NULL )
Result:
case_nr
--------
2
3
Sample result in SQL Fiddle.
Group by the case_nr and take only those having no record with date is null
select case_nr
from your_table
group by case_nr
having sum(case when date is null then 1 else 0 end) = 0
SQLFiddle demo
As an alternative:
SELECT
case_nr
FROM (
SELECT
case_nr,
COUNT(CASE WHEN [date] IS NULL THEN 1 END) AS cnt
FROM
yourTable
GROUP BY
case_nr
) t
WHERE
cnt = 0

Right using of PIVOT-statement in SQL Server

I have an audit table, part of data is looked like:
Id ColumnName Value RowId
---------------------------------
1 EditCheckId 100 1
2 FieldData_Id 10 1
3 EditType 1 1
4 Outcome True 1
5 EditCheckId 200 2
6 FieldData_Id 20 2
7 EditType 2 2
8 Outcome False 2
9 EditCheckId 300 3
10 FieldData_Id 30 3
11 EditType 3 3
12 Outcome True 3
and I want to construct such table groupping data by RowId
EditCheck_Id FieldData_Id EditType Outcome
---------------------------------------------------
100 10 1 True
200 20 2 False
300 30 3 True
I've tried the query:
select [EditCHeck_Id], [FieldData_Id], [EditType], [Outcome]
from
(
select [ColumnName], [Value]
from Audit a
) as [SourceTable]
pivot
(
max([Value])
for [ColumnName] in ([EditCHeck_Id], [FieldData_Id], [EditType], [Outcome])
) as [PivotTable];
http://sqlfiddle.com/#!6/7af71/3
using PIVOT statement but there is only one row in answer. Where is my problem?
You'll need some value to GROUP BY to make each of the rows distinct. Typically, you would use a windowing function like row_number() to generate a unique sequenced number over your current columnname. You can alter your query to the following:
select
[EditCheckId],
[FieldData_Id],
[EditType],
[Outcome]
from
(
select [ColumnName], [Value],
rn = row_number() over(partition by ColumnName order by id)
from Audit a
) as [SourceTable]
pivot
(
max([Value])
for [ColumnName] in ([EditCheckId], [FieldData_Id], [EditType], [Outcome])
) as [PivotTable];
See SQL Fiddle with Demo.
You could also use an aggregate function with a CASE expression to get the final result:
select
max(case when ColumnName = 'EditCheckId' then value end) [EditCheckId],
max(case when ColumnName = 'FieldData_Id' then value end) [FieldData_Id],
max(case when ColumnName = 'EditType' then value end) [EditType],
max(case when ColumnName = 'Outcome' then value end) [Outcome]
from
(
select [ColumnName], [Value],
rn = row_number() over(partition by ColumnName order by id)
from Audit a
) d
group by rn;
See SQL Fiddle with Demo Both give the result:
| EDITCHECKID | FIELDDATA_ID | EDITTYPE | OUTCOME |
|-------------|--------------|----------|---------|
| 100 | 10 | 1 | True |
| 200 | 20 | 2 | False |
| 300 | 30 | 3 | True |
You need something to distinguish the output rows, like for example your RowId
pivot does the grouping for you like so:
select [RowID], [EditCheckId], [FieldData_Id], [EditType], [Outcome]
from
(
select [ColumnName], [Value], [RowId]
from Audit a
) as [SourceTable]
pivot
(
max([Value])
for [ColumnName] in ([EditCheckId], [FieldData_Id], [EditType], [Outcome])
) as [PivotTable];
edited Fiddle demo

Resources