How to count occurrences of a column value in SQL? - sql

I have a table of students:
id | age|num
-------------
0 | 25| 10
1 | 25| 5
2 | 23| 5
I want to query for all students, and an additional column that counts how many students are of the same age:
id | num | age | agecount|numcount
-------------------------------------
0 | 10 | 25 | 2 |1
1 | 5 | 23 | 1 |2
What's the most efficient way of doing this? if there's a better way**. Is there?

You have two queries:
One for the list of the students:
SELECT
id, age, num
FROM
students
And one for the count of students with the same age:
SELECT
age
, count(1)
FROM
students
GROUP BY
age
Now you have to combine these two queries:
You can JOIN one or more tables or subqueries. Lets do it:
SELECT
S.id, S.age, S.num, age.cnt
FROM
-- List of all students
(
SELECT
id, age, num
FROM
students
) S
-- Ages with student counts
INNER JOIN (
SELECT
age
, count(1) AS cnt
FROM
students
GROUP BY
age
) A
ON S.age = A.age
You can simplify the above query with removing the first subquery and use the students table instead:
SELECT
S.id, S.age, S.num, A.cnt
FROM
students S
-- Ages with student counts
INNER JOIN (
SELECT
age
, count(1) AS cnt
FROM
students
GROUP BY
age
) A
ON students.age = age.age
Now you can modify this sample query to achieve your goal.

To count student with the same age :
select age ,count(age) from s_table group by age

Related

An SQL query to pull count of employees absent under each manager on all dates

The objective of the query is get a count of employees absent under each manager.
Attendance (Dates when employees are present)
id date
1 16/05/2020
2 16/05/2020
1 17/05/2020
2 18/05/2020
3 18/05/2020
Employee
id manager_id
1 2
2 3
3 NA
The desired output should be in this format:
Date manager_id Number_of_absent_employees
16/05/2020 NA 1
17/05/2020 3 1
17/05/2020 NA 1
18/05/2020 2 1
I have tried writing code but partially understood it, intuition being calculating total number of actual employees under each manager and subtracting it from number of employees present on given day. Please help me in completing this query, many thanks!
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee as e
inner join employee on e.employee_id=employee.employee_id
group by employee.manager_id
)
,t2 as /* for counting total employees present each day */
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select * from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Cross join the distinct dates from Attendance to Employee and left join Attendance to filter out the matching rows.
The remaining rows are the absences so then you need to aggregate:
select d.date, e.manager_id,
count(*) Number_of_absent_employees
from (select distinct date from Attendance) d
cross join Employee e
left join Attendance a on a.date = d.date and a.id = e.id
where a.id is null
group by d.date, e.manager_id
See the demo.
Results:
| date | manager_id | Number_of_absent_employees |
| ---------- | ---------- | -------------------------- |
| 16/05/2020 | NA | 1 |
| 17/05/2020 | 3 | 1 |
| 17/05/2020 | NA | 1 |
| 18/05/2020 | 2 | 1 |
Try this query. In first cte just simplify your code. And in the last query calculate absent employees.
--in this CTE just simplify counting
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee
group by manager_id
)
,t2 as
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select t2.date,t2.manager_id, (t1.totalc-t2.present) as employees_absent from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Select ec.manager_id, date, (total_employees - employee_attended) as employees_absent from
(Select manager_id, count(id) as total_employees
from employee
group by manager_id) ec,
(Select distinct e.manager_id, a.date, count(a.id) over (partition by e.manager_id, a.date) as employee_attended
from Employee e, attendence, a
where e.id = a.id(+)) ea
where ec.manager_id = ea.manager_id (+)
I guess this should work

select the highest record between two table

