I'm trying to get two rows count and assign it to a single column.
select distinct
userreferenceid, 'Level(' + cast(level as varchar(255))+')' GroupLevel,
referenceid
from
(select
t0.userreferenceid,
case level.level
when 1 then t1.userreferenceid
when 2 then t2.userreferenceid
when 3 then t3.userreferenceid
when 4 then t4.userreferenceid
end referenceid,
level.level
from
member_details t0
left outer join
member_details t1 on t1.referenceid = t0.userreferenceid
left outer join
member_details t2 on t2.referenceid = t1.userreferenceid
left outer join
member_details t3 on t3.referenceid = t2.userreferenceid
left outer join
member_details t4 on t4.referenceid = t3.userreferenceid
cross join
(select 1 level
union all
select 2
union all
select 3
union all
select 4) level) t
where
t.referenceid is not null
Where my refernceid is the one which user uses to register (referalid), userreferenceid is the own id of the user (own referenceid).
For the above query my result set is displaying like this
user
ref
ere
nceid GroupLevel Referenceid
-------------------------------------------
REF101 Level(1) REF143
REF101 Level(2) REF113
REF101 Level(3) REF119
REF101 Level(3) REF227
REF101 Level(4) REF245
REF101 Level(4) REF251
REF107 Level(1) REF221
REF107 Level(1) REF257
REF107 Level(2) REF119
REF107 Level(2) REF227
REF107 Level(3) REF125
REF107 Level(3) REF161
REF107 Level(4) REF191
REF113 Level(3) REF191
REF119 Level(1) REF125
I need the result set exactly look like this.
I need to assign each and every refid and count of all users in level 1 ,2,3,4,
Userreferenceid | Level1users|Level2Users|level3Users|Level4Users
-----------------+------------+-----------+-----------+------------
REF101 1 1 2 2
REF107 2 2 2 1
REF113 0 0 1 0
REF119 1 0 0 0
My table structure is
userreferenceid (varchar) || referenceid(varchar)
Please help me out of this.. Thanks in advance
try this
Aggregate summation
select Userreferenceid,
sum(case when GroupLevel='Level(1)' then 1 else 0 end) Level1users,
sum(case when GroupLevel='Level(2)' then 1 else 0 end) Level2users,
sum(case when GroupLevel='Level(3)' then 1 else 0 end) Level3users,
sum(case when GroupLevel='Level(4)' then 1 else 0 end) Level4users
from mytable group by Userreferenceid
You can do it by executing dynamic sql query also. If lot of GroupLevel values are there, then writing query would be hectic.
Query
declare #sql as varchar(max);
select #sql = stuff((
select distinct
', sum(case when [GroupLevel] = ' + char(39) + [GroupLevel] + char(39)
+ ' then 1 else 0 end) as [' + replace(replace([GroupLevel], '(', ''),
')', '') + 'Users]'
from [your_table_name]
for xml path('')
)
, 1, 2, ''
);
select #sql = 'select [userreferenceid], ' + #sql
+ 'from [your_table_name] '
+ 'group by [userreferenceid];';
exec(#sql);
Related
I have the following table:
GR WORD NO.
1 A 4
2 B 5
3 C 6
1 G 5
2 H 5
3 I 5
I would like to get the following table:
GR 4 5 6
1 1 1 0
2 0 2 0
3 0 1 1
For each GR column value I count the NO. values.
Here's a dynamic solution:
--Sample data
--CREATE TABLE tbl (GR int, WORD char(1), [NO] int)
--INSERT INTO tbl values
--(1,'A',4),
--(2,'B',5),
--(3,'C',6),
--(1,'G',5),
--(2,'H',5),
--(3,'I',5)
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = '
SELECT *
FROM tbl
PIVOT(
COUNT(WORD) FOR [NO] IN (' +
(SELECT STUFF(
(
SELECT DISTINCT ',' + QUOTENAME(CAST([NO] AS VARCHAR(10)))
FROM tbl
FOR XML PATH('')
)
, 1, 1, ''))
+ ')
) p
'
EXEC sp_executesql #sql
This is a conditional aggregation
select
GR
,[4] = count(case when NO. = 4 then WORD end)
,[5] = count(case when NO. = 5 then WORD end)
,[6] = count(case when NO. = 6 then WORD end)
from YourTable
group by GR
Or a pivot
select *
from YourTable
pivot(
count(WORD) for NO. in ([4],[5],[6])
) p
I have two tables and I would like to get MAX(date) from one-to-many table. If there is no value, it should be NULL. Only way I know how to do it by making sub queries but if there is ~20 different types then 20 sub queries does not sound efficient enough. Is there any better way to do it?
Table A:
UserId | Name
1 | John
2 | Jane
Table B:
UserId | Type | Date
1 | A | 2015-01-01
1 | A | 2015-12-31
1 | B | 2015-01-01
1 | B | 2015-12-31
2 | B | 2015-06-06
1 | C | 2015-01-01
2 | C | 2015-09-09
Result:
UserId | Type A date | Type B date | Type C date
1 | 2015-12-31 | 2015-12-31 | NULL
2 | NULL | 2015-06-06 | 2015-09-09
Current solution:
SELECT UserId,
(SELECT MAX(Date) FROM B WHERE Type = 'A' AND B.UserId= A.UserId),
(SELECT MAX(Date) FROM B WHERE Type = 'B' AND B.UserId= A.UserId),
(SELECT MAX(Date) FROM B WHERE Type = 'C' AND B.UserId= A.UserId
AND Date > (SELECT MAX(Date) FROM B WHERE Type = 'B' AND B.UserId = A.UserId))
FROM A
Thank you for all quick answers! They work perfectly. I modified my question little bit since I noticed that I need to add some conditions on some types. For example. Type C should be only presented if it's bigger than type B.
To get the expected result you don't even need to join, simply do a group by and use case expressions do to conditional aggregation:
select userid,
max(case when type = 'A' then date end) Adate,
max(case when type = 'B' then date end) Bdate
from tableB
group by userid
If you also want the username, you can join tableA with the above query:
select a.name, b.Adate, b.Bdate
from tableA a
join (select userid,
max(case when type = 'A' then date end) Adate,
max(case when type = 'B' then date end) Bdate
from tableB
group by userid) b
on a.userid = b.userid
However, if the number of types is unknown, I'd group by the column type too. I.e return types in different rows.
select a.name, b.type, b.date
from tableA a
left join (select userid, type, max(date) date,
from tableB
group by userid, type) b
on a.userid = b.userid
You can do a conditional aggregation instead of the subqueries in the SELECT:
SELECT
a.UserId,
MAX(CASE WHEN b.Type = 'A' THEN b.date END) AS ADate,
MAX(CASE WHEN b.Type = 'B' THEN b.date END) AS BDate
FROM TableA a
LEFT JOIN TableB b
ON b.UserId = a.UserId
GROUP BY a.UserId;
However, the above will only work if you know the values of Type. If you don't, then you need to do it using a dynamic query.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
a.UserId' + CHAR(10) +
(SELECT DISTINCT
' , MAX(CASE WHEN b.Type = ''' + Type + ''' THEN b.Date END) AS ' + QUOTENAME(Type + 'Date') + CHAR(10)
FROM TableB
FOR XML PATH('')
) +
'FROM TableA a
LEFT JOIN TableB b
ON b.UserId = a.UserId
GROUP BY a.UserId;';
EXEC (#sql);
ONLINE DEMO
Reference: Cross Tabs and Pivots by Jeff Moden
Use Dynamic Pivot, no matter how many type you have, you dont need to change the query.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName = ISNULL(#ColumnName + ',','') + QUOTENAME([TYPE])
FROM (SELECT DISTINCT [TYPE] as [TYPE] FROM [master].[dbo].[YourTable]) AS TypeTable
SET #DynamicPivotQuery =
'SELECT USERID,' + #ColumnName+'
FROM [master].[dbo].[YourTable]
PIVOT(MAX([Date])
FOR [TYPE] IN (' + #ColumnName+')) AS PVTTable'
EXEC sp_executesql #DynamicPivotQuery
Result is like below :
USERID A B
1 2015-12-31 2015-12-31
2 NULL 2015-06-06
Edit : For type C,there are some condition:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #ColumnNameForDisplay AS NVARCHAR(MAX)
DECLARE #otherCondition AS NVARCHAR(MAX)
SELECT #ColumnName = ISNULL(#ColumnName + ',','') + QUOTENAME([TYPE])
FROM (SELECT DISTINCT [TYPE] as [TYPE] FROM [master].[dbo].[YourTable]) AS TypeTable
SELECT #ColumnNameForDisplay = ISNULL(#ColumnNameForDisplay + ',','') + [TYPE2]
FROM (SELECT DISTINCT [TYPE]+' as [Type '+[TYPE]+' date]' as [TYPE2] FROM [master].[dbo].[YourTable] where type <> 'c') AS TypeTable
SELECT #otherCondition = ' ,Case when c <= b then null else c END as [Type C date] '
SET #DynamicPivotQuery = '
SELECT USERID,'+#ColumnNameForDisplay+#otherCondition+' from(
SELECT USERID,' + #ColumnName+'
FROM [master].[dbo].[YourTable]
PIVOT(MAX([Date])
FOR [TYPE] IN (' + #ColumnName+')) AS PVTTable) as t'
EXEC sp_executesql #DynamicPivotQuery
Result is like below :
USERID Type A date Type B date Type C date
1 2015-12-31 2015-12-31 NULL
2 NULL 2015-06-06 2015-09-09
My problem is similar in a way to this one, yet different enough in my understanding.
I have three tables:
Units ([UnitID] int, [UnitParentID] int)
Students ([StudentID] int, [UnitID] int)
Events ([EventID] int, [EventTypeID] int, [StudentID] int)
Students belong to units, units are stacked in a hierarchy (tree form - one parent per child), and each student can have events of different types.
I need to sum up the number of events of each type per user, then aggregate for all users in a unit, then aggregate through hierarchy until I reach the mother of all units.
The result should be something like this:
My tools are SQL Server 2008 and Report Builder 3.
I put up a SQL fiddle with sample data for fun.
Use this query:
;WITH CTE(Id, ParentId, cLevel, Title, ord) AS (
SELECT
u.UnitID, u.UnitParentID, 1,
CAST('Unit ' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)) AS varchar(max)),
CAST(RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3) AS varchar(max))
FROM
dbo.Units u
WHERE
u.UnitParentID IS NULL
UNION ALL
SELECT
u.UnitID, u.UnitParentID, c.cLevel + 1,
c.Title + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY c.cLevel ORDER BY c.Id) AS varchar(3)),
c.ord + RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3)
FROM
dbo.Units u
JOIN
CTE c ON c.Id = u.UnitParentID
WHERE
u.UnitParentID IS NOT NULL
), Units AS (
SELECT
u.Id, u.ParentId, u.cLevel, u.Title, u.ord,
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END) AS EventA,
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END) AS EventB,
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END) AS EventC,
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END) AS EventD
FROM
CTE u
LEFT JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
u.Id, u.ParentId, u.cLevel, u.Title, u.ord
), addStudents AS (
SELECT *
FROM Units
UNION ALL
SELECT
s.StudentId, u.Id, u.cLevel + 1,
'Student ' + CAST(s.StudentId AS varchar(3)),
u.ord + RIGHT('000' + CAST(s.StudentId AS varchar(3)), 0),
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END)
FROM Units u
JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
s.StudentID, u.ID, u.cLevel, u.ord
)
SELECT --TOP(10)
REPLICATE(' ', cLevel) + Title As Title,
EventA, EventB, EventC, EventD
FROM
addStudents
ORDER BY
ord
For this:
Title | EventA | EventB | EventC | EventD
-----------------+--------+---------+--------+--------
Unit 1 | 0 | 1 | 0 | 0
Student 6 | 0 | 1 | 0 | 0
Unit 1.1 | 0 | 0 | 0 | 1
Student 21 | 0 | 0 | 0 | 1
Student 33 | 0 | 0 | 0 | 0
Unit 1.1.1 | 0 | 0 | 0 | 0
Student 23 | 0 | 0 | 0 | 0
Unit 1.1.1.1 | 3 | 2 | 3 | 0
Student 10 | 0 | 0 | 0 | 0
Student 17 | 1 | 0 | 0 | 0
...
SQL Fiddle Demo
Do you need also the hierarchy to be sorted / visualized? At least this will calculate the sums, but the order of the data is pretty random :)
;with CTE as (
select S.StudentId as UnitID, S.UnitId as UnitParentID,
S.StudentID, 'Student' as Type
from Students S
union all
select U.UnitId, U.UnitParentId,
CTE.StudentId as StudentID, 'Unit ' as Type
from
Units U
join CTE
on U.UnitId = CTE.UnitParentId
)
select C.Type + ' ' + convert(varchar, C.UnitId),
sum(case when EventTypeId = 1 then 1 else 0 end) as E1,
sum(case when EventTypeId = 2 then 1 else 0 end) as E2,
sum(case when EventTypeId = 3 then 1 else 0 end) as E3,
sum(case when EventTypeId = 4 then 1 else 0 end) as E4
from
CTE C
left outer join events E on C.StudentId = E.StudentId
group by
C.Type, C.UnitId
SQL Fiddle
If you need also the hierarchy to be in order, you'll probably have add few extra CTEs to get the numbering from top down with something like #shA.t did. This gathers the hierarchy separately for each student, so it's not really possible to add the level numbers in a simple way.
table [Status] has the following data:
ID Status
1 PaymentPending
2 Pending
3 Paid
4 Cancelled
5 Error
====================================
Data Table has the following structure:
ID WeekNumber StatusID
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 2
7 2 3
Looking for a Pivot
Week # PaymentPending Pending Paid Cancelled
Week 1 1 1 1 0
Week 2 1 2 1 0
SELECT 'Week '+CAST(coun.WeekNumber AS VARCHAR(10)) [Week #],[PaymentPending],[Pending],[Paid],[Cancelled],[Error] FROM
(SELECT [WeekNumber],[Status] FROM dbo.WeekDetails
INNER JOIN [dbo].[Status] AS s
ON [dbo].[WeekDetails].[StatusID] = [s].[ID]) AS wee
PIVOT (COUNT(wee.[Status]) FOR wee.[Status]
IN ([PaymentPending],[Pending],[Paid],[Cancelled],[Error])) AS Coun
A pivot might look like this:
SELECT * FROM
(SELECT
'Week ' + CAST(D.WeekNumber AS varchar(2)) [Week #],
S.Status
FROM DataTbl D
INNER JOIN Status S ON D.StatusID = S.ID
) Derived
PIVOT
(
COUNT(Status) FOR Status IN
([PaymentPending], [Pending], [Paid], [Cancelled]) -- add [Error] if needed
) Pvt
If you expect the number of items in theStatustable to change you might want to consider using a dynamic pivot to generate the column headings. Something like this:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = ISNULL(#cols + ',','') + QUOTENAME(Status)
FROM (SELECT ID, Status FROM Status) AS Statuses ORDER BY ID
SET #sql =
N'SELECT * FROM
(SELECT ''Week '' + CAST(D.WeekNumber AS varchar(2)) [Week #], S.Status
FROM Datatbl D
INNER JOIN Status S ON D.StatusID = S.ID) Q
PIVOT (
COUNT(Status)
FOR Status IN (' + #cols + ')
) AS Pvt'
EXEC sp_executesql #sql;
Sample SQL Fiddle
You can use CASE based aggregation with GROUP BY
SELECT 'Week ' + cast(WeekNumber as varchar(10)) as 'Week#',
SUM ( CASE WHEN StatusId =1 THEN 1 else 0 end) as 'PaymentPending',
SUM ( CASE WHEN StatusId =2 THEN 1 else 0 end) as 'Pending',
SUM ( CASE WHEN StatusId =3 THEN 1 else 0 end) as 'Paid',
SUM ( CASE WHEN StatusId =4 THEN 1 else 0 end) as 'Cancelled'
FROM DataTbl D
GROUP BY 'Week ' + cast(WeekNumber as varchar(10))
I need to alter view that show user count(ScheduleID) by period on same row. Now the Period table content can grow and contain more than 3 periods.
The actual SQL is:
SELECT r.Code,
SUM(CASE WHEN s.PeriodID=1 THEN 1 ELSE 0 END) AS PeriodID1,
SUM(CASE WHEN s.PeriodID=2 THEN 1 ELSE 0 END) AS PeriodID2,
SUM(CASE WHEN s.PeriodID=3 THEN 1 ELSE 0 END) AS PeriodID3,
SUM(CASE WHEN s.PeriodID IN (1,2,3) THEN 1 ELSE 0 END) AS Total
FROM Schedules s
JOIN Periods p ON p.PeriodID = s.PeriodID
JOIN Resources r ON r.ResourceID = s.ResourceID
GROUP BY r.Code;
Example data:
Table Schedules
ScheduleID(int) ResourceID(int) ResourceCode(varchar 4) PeriodID(int)
1 1 AA 1
2 1 AA 3
3 1 AA 3
4 2 BB 1
5 3 CC 1
6 1 AA 1
7 3 CC 2
8 3 CC 3
9 2 BB 1
10 2 BB 2
11 2 BB 3
12 1 AA 3
Table Periods
PeriodID(int) Code (varchar 4)
1 P1
2 P2
3 P3
4 P4
5 P5
6 P6
7 P7
8 P8
The result I need is:
ResourceCode PeriodID1 PeriodID2 PeriodID3 ... PeriodID8 TOTAL
AA 2 0 3 0 5
BB 2 1 1 0 4
CC 1 1 1 0 3
The Periods table content is now dynamic.
The database version is an Microsoft SQL 2008
I like to know if is possible to do that without create stored procedure...and doing this in one query like this:
SELECT *
FROM (
SELECT R.Code, P.PeriodID, COUNT(S.ScheduleID) AS RPCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID = P.PeriodID
JOIN Resources R ON S.ResourceID = R.ResourceID
WHERE S.ResourceID is not null
GROUP BY R.Code, P.PeriodID
) as data
PIVOT
(
SUM(RPCount)
--FOR PeriodID IN ([1],[2],[3])
FOR PeriodID IN (SELECT PeriodID From Periods)
)AS pvt
ORDER BY Code
Since you are using SQL Server then you can implement the PIVOT function and if you have an unknown number of period values, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('PeriodId'+cast(periodid as varchar(10)))
from Periods
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT resourcecode, ' + #cols + ' , Total
from
(
select s.resourcecode,
''PeriodId''+cast(p.periodid as varchar(10)) period,
count(*) over(partition by s.resourcecode) Total
from periods p
left join schedules s
on p.periodid = s.periodid
) x
pivot
(
count(period)
for period in (' + #cols + ')
) p
where resourcecode is not null
order by resourcecode'
execute(#query)
See SQL Fiddle with Demo. This gives a result:
| RESOURCECODE | PERIODID1 | PERIODID2 | PERIODID3 | PERIODID4 | PERIODID5 | PERIODID6 | PERIODID7 | PERIODID8 | TOTAL |
------------------------------------------------------------------------------------------------------------------------
| AA | 2 | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 5 |
| BB | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 4 |
| CC | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 3 |
Based on your previous question that was tagged with MySQL, I am assuming you are using MySQL as the database. If so, then you do not have a PIVOT function so you will have to use an aggregate function with a CASE expression to transform the rows of data into columns.
If your column values are known, then you can hard-code the query:
select resourcecode,
sum(case when period = 'PeriodId1' then 1 else 0 end) PeriodId1,
sum(case when period = 'PeriodId2' then 1 else 0 end) PeriodId2,
sum(case when period = 'PeriodId3' then 1 else 0 end) PeriodId3,
sum(case when period = 'PeriodId4' then 1 else 0 end) PeriodId4,
sum(case when period = 'PeriodId5' then 1 else 0 end) PeriodId5,
sum(case when period = 'PeriodId6' then 1 else 0 end) PeriodId6,
sum(case when period = 'PeriodId7' then 1 else 0 end) PeriodId7,
sum(case when period = 'PeriodId8' then 1 else 0 end) PeriodId8,
count(*) Total
from
(
select concat('PeriodId', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode;
See SQL Fiddle with Demo. But if the values will be unknown or dynamic then you will need to use a prepared statement to generate a sql string to execute:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN period = ''',
concat('PeriodId', periodid),
''' THEN 1 else 0 END) AS `',
concat('PeriodId', periodid), '`'
)
) INTO #sql
FROM periods;
SET #sql
= CONCAT('SELECT resourcecode, ', #sql, ' , count(*) Total
from
(
select concat(''PeriodId'', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Use PIVOT
try this
SELECT *
FROM (
SELECT
S.ResourceCode ,
P.PeriodID AS period,
COUNT(*) AS PCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID =P.PeriodID
GROUP BY S.ResourceCode ,P.PeriodID
) as s
PIVOT
(
PCount,
FOR [period] IN (SELECT DISTINCT PeriodID From Periods)
)AS pivot
Please try below code for MS Sql server:
DECLARE #column VARCHAR(MAX), #SumQuery VARCHAR(MAX)
SELECT
#column = COALESCE(#column + '], [', '')+ CAST(PeriodID as nvarchar(10)),
#SumQuery = COALESCE(#SumQuery + ']+[', '')+ CAST(PeriodID as nvarchar(10))
FROM
Periods
GROUP BY PeriodID
EXEC ('select *, ['+#SumQuery+'] as [Total] From
(
select * From Schedules
)up
pivot (count(ScheduleID) for PeriodID in (['+#column+'])) as pvt')