SQL - Counting one attribute, grouping by another - sql

I have a table staff
staff
pt | ward
P | 1
P | 1
T | 1
P | 2
T | 2
I want to produce a table that counts how many P's and T's there is for each ward like this:
staff
ward | P | T
1 | 2 | 1
2 | 1 | 1
I have tried this
WITH cte(ward, P, T) AS(
SELECT ward,
(SELECT COUNT(PT) FROM staff WHERE PT = 'P' ),
(SELECT COUNT(PT) FROM staff WHERE PT = 'T' ) FROM staff GROUP BY ward)
SELECT * FROM cte
but then I get this table
staff
ward | P | T
1 | 3 | 2
2 | 2 | 2
Any help would be appreciated

Case statements will work here:
SELECT
ward,
SUM(CASE WHEN pt = P THEN 1 ELSE 0 END) AS P,
SUM(CASE WHEN pt = T THEN 1 ELSE 0 END) AS T
FROM
table
GROUP BY
ward

Use conditional aggregation:
select ward, sum( (pt = 'P')::int ) as p, sum ( (pt = 'T')::int ) as t
from t
group by ward;

Related

Need help for MS Access Select Request using 2 tables

For a "products reservation system", I have 2 tables :
"RD", for global reservations data (fieds: ID, CustomerID, Date, ...)
"RP", for reserved products data per reservation (fields: ID, RD_ID, ProductID, Status, ...). RD_ID fits with the ID in RD table (field for joining). Status field can have these values: O, C, S.
I need to extract (with 2 Select instructions) the list of reservations and the number of reservations for which all products have status 'O' .
Data example for RP:
ID | RD_ID | ProdID | Status
----------------------------
1 | 1 | 100 | O
2 | 1 | 101 | O
3 | 1 | 102 | O
4 | 2 | 105 | O
5 | 2 | 100 | S
6 | 3 | 101 | C
7 | 3 | 102 | O
In this example, Select statement should return only RD_ID 1
For the number of ID, the following request does not work because it also includes reservations with products having different status:
SELECT COUNT(rd.ID) FROM rd INNER JOIN rp ON rp.RD_ID = rd.ID WHERE rp.Status = 'O';
Could you help me for the right Select statement?
Thank you.
SELECT rd.ID, COUNT(rd.ID) CountOfRD, status
FROM rd INNER JOIN rp ON rp.RD_ID
GROUP BY rd.ID, status
Use not exists as follows:
Select t.* from your_table t
Where t.status = 'O'
And not exists (select 1 from your_table tt
Where t.rd_id = tt.rd_id
And t.status != tt.status)
You can also use group by and having as follows:
Select rd_id
From your_table t
Group by rd_id
Having sum(case when status <> 'O' then 1 end) > 0

Optimized Query that selects 3 salaries from the past 2 years