I have two table. One table contains graduation records and the second table contains post graduation records. A candidate must have graduation, but it is not necessarily to have post graduation.
My question is to select the post graduation record if the candidate has post graduation else only graduation.
table 1 graduation_table
rollno | degree | division
--------------------------
001 | B.tech | 1st
002 | B.sc | 1st
003 | BA | 1st
table 2 postgraduation_table
rollno | degree | division
--------------------------
002 | M.sc | 1st
the result must be
rollno | degree | division
--------------------------
001 | B.tech | 1st
002 | M.sc | 1st
003 | BA | 1st
You want all rows from graduation_table which do not have a row in postgraduation_table plus those in postgraduation_table. This can be expressed with a not exists and union query:
select gt.rollno, gt.degree, gt.division
from graduation_table gt
where not exists (select *
from postgraduation_table pg
where pg.rollno = gt.rollno)
union all
select rollno, degree, division
from postgraduation_table
order by rollno;
Online example: http://rextester.com/IFCQR67320
select
rollno,
case when p.degree is null then g.degree else p.degree end as degree,
case when p.division is null then g.division else p.division end as division
from
grad g
left join
post p using (rollno)
Or better as suggested in the comments:
select
rollno,
coalesce (p.degree, g.degree) as degree,
coalesce (p.division, g.division) as division
from
grad g
left join
post p using (rollno)
Take a union of both tables, and introduce a position column, to rank the relative importance of the two tables. The postgraduate table has a pos value of 1, and the graduate table has a value of 2. Then, apply ROW_NUMBER() over this union query and assign a row number to each rollno group of records (presumed to be either one or at most two records). Finally, perform one more outer subquery to retain the most important record, postgraduate first, graduate second.
SELECT rollno, degree, division
FROM
(
SELECT
rollno, degree, division,
ROW_NUMBER() OVER (PARTITION BY rollno ORDER BY pos) rn
FROM
(
SELECT p.*, 1 AS pos p FROM postgraduation_table
UNION ALL
SELECT p.*, 2 FROM graduation_table p
) t
) t
WHERE t.rn = 1;
This should make your needs :
SELECT dg.rollno, CASE WHEN pg IS NOT NULL THEN pg.degree ELSE gd.degree END AS degree, dg.division
FROM graduation_table AS dg
LEFT OUTER JOIN postgraduation_table AS pg USING (rollno)
GROUP BY dg.rollno, dg.division;
Hope this help.

select only one row that has the highest count in sql

I need to select one row only which has the highest count. How do I do that?
This is my current code:
select firstname, lastname, count(*) as total
from trans
join work
on trans.workid = work.workid
join artist
on work.artistid = artist.artistid
where datesold is not null
group by firstname, lastname;
Example current:
FIRSTNAME | LASTNAME | TOTAL
------------------------------
Tom | Cruise | 3
Angelina | Jolie | 9
Britney | Spears | 5
Ellie | Goulding | 4
I need it to select only this:
FIRSTNAME | LASTNAME | TOTAL
--------------------------------
Angelina | Jolie | 9
You can add order by total desc and fetch first 1 row only (since Oracle 12c r1 only, otherwise you should use your result as temp table and select from it to use rownum = 1 limitation in the where clause) , in case you total can't be the same for different groups. The other way is to add this having clause, so you can list all people with maximum total:
having count(*) = (select max(total) from (select count(*) as total from <your_query>) tmp)
or that:
having count(*) = (select count(*) as total from <your_query> order by total desc fetch first 1 row only)
In Oracle 12, you can do:
select firstname, lastname, count(*) as total
from trans join
work
on trans.workid = work.workid join
artist
on work.artistid = artist.artistid
where datesold is not null
group by firstname, lastname
order by count(*) desc
fetch first 1 row only;
In older versions, you can do this with a subquery:
select twa.*
from (select firstname, lastname, count(*) as total
from trans join
work
on trans.workid = work.workid join
artist
on work.artistid = artist.artistid
where datesold is not null
group by firstname, lastname
order by count(*) desc
) twa
where rownum = 1;
this will work sql server 2012..
with CTECount (firstname, lastname,total)
as
(
select firstname, lastname, count(1) as total
from trans
join work
on trans.workid = work.workid
join artist
on work.artistid = artist.artistid
where datesold is not null
group by firstname, lastname
)
select top(1) with ties from CTECount
order by total desc
Thanks
You could do like this, very simple:
select TOP 1 firstname, lastname, count(*) as total from trans
join work on trans.workid=work.workid
join artist on work.artistid=artist.artistid
where datesold is not null
group by firstname, lastname
Order By Total DESC;

