I have a table like this:
--------------------------------------------
| Job | Class | Employee | PayType | Hours |
| 212 A John 1 20 |
| 212 A John 2 10 |
| 911 C Rebekah 1 15 |
| 911 C Rebekah 2 10 |
--------------------------------------------
I want to convert this table so i can get following output
------------------------------------
| Job | Class | Employee | OT | ST |
| 212 | A | John | 20 | 10 |
| 911 | C | Rebekah | 15 | 10 |
------------------------------------
Here I've set 1 for OT and 2 for ST
You can conditional aggregation:
select
job,
class,
employee
sum(case when paytype = 1 then hours else 0 end) ot,
sum(case when paytype = 2 then hours else 0 end) st
from mytable
group by
jobs,
class,
employee
Using PIVOT TABLE:
select
Job,
Class,
Employee,
[1] as OT,
[2] as ST from
(
select * from test2
) as t
pivot
(
sum([Hours])
for paytype in([1],[2])
) as pvt;
Related
I have to group by age and need to show results on the basis of sum of bonus, gender, is_permanent and then department. What will be the sql query for this kind of report.
Department
- Is Permanent
- gender
- monthly bonus
- Quarterly bonus
- Yearly Bonus
All the data should come from EMPLOYEE table, looks like
empid| DOB | gender| annual_bonus| monthly_bonus| quarterly_bonus| Is_Permanent| Department|
-------------------------------------------------------------------------------------------
101 | 31122002| M | 100 | 300 | 600 | No | Admin|
102 | 31122002| f | 120 | 3020 | 600 | No | Admin|
103 | 31122001| M | 1230 | 300 | 6100 | No | Sales|
104 | 31122001| f | 1100 | 3200 | 1600 | No | Sales|
105 | 31122000| M | 1100 | 300 | 6010 | No | Marketing|
106 | 31122000| f | 1200 | 300 | 600 | No | Marketing|
107 | 31121999| M | 1100 | 300 | 600 | No | Finance|
108 | 31121999| f | 1200 | 300 | 600 | No | Finance|
109 | 31121998| M | 1200 | 300 | 600 | No | Purchase|
110 | 31121998| f | 1200 | 300 | 600 | No | Purchase|
111 | 31121997| M | 1200 | 300 | 600 | No | Transport|
112 | 31121997| f | 1200 | 300 | 600 | No | Transport|
You need to use aggregate function as following:
With dataa as
(Select trunc(months_between(sysdate, dob)/12) as age,
sum(annual_bonus) as annual_bonus,
sum(monthly_bonus) as monthly_bonus,
sum(quarterly_bonus) as quarterly_bonus,
Is_permemant
From employee
Where gender = 'M'
Group by trunc(months_between(sysdate, dob)/12), Is_permemant )
Select coalesce(t1.age, t2.age) as age,
t1.annual_bonus, t1.monthly_bonus,t1.quarterly_bonus,
t2.annual_bonus, t2.monthly_bonus,t2.quarterly_bonus
From dataa t1 full outer join dataa t2
On t1.age = t2.age
And t1.Is_permemant = 'Yes' and t2.Is_permemant = 'No'
Cheers!!
You seem to want conditional aggregation. I would recommend putting the data in rows and pivoting in the application:
select department, is_permenanent, gender,
floor(months_between(dob, sysdate) / 12) as age,
sum(annual_bonus),
sum(monthly_bonus)
sum(quarterly_bonus)
from employee
group by department, is_permenanent, gender,
floor(months_between(dob, sysdate) / 12);
You can pivot the data in SQL using conditional aggregation:
select floor(months_between(dob, sysdate) / 12) as age,
sum(case when department = 'Sales' and
is_permanent = 'Yes' and
gender = 'F'
then annual_bonus else 0
end) as sales_perm_f_annual,
sum(case when department = 'Sales' and
is_permanent = 'Yes' and
gender = 'F'
then annual_monthly else 0
end) as sales_perm_f_monthly,
sum(case when department = 'Sales' and
is_permanent = 'Yes' and
gender = 'F'
then quarterly_bonus else 0
end) as sales_perm_f_quarterly_bonus,
. . .
from employee
group by floor(months_between(dob, sysdate) / 12);
Given tables CollegeMajors
| Id | Major |
|----|-------------|
| 1 | Accounting |
| 2 | Math |
| 3 | Engineering |
and EnrolledStudents
| Id | CollegeMajorId | Name | HasGraduated |
|----|----------------|-----------------|--------------|
| 1 | 1 | Grace Smith | 1 |
| 2 | 1 | Tony Fabio | 0 |
| 3 | 1 | Michael Ross | 1 |
| 4 | 3 | Fletcher Thomas | 1 |
| 5 | 2 | Dwayne Johnson | 0 |
I want to do a query like
Select
CollegeMajors.Major,
Count(select number of students who have graduated) AS TotalGraduated,
Count(select number of students who have not graduated) AS TotalNotGraduated
From
CollegeMajors
Inner Join
EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
and I'm expecting these kind of results
| Major | TotalGraduated | TotalNotGraduated |
|-------------|----------------|-------------------|
| Accounting | 2 | 1 |
| Math | 0 | 1 |
| Engineering | 1 | 0 |
So the question is, what kind of query goes inside the COUNT to achieve the above?
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can use the CASE statement inside your COUNT to achieve the desired result.Please try the below updated query.
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can try this for graduated count:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 1
And change 1 to zero for not graduated ones:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 0
I have inherited two tables, where the data for one is in hours, and the data for the other is in days.
One table has planned resource use, the other holds actual hours spent
Internal_Resources
| PeopleName | NoOfDays | TaskNo |
|------------|----------|--------|
| Fred | 1 | 100 |
| Bob | 3 | 100 |
| Mary | 2 | 201 |
| Albert | 10 | 100 |
TimeSheetEntries
| UserName | PaidHours | TaskNumber |
|----------|-----------|------------|
| Fred | 7 | 100 |
| Fred | 14 | 100 |
| Fred | 7 | 100 |
| Bob | 7 | 100 |
| Bob | 21 | 100 |
| Mary | 7 | 201 |
| Mary | 14 | 100 |
What I need is a comparison of time planned vs time spent.
| name | PlannedDays | ActualDays |
|--------|-------------|------------|
| Albert | 10 | NULL |
| Bob | 3 | 4.00 |
| Fred | 1 | 4.00 |
| Mary | NULL | 2.00 |
I've cobbled together something that almost does the trick:
SELECT
UserName,
( SELECT
NoOfDays FROM Internal_Resources as r
WHERE r.PeopleName = e.UserName AND r.TaskNumber = ? ) AS PlannedDays,
SUM ( Round( PaidHours / 7 , 2 ) ) as ActualDays
FROM TimeSheetEntries e WHERE TaskNo = ?
GROUP BY UserName
Which for task 100 gives me back something like:
| UserName | PlannedDays | ActualDays |
|----------|-------------|------------|
| Bob | 3 | 4 |
| Fred | 1 | 4 |
| Mary | 0 | 2 |
but lazy Albert doesn't feature! I'd like:
| UserName | PlannedDays | ActualDays |
|----------|-------------|------------|
| Albert | 10 | 0 |
| Bob | 3 | 4 |
| Fred | 1 | 4 |
| Mary | 0 | 2 |
I've tried using variations on
SELECT * FROM ( SELECT ... ) AS plan
INNER JOIN ( [second-query] ) AS actual
ON plan.PeopleName = actual.UserName
What should I be doing? I suspect I need to squeeze a cross-join in there somewhere, but I'm getting nowhere...
( This going to be run inside a FileMaker ExecuteSQL() call, so I need pretty vanilla SQL... And no, I don't have control over the column or table names :-( )
EDIT:
To be clear, I need the result set to include both users who had planned days and haven't worked on a task, as well as those who have worked on a task without having planned days...
EDIT 2:
I can kind of get what I want manually, but can't see how to combine the statements below:
SELECT people.name, PlannedDays, ActualDays FROM
( SELECT PeopleName as name FROM Internal_Resources WHERE TaskNo = 100
UNION
SELECT DISTINCT UserName as name FROM TimeSheetEntries WHERE TaskNumber = 100
ORDER BY Name) AS people
gets me:
+--------+
| name |
+--------+
| Albert |
| Bob |
| Fred |
| Mary |
+--------+
and:
( SELECT PeopleName AS name, NoOfDays AS PlannedDays
FROM Internal_Resources WHERE TaskNo = 100 ) AS actual
gets me:
+--------+-------------+
| name | PlannedDays |
+--------+-------------+
| Fred | 1 |
| Bob | 3 |
| Albert | 10 |
+--------+-------------+
and finally,
( SELECT UserName AS name, SUM( Round( PaidHours / 7, 2 ) ) AS ActualDays
FROM TimeSheetEntries
WHERE TaskNumber = 100 GROUP BY UserName ) AS planned
gets me:
+------+------------+
| name | ActualDays |
+------+------------+
| Bob | 4.00 |
| Fred | 4.00 |
| Mary | 2.00 |
+------+------------+
Now all (All! ha!) I want is to combine these into this:
+--------+-------------+------------+
| name | PlannedDays | ActualDays |
+--------+-------------+------------+
| Albert | 10 | NULL |
| Bob | 3 | 4.00 |
| Fred | 1 | 4.00 |
| Mary | NULL | 2.00 |
+--------+-------------+------------+
EDIT 3:
I've tried combining it with something along the lines of:
SELECT people.name, PlannedDays, ActualDays
FROM ( SELECT PeopleName as name FROM Internal_Resources WHERE TaskNo = 100
UNION
SELECT DISTINCT UserName as name FROM TimeSheetEntries WHERE TaskNumber = 100
ORDER BY Name) AS people
LEFT JOIN ( SELECT PeopleName AS name, NoOfDays AS PlannedDays FROM Internal_Resources WHERE TaskNo = 100 ) AS actual,
ON people.name = actual.name
LEFT JOIN ( SELECT UserName AS name, SUM( Round( PaidHours / 7, 2 ) ) AS ActualDays FROM TimeSheetEntries WHERE TaskNumber = 100 GROUP BY UserName ) AS planned
ON people.name = planned.name;
but the syntax is clearly wonky.
Okay - this works:
SELECT people.name, COALESCE(PlannedDays, 0) as planned, COALESCE(ActualDays, 0) as actual
FROM ( SELECT PeopleName as name FROM Internal_Resources WHERE TaskNo = 100
UNION
SELECT DISTINCT UserName as name FROM TimeSheetEntries WHERE TaskNumber = 100
ORDER BY Name) AS people
LEFT JOIN ( SELECT PeopleName AS name, NoOfDays AS PlannedDays FROM Internal_Resources WHERE TaskNo = 100 ) AS ir
ON people.name = ir.name
LEFT JOIN ( SELECT UserName AS name, SUM( Round( PaidHours / 7, 2 ) ) AS ActualDays FROM TimeSheetEntries WHERE TaskNumber = 100 GROUP BY UserName ) AS ts
ON people.name = ts.name;
Giving:
+--------+---------+--------+
| name | planned | actual |
+--------+---------+--------+
| Albert | 10 | 0.00 |
| Bob | 3 | 4.00 |
| Fred | 1 | 4.00 |
| Mary | 0 | 2.00 |
+--------+---------+--------+
I thought there must be an easier way, and this looks simpler:
SELECT name, SUM(x) AS planned, SUM(y) AS actual
FROM (
SELECT PeopleName AS name, NoOfDays AS x, 0 AS y
FROM Internal_Resources WHERE TaskNo = 100
UNION
SELECT UserName AS name, 0 AS x, SUM( PaidHours / 7 ) AS y
FROM TimeSheetEntries WHERE TaskNumber = 100 GROUP BY UserName) AS source
GROUP BY name;
But frustratingly - both work in MySQL and both FAIL in FileMaker's cut-down SQL version - SELECTing from a derived table doesn't appear to be supported.
Finally - the trick to getting it to work in FileMaker SQL - subqueries are supported for IN and NOT IN... so a union of three queries - people who have planned days and have done some work, people who have done unplanned work, and people who haven't done planned work:
SELECT PeopleName as name, NoOfDays as planned, Sum( PaidHours / 7 ) as actual
FROM Internal_Resources
JOIN TimeSheetEntries
ON PeopleName = UserName
WHERE TaskNumber = 100 AND TaskNo = 100 GROUP BY PeopleName
UNION
SELECT UserName as name, 0 as planned, Sum( PaidHours / 7 ) as actual
FROM TimeSheetEntries
WHERE TaskNumber = 100
AND UserName NOT IN (
SELECT PeopleName FROM Internal_Resources WHERE TaskNo = 100
)
UNION
SELECT PeopleName as name, NoOfDays as planned, 0 as actual
FROM Internal_Resources WHERE TaskNo = 100
AND PeopleName NOT IN (
SELECT PeopleName as name
FROM Internal_Resources JOIN TimeSheetEntries
ON PeopleName = UserName
WHERE TaskNumber = 100 AND TaskNo = 100
GROUP BY PeopleName
)
ORDER BY name;
Hope this helps someone.
Doesn't filemaker support LEFT OUTER JOINs?
SELECT
PeopleName,
NoOfDays AS PlannedDays
ROUND(SUM(PaidHours) / 7, 2) AS ActualDays
FROM
Internal_Resources AS planned
-- left join should not discard Albert's record from Internal_Resources
LEFT JOIN TimeSheetEntries AS actual
ON planned.PeopleName = actual.UserName
AND planned.TaskNo = actual.TaskNumber
WHERE
planned.TaskNo = ?
GROUP BY PeopleName, NoOfDays
Invert the logic to read from Internal_resources in the outer query:
SELECT ir.UserName, NoOfDays as PlannedDays,
(SELECT SUM ( Round( PaidHours / 7 , 2 ))
FROM TimeSheetEntries e
WHERE e.TaskNo = ? AND ir.PeopleName = e.UserName
) as ActualDays
FROM Internal_Resources ir
WHERE ir.TaskNumber = ?
GROUP BY ir.UserName, NoOfDays;
I apologize if this is a duplicate question but I could not find my answer.
I am trying to take data that is horizontal, and get a count of how many times a specific number appears.
Example table
+-------+-------+-------+-------+
| Empid | KPI_A | KPI_B | KPI_C |
+-------+-------+-------+-------+
| 232 | 1 | 3 | 3 |
| 112 | 2 | 3 | 2 |
| 143 | 3 | 1 | 1 |
+-------+-------+-------+-------+
I need to see the following:
+-------+--------------+--------------+--------------+
| EmpID | (1's Scored) | (2's Scored) | (3's Scored) |
+-------+--------------+--------------+--------------+
| 232 | 1 | 0 | 2 |
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
+-------+--------------+--------------+--------------+
I hope that makes sense. Any help would be appreciated.
Since you are counting data across multiple columns, it might be easier to unpivot your KPI columns first, then count the scores.
You could use either the UNPIVOT function or CROSS APPLY to convert your KPI columns into multiple rows. The syntax would be similar to:
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
See SQL Fiddle with Demo. This gets your multiple columns into multiple rows, which is then easier to work with:
| EMPID | KPI | VAL |
|-------|-----|-----|
| 232 | A | 1 |
| 232 | B | 3 |
| 232 | C | 3 |
| 112 | A | 2 |
Now you can easily count the number of 1's, 2's, and 3's that you have using an aggregate function with a CASE expression:
select EmpId,
sum(case when val = 1 then 1 else 0 end) Score_1,
sum(case when val = 2 then 1 else 0 end) Score_2,
sum(case when val = 3 then 1 else 0 end) Score_3
from
(
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
) d
group by EmpId;
See SQL Fiddle with Demo. This gives a final result of:
| EMPID | SCORE_1 | SCORE_2 | SCORE_3 |
|-------|---------|---------|---------|
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
| 232 | 1 | 0 | 2 |
I'm having a problem with Crosstab query in SQL Server.
Suppose that I have data as below:
| ScoreID | StudentID | Name | Sex | SubjectName | Score |
------------------------------------------------------------------
| 1 | 1 | Student A | Male | C | 100 |
| 2 | 1 | Student A | Male | C++ | 40 |
| 3 | 1 | Student A | Male | English | 60 |
| 4 | 1 | Student A | Male | Database | 15 |
| 5 | 1 | Student A | Male | Math | 50 |
| 6 | 2 | Student B | Male | C | 77 |
| 7 | 2 | Student B | Male | C++ | 12 |
| 8 | 2 | Student B | Male | English | 56 |
| 9 | 2 | Student B | Male | Database | 34 |
| 10 | 2 | Student B | Male | Math | 76 |
| 11 | 3 | Student C | Female | C | 24 |
| 12 | 3 | Student C | Female | C++ | 10 |
| 13 | 3 | Student C | Female | English | 15 |
| 14 | 3 | Student C | Female | Database | 40 |
| 15 | 3 | Student C | Female | Math | 21 |
| 16 | 4 | Student D | Female | C | 17 |
| 17 | 4 | Student D | Female | C++ | 34 |
| 18 | 4 | Student D | Female | English | 24 |
| 19 | 4 | Student D | Female | Database | 56 |
| 20 | 4 | Student D | Female | Math | 43 |
I want to make query which show the result as below:
| StuID| Name | Sex | C | C++ | Eng | DB | Math | Total | Average |
| 1 | Student A | Male | 100| 40 | 60 | 15 | 50 | 265 | 54 |
| 2 | Student B | Male | 77 | 12 | 56 | 34 | 76 | 255 | 51 |
| 3 | Student C | Female | 24 | 10 | 15 | 40 | 21 | 110 | 22 |
| 4 | Student D | Female | 17 | 34 | 24 | 56 | 43 | 174 | 34.8 |
How could I query to show output like this?
Note:
Subject Name:
C
C++
English
Database
Math
will be changed depend on which subject student learn.
Please go to http://sqlfiddle.com/#!6/2ba07/1 to test this query.
There are two ways to perform a PIVOT static where you hard-code the values and dynamic where the columns are determined when you execute.
Even though you will want a dynamic version, sometimes it is easier to start with a static PIVOT and then work towards a dynamic one.
Static Version:
SELECT studentid, name, sex,[C], [C++], [English], [Database], [Math], total, average
from
(
select s1.studentid, name, sex, subjectname, score, total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
) x
pivot
(
min(score)
for subjectname in ([C], [C++], [English], [Database], [Math])
) p
See SQL Fiddle with demo
Now, if you do not know the values that will be transformed then you can use Dynamic SQL for this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(SubjectName)
from Score
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT studentid, name, sex,' + #cols + ', total, average
from
(
select s1.studentid, name, sex, subjectname, score, total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
) x
pivot
(
min(score)
for subjectname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Both versions will yield the same results.
Just to round out the answer, if you do not have a PIVOT function, then you can get this result using CASE and an aggregate function:
select s1.studentid, name, sex,
min(case when subjectname = 'C' then score end) C,
min(case when subjectname = 'C++' then score end) [C++],
min(case when subjectname = 'English' then score end) English,
min(case when subjectname = 'Database' then score end) [Database],
min(case when subjectname = 'Math' then score end) Math,
total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
group by s1.studentid, name, sex, total, average
See SQL Fiddle with Demo
You need to use SQL PIVOT in this case. Plese refer the following link:
Pivot on Unknown Number of Columns
Pivot two or more columns in SQL Server
Pivots with Dynamic Columns in SQL Server
This requires building a SQL query string at runtime. Column names, counts and data-types in SQL Server are always static (the most important reason for that is that the optimizer must know the query data flow at optimization time).
So I recommend that you build a PIVOT-query at runtime and run it through sp_executesql. Note that you have to hardcode the pivot-column values. Be careful to escape them properly. You cannot use parameters for them.
Alternatively you can build one such query per column-count and use parameters just for the pivot values. You would have to assign some dummy column names like Pivot0, Pivot1, .... Still you need one query template per count of columns. Except if you are willing to hard-code the maximum number of pivot-columns into the query (say 20). In this case you actually could use static SQL.