I have a query that will show the past 3 salaries within a 2 year period (if there are three) I have the query up and running the problem is, is its extremely slow... I'm wondering if there was a better way to write this query. I'm some-what new to oracle.
Here are my Tables
TABLE 1: Salary
ASSIGN_ID | start_date | end_date | salary |
1 | 11/27/2017 | 1/05/2018 | 50000.0 |
2 | 1/06/2018 | 6/08/2018 | 76000.0 |
3 | 6/09/2018 | 12/31/4712 | 80500.0 |
TABLE 2: Assignments
ASSIGN_ID | per_ID | start_date | end_date |
1 | 1 | 11/2/2017 | 1/05/2018 |
2 | 1 | 1/06/2018 | 6/08/2018 |
3 | 1 | 6/09/2018 | 12/31/4712 |
4 | 2 | 5/12/2016 | 7/18/2017 |
5 | 2 | 7/19/2017 | 12/31/4712 |
Table 3: Person
per_id | first_name | last_name |
1 | John | Smith |
2 | Jane | Doe |
Our end dates default to 12/31/4712 if they're are currently active in the assignment
My Query looks like this:
SELECT
per.first_name,
per.last_name,
(CASE WHEN sal1.start_date >= add_months(CURRENT_DATE, -24)
THEN sal1.salary
ELSE NULL END) oldest_salary,
(CASE WHEN sal2.start_date >= add_months(CURRENT_DATE, -24)
THEN sal2.salary
ELSE NULL END) prior_salary,
sal3.salary current_salary,
FROM
person per
INNER JOIN assignments asg1 ON asg1.per_id = per.per_id
INNER JOIN assignments asg2 ON asg2.per_id = asg1.per_id
INNER JOIN assignments asg3 ON asg3.per_id = asg2.per_id
INNER JOIN salary sal1 ON sal1.assign_id = asg1.assign_id
INNER JOIN salary sal2 ON sal2.assign_id = asg2.assign_id
INNER JOIN salary sal3 ON sal3.assign_id = asg3.assign_id
WHERE asg3.start_date =
(SELECT MAX(asg.start_date
FROM assignments asg
WHERE asg.assign_id = asg3.assign_id)
AND (asg3.start_date - 1) BETWEEN asg2.start_date and asg2.end_date
AND (asg2.start_date - 1) BETWEEN asg1.start_date and asg1.end_date
AND sal1.salary != sal2.salary
AND sal2.salary != sal3.salary
ORDER BY 2,1
Is there a simpler way to do this? because when I run my script it processes forever. I think I might need better joins. like I said i'm new and my understanding of joins is weak.
A simpler form:
SELECT
z.first_name,
z.last_name,
--typical cross-db compatible pivot method
MAX(CASE WHEN z.rown = 1 THEN z.salary END) as recentsalary,
MAX(CASE WHEN z.rown = 2 THEN z.salary END) as oldersalary,
MAX(CASE WHEN z.rown = 3 THEN z.salary END) as oldestsalary
FROM
(
SELECT
per.first_name,
per.last_name,
--number assignments from 1=recent to N older
row_number() over(partition by a.per_id order by a.start_date desc) rown
s.salary
FROM --join up all
person p
INNER JOIN assignments a ON a.per_id = p.per_id
INNER JOIN salary s ON s.assign_id = s.assign_id
WHERE a.end_date > ADD_MONTHS(SYSDATE, -36) --only recent 3 years
) z
WHERE z.rown <= 3 --only the most recent 3 assignments
GROUP BY first_name, last_name --achieve pivot
It works by:
Join up all data so people, assignments and salaries are known
Only consider assignments ended since 3 years ago
Number the assignments in youngest to oldest order (1=youngest)
Pivot the top 3 numberings into 3 columns for recent, older and oldest salary, per person

Select data from three table in sql

I have three table Like
Student : Sid, SName, SEmail
Fees_Type : Fid, FName, FPrice
StudentFees : Sid(FK from Student),Fid(FK from Fees_Type), FDate
Data of Each Table :
Student :
SID |SName | SEmail
1 | ABC | ABC#www.com
2 | XYZ | xyz#www.com
Fees_Type:
Fid | FName | FPrice
1 | Chess | 100
2 | Cricket | 200
StudentFees:
Sid | Fid| FDate
1 | 1 | 5/2
1 | 2 | 6/2
2 | 1 | 7/2
2 | 2 | 8/2
1 | 1 | 6/2
Now I want to Get data Like
SID|SName|SEmail | Total_Chess_Played|Total_Cricket_Played | ToTal_Fees
1 | ABC |ABC#www.com | 2 | 1 | 400
2 | XYZ |xyz#www.com | 1 | 1 | 300
I have tried these following query but can not get Group by or perfect result
SELECT s.sId, SEmail, SName, FName ,FPrice
FROM Student s
INNER JOIN StudentFees sf ON s.sId = sf.EId
INNER JOIN Fees_Type f ON f.fId = sf.fId
WHERE MONTH(pr.TDDate) = MONTH(dateadd(dd, 1, GetDate())) AND
YEAR(pr.TDDate) = YEAR(dateadd(dd, -1, GetDate()))
I am new in SQL. So Please Help Me.
Thank You.
You could do something like this:
SELECT
Student.SID,
Student.SName,
Student.SEmail,
SUM(CASE WHEN Fees_Type.FName='Chess' THEN 1 ELSE 0 END) AS Total_Chess_Played,
SUM(CASE WHEN Fees_Type.FName='Cricket' THEN 1 ELSE 0 END) AS Total_Cricket_Played,
SUM(Fees_Type.FPrice) AS ToTal_Fees
FROM
Student
JOIN StudentFees ON Student.sId = StudentFees.EId
JOIN Fees_Type ON Fees_Type.fId = StudentFees.fId
WHERE
MONTH(StudentFees.TDDate) = MONTH(dateadd(dd, 1, GetDate())) AND
YEAR(StudentFees.TDDate) = YEAR(dateadd(dd, -1, GetDate()))
GROUP BY
Student.SID,
Student.SName,
Student.SEmail
try this
SELECT
s.SID,
s.SName,
s.SEmail,
SUM(CASE WHEN ft.FName='Chess' THEN 1 ELSE 0 END) AS Total_Chess_Played,
SUM(CASE WHEN ft.FName='Cricket' THEN 1 ELSE 0 END) AS Total_Cricket_Played,
SUM(ft.FPrice) AS ToTal_Fees
FROM
Student s
JOIN StudentFees sf ON s.sId = sf.Sid
JOIN Fees_Type ft ON ft.fId = sf.fId
GROUP BY
s.SID,
s.SName,
s.SEmail

SQL Server query to roll up data

I have the following SQL statement. It joins three tables: Person, Deliverable, and DeliverableActions
select
p.first_name, p. last_name, d.title, da.type
from
Deliverable d
right join
Person p on d.person_responsible_id = p.id
right join
DeliverableAction da on da.DeliverableID = d.id
where
d.date_deadline >= #startDate and
d.date_deadline <= #endDate
order by
d.title
The result is the following:
first_name | last_name | title | type
-----------+-------------+--------------+------
Joe | Kewl | My_Report_1 | 2
Joe | Kewl | My_Report_1 | 3
Joe | Kewl | My_Report_1 | 1
Sly | Foxx | Other_Rep_1 | 1
Sly | Foxx | Other_Rep_1 | 2
My goal result is to get the following table:
first_name | last_name | title | type_1 | type_2 | type_3 | type_4
-----------+------------+--------------+--------+--------+--------+---------
Joe | Kewl | My_report_1 | 1 | 1 | 1 | 0
Sly | Foxx | Other_Rep_1 | 1 | 1 | 0 | 0
Unfortunately I don't know what term to describe what I'm doing. I've searched 'grouping' and 'aggregation', but I'm left without an answer so I am putting it to the community. Thank you in advance for your help.
you can use case based aggregation or you can also use pivot
select p.first_name,
p. last_name,
d.title,
sum(case when da.type = 1 then 1 else 0 end) as type_1,
sum(case when da.type = 2 then 1 else 0 end) as type_2,
sum(case when da.type = 3 then 1 else 0 end) as type_3,
sum(case when da.type = 4 then 1 else 0 end) as type_4,
from Deliverable d
right join Person p on d.person_responsible_id = p.id
right join DeliverableAction da on da.DeliverableID = d.id
where d.date_deadline >= #startDate and
d.date_deadline <= #endDate
group by p.first_name, p.last_name, d.title
select
first_name, last_name, title,
sum(case when type = 1 then 1 else 0 end) as type_1
from
(
select p.first_name, p. last_name, d.title, da.type from Deliverable d
right join Person p on d.person_responsible_id = p.id
right join DeliverableAction da on da.DeliverableID = d.id
where d.date_deadline >= #startDate and
d.date_deadline <= #endDate
) as a
group by first_name, last_name, title
You're looking for PIVOT
If you're using SQL Server 2008+, it has pivot function as described at http://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx
Basically, you write something like (sorry, I just pasted example from the quoted link but that should give you some idea):
-- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;

SQL Finding multiple values difficulty

I'm trying to generate the correct SQL for a project.
Here is a sample dataset:
DateTime | EmpID | Function | Location
--------------------------------------------------
1/23/2015 2:00PM | 123 | 1 | 1
1/23/2015 2:10PM | 123 | 2 | 1
1/23/2015 2:20PM | 123 | 1 | 2
1/23/2015 2:40PM | 123 | 2 | 2
1/24/2015 2:00PM | 321 | 1 | 2
1/24/2015 2:15PM | 321 | 2 | 2
1/24/2015 2:30PM | 321 | 1 | 3
I need to pull a count of all records where functionid = 1 and location MUST EQUAL both 1 and 2. So the first row and the third row would be returned and considered a count of 1.
Hopefully I'm making sense with this. Basically I need to know how many times an employee was at two locations. Any help would be appreciated.
Group by EmpId and count locations.
SELECT *
FROM MyTable T1
WHERE Function = 1 AND
NOT EXISTS (SELECT 1
FROM MyTable T2
WHERE T1.EmpId = T2.EmpId AND
T1.Function = T2.Function AND
T2.Location NOT IN (1, 2))
GROUP BY EmpId
HAVING Count(DISTINCT Location) > 1
Have not tested it but think this will work
SELECT EmpID, COUNT(EmpID) AS NumOfTimes
From [Table Name]
WHERE FunctionID = 1 AND (Location = 1 OR Location = 2)
GROUP BY EmpID
HAVING NumOfTimes = 2
SELECT EmpID , COUNT(*) total, COUNT (CASE WHEN Location = 1 THEN 1 END) was_in_1,COUNT (CASE WHEN Location = 2 THEN 1 END) was_in_2
FROM table
WHERE Function = 1
GROUP BY EmpID
HAVING MAX(CASE WHEN Location = 1 THEN 1 ELSE 0 END) = MAX(CASE WHEN Location = 2 THEN 1 ELSE 0 END)
but if you would like to know if the employee was in any 2 locations then jarlh gave the right comment
group by EmpID
having count(distinct location) >= 2
SELECT A.EmpID, COUNT(*)
FROM YOURTABLENAME A
INNER JOIN YOURTABLENAME B
ON A.EmpID= B.EmpID
WHERE A.Location = 1 and B.Location = 2
and A.Function = B.Function and A.Function = 1
GROUP BY A.EmpID