Select from two tables without using an OR

I have two tables: employee and department
employee
name | salary | dNumber
A | 20000 | 1
B | 25000 | 1
C | 10000 | 5
D | 10000 | 1
department
departmentName | departmentNumber
math | 1
science | 2
How can I select from these two tables without using boolean operators like OR & AND?
I want to find the employee that works for department 1 or has a salary greater than $20,000.
What I tried:
select name from employee
where salary>= 20000 from (where dNumber in (select departmentNumber from department
where departmentNumber = 5));
That doesn't work because:
ERROR: syntax error at or near "where"
If the requirement is to not use an OR, you could use UNION instead. Since you filter the department on its number, not on its name, you do not need the second table at all:
SELECT name FROM employee WHERE salary > 20000
UNION
SELECT name FROM employee WHERE dNumber = 1
If you wanted to filter the department by name, a join or a subquery would be required:
SELECT name FROM employee WHERE salary > 20000
UNION
SELECT name FROM employee e
JOIN department d ON e.dNumber=d.departmentNumber
WHERE departmentName = 'math'
Here's how you do it:
select name from employee where where dnumber = 1
union
select name from employee where salary > 20000
Basically, union works like the OR operator and intersection works like the AND operator.

Get top results for each group (in Oracle)

How would I be able to get N results for several groups in
an oracle query.
For example, given the following table:
|--------+------------+------------|
| emp_id | name | occupation |
|--------+------------+------------|
| 1 | John Smith | Accountant |
| 2 | Jane Doe | Engineer |
| 3 | Jack Black | Funnyman |
|--------+------------+------------|
There are many more rows with more occupations. I would like to get
three employees (lets say) from each occupation.
Is there a way to do this without using a subquery?
I don't have an oracle instance handy right now so I have not tested this:
select *
from (select emp_id, name, occupation,
rank() over ( partition by occupation order by emp_id) rank
from employee)
where rank <= 3
Here is a link on how rank works: http://www.psoug.org/reference/rank.html
This produces what you want, and it uses no vendor-specific SQL features like TOP N or RANK().
SELECT MAX(e.name) AS name, MAX(e.occupation) AS occupation
FROM emp e
LEFT OUTER JOIN emp e2
ON (e.occupation = e2.occupation AND e.emp_id <= e2.emp_id)
GROUP BY e.emp_id
HAVING COUNT(*) <= 3
ORDER BY occupation;
In this example it gives the three employees with the lowest emp_id values per occupation. You can change the attribute used in the inequality comparison, to make it give the top employees by name, or whatever.
Add RowNum to rank :
select * from
(select emp_id, name, occupation,rank() over ( partition by occupation order by emp_id,RowNum) rank
from employee)
where rank <= 3
tested this in SQL Server (and it uses subquery)
select emp_id, name, occupation
from employees t1
where emp_id IN (select top 3 emp_id from employees t2 where t2.occupation = t1.occupation)
just do an ORDER by in the subquery to suit your needs
I'm not sure this is very efficient, but maybe a starting place?
select *
from people p1
join people p2
on p1.occupation = p2.occupation
join people p3
on p1.occupation = p3.occupation
and p2.occupation = p3.occupation
where p1.emp_id != p2.emp_id
and p1.emp_id != p3.emp_id
This should give you rows that contain 3 distinct employees all in the same occupation. Unfortunately, it will give you ALL combinations of those.
Can anyone pare this down please?