SQL query for querying counts from a table - sql

The prompt is to form a SQL query.
That finds the students name and ID who attend all lectures having ects more than 4.
The tables are
CREATE TABLE CLASS (
STUDENT_ID INT NOT NULL,
LECTURE_ID INT NOT NULL
);
CREATE TABLE STUDENT (
STUDENT_ID INT NOT NULL,
STUDENT_NAME VARCHAR(255),
PRIMARY KEY (STUDENT_ID)
)
CREATE TABLE LECTURE (
LECTURE_ID INT NOT NULL,
LECTURE_NAME VARCHAR(255),
ECTS INT,
PRIMARY KEY (LECTURE_ID)
)
I came up with this query but this didn't seem to work on SQLFIDDLE. I'm new to SQL and this query has been a little troublesome for me. How would you query this?
SELECT STUD.STUDENT_NAME FROM STUDENT STUD
INNER JOIN CLASS CLS AND LECTURE LEC ON
CLS.STUDENT_ID = STUD.STUDENT_ID
WHERE LEC.CTS > 4
How do I fix this query?
UPDATE
insert into STUDENT values(1, 'wick', 20);
insert into STUDENT values(2, 'Drake', 25);
insert into STUDENT values(3, 'Bake', 42);
insert into STUDENT values(4, 'Man', 5);
insert into LECTURE values(1, 'Math', 6);
insert into LECTURE values(2, 'Prog', 6);
insert into LECTURE values(3, 'Physics', 1);
insert into LECTURE values(4, '4ects', 4);
insert into LECTURE values(5, 'subj', 4);
insert into SCLASS values(1, 3);
insert into SCLASS values(1, 2);
insert into SCLASS values(2, 3);
insert into SCLASS values(3, 1);
insert into SCLASS values(3, 2);
insert into SCLASS values(3, 3);
insert into SCLASS values(4, 4);
insert into SCLASS values(4, 5);

The following approach might get the job done.
It works by generating two subqueries :
one that counts how many lectures whose ects is greater than 4 were taken by each user
another that just counts the total number of lectures whose ects is greater than 4
Then, the outer query filters in users whose count reaches the total :
SELECT x.student_id, x.student_name
FROM
(
SELECT s.student_id, s.student_name, COUNT(DISTINCT l.lecture_id) cnt
FROM
student s
INNER JOIN class c ON c.student_id = s.student_id
INNER JOIN lecture l ON l.lecture_id = c.lecture_id
WHERE l.ects > 4
GROUP BY s.student_id, s.student_name
) x
CROSS JOIN (SELECT COUNT(*) cnt FROM lecture WHERE ects > 4 ) y
WHERE x.cnt = y.cnt ;

As GMB already said in their answer: count required lections and compare with those taken per student. Here is another way to write such query. We outer join classes to all lectures with ECTS > 4. Analytic window functions allow us to aggregate by two different groups at the same time (here: all rows and student's rows).
select *
from student
where (student_id, 0) in -- 0 means no gap between required and taken lectures
(
select
student_id,
count(distinct lecture_id) over () -
count(distinct lecture_id) over (partition by c.student_id) as gap
from lecture l
left join class c using (lecture_id)
where l.ects > 4
);
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=74371314913565243863c225847eb044

You can try the following query.
SELECT distinct
STUD.STUDENT_NAME,
STUD.STUDENT_ID
FROM STUDENT STUD
INNER JOIN CLASS CLS ON CLS.STUDENT_ID = STUD.STUDENT_ID
INNER JOIN LECTURE LEC ON LEC.LECTURE_ID=CLS.LECTURE_ID
where LEC.ECTS > 4 group by STUD.STUDENT_ID,STUD.STUDENT_NAME
having COUNT(STUD.STUDENT_ID) =(SELECT COUNT(*) FROM LECTURE WHERE ECTS > 4)

Related

How can I create a Running Total Sumifs-like function in SQL?

I'm pretty new to SQL, but Excel has become far too slow to continue working with, so I'm trying SQLiteStudio. I'm looking to create a column in a query showing running total over time (characterized as Schedule Points, marking each percent through a project's run time). Complete marks whether a Location has completed the install (Y/NULL), and is used simply to filter out incomplete locations from further calculations.
I currently have
With cte as(
Select [Location]
,[HW/NonHW]
,[Obligation/Actual]
,[Schedule Point]
,[CY20$]
,[Vendor Name]
,[Vendor Zip Code]
,[Complete]
,[System Rollup (Import)]
,IIf([Complete] = "Y", [CY20$], 0) As [Completed Costs]
FROM data)
Select [Location]
,[HW/NonHW]
,[Obligation/Actual]
,[Schedule Point]
,[CY20$]
,[Vendor Name]
,[Vendor Zip Code]
,[Complete]
,[System Rollup (Import)]
,[Completed Costs]
,SUM([Completed Costs]) OVER (PARTITION BY [Obligation/Actual], [Normalized Schedule Location 1%],[System Rollup (Import)], [HW/NonHW]) As [CY20$ Summed]
FROM cte
At this point, what I'm looking to do is a sum not for each Schedule Point, but all prior Schedule Points (i.e. the <= operator in an Excel sumifs statement)
For reference, here is the sumifs I am trying to replicate:
=SUMIFS($N$2:$N$541790,$AU$2:$AU$541790,"Y",$AQ$2:$AQ$541790,AQ2,$AI$2:$AI$541790,AI2,$AH$2:$AH$541790,AH2,$AJ$2:$AJ$541790, "<=" & AJ2)
N is CY20$, AU is Complete, AQ is System, AI is Obligation/Actual, AH is HW/NonHW, AJ is Schedule Point.
Any help would be appreciated!
The equivalent to SUMIFS is a combination of SUM and CASE-WHEN in SQL.
Abstract example:
SELECT
SUM(
CASE
WHEN <condition1> AND <condition2> AND <condition3> THEN 1
ELSE 0
END
)
FROM yourtable
In the above, condition1, condition2 and condition3 are logical expressions, the < and > are just notifying that you have an expression there, it is not part of the syntax. It is also unnecessary to have exactly 3 conditions, you can have as many as you like. Also, it is unnecessary to use AND as the operator, you can construct your own expression as you like. The reason for which I have used the AND operator was that you intend to have a disjunction, presumably, based on the fact that you used SUMIFS.
A more concrete example:
CREATE TABLE person(
number int,
name text,
age int
);
INSERT INTO person(number, name, age)
VALUES(1, 'Joe', 12);
INSERT INTO person(number, name, age)
VALUES(2, 'Jane' 12);
INSERT INTO person(number, name, age)
VALUES(3, 'Robert', 16);
INSERT INTO person(number, name, age)
VALUES(4, 'Roberta', 15);
INSERT INTO person(number, name, age)
VALUES(5, 'Blian', 18);
INSERT INTO person(number, name, age)
VALUES(6, 'Bigusdqs', 19);
SELECT
SUM(
CASE
WHEN age <= 16 AND name <> 'Joe' THEN 1
ELSE 0
END
) AS MySUMIFS
FROM person;
EDIT
If we are interested to know how many people have a smaller age than the current person, then we can do a join:
SELECT
SUM(
CASE
WHEN p2.age <= p1.age THEN 1
ELSE 0
END
) AS MySUMIFS, name
FROM person p1
JOIN person p2
ON p1.name <> p2.name
GROUP BY p1.name;
EDIT2
Created a Fiddle based on the ideas described above, you can reach it at https://dbfiddle.uk/?rdbms=sqlite_3.27&fiddle=3cb0232e5d669071a3aa5bb1df68dbca
The code in the fiddle:
CREATE TABLE person(
number int,
name text,
age int
);
INSERT INTO person(number, name, age)
VALUES(1, 'Joe', 12);
INSERT INTO person(number, name, age)
VALUES(2, 'Jane' 12);
INSERT INTO person(number, name, age)
VALUES(3, 'Robert', 16);
INSERT INTO person(number, name, age)
VALUES(4, 'Roberta', 15);
INSERT INTO person(number, name, age)
VALUES(5, 'Blian', 18);
INSERT INTO person(number, name, age)
VALUES(6, 'Bigusdqs', 19);
SELECT
SUM(
CASE
WHEN p2.age <= p1.age THEN 1
ELSE 0
END
) AS MySUMIFS, p1.name
FROM person p1
JOIN person p2
ON p1.name <> p2.name
GROUP BY p1.name;

What is the correct order for recursive queries using two tables on SQL?

I have the following two tables:
CREATE TABLE empleados (
id INTEGER PRIMARY KEY,
nombre VARCHAR(255) NOT NULL,
gerenteId INTEGER,
FOREIGN KEY (gerenteId) REFERENCES empleados(id)
);
CREATE TABLE ventas (
id INTEGER PRIMARY KEY,
empleadoId INTEGER NOT NULL,
valorOrden INTEGER NOT NULL,
FOREIGN KEY (empleadoId) REFERENCES empleados(id)
);
With the following data:
INSERT INTO empleados(id, nombre, gerenteId) VALUES(1, 'Roberto', null);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(2, 'Tomas', null);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(3, 'Rogelio', 1);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(4, 'Victor', 3);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(5, 'Johnatan', 4);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(6, 'Gustavo', 2);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(1, 3, 400);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(2, 4, 3000);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(3, 5, 3500);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(4, 2, 40000);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(5, 6, 3000);
I'm trying to get a query to obtain the sum of all the "Orders" which belong directly or inderectly to the main managers. The main managers are the ones whose doesn't report to anybody else. In this calse, Roberto and Tomas are the main managers but there could be other ones. The result must to take into account not just the sales (ventas) made directly by him but also by any of their employees (direct employees or employees of their employees).
So in this case I'm expecting the following result:
-- Id TotalVentas
-- ----------------
-- 1 6900
-- 2 43000
Where the Id column refers to the employees' id which are "main" managers and TotalVentas column is the sum of all the ventas (valorOrden) made by them and their employees.
So Roberto has no records for orders but Rogelio (his employee) has one of 400, Victor (Rogelio's employee) has one for 3000 and Johnatan (Victor's employee) has another for 3500. So the sum of all of them is 6900. And it is the same case with Tomas which has one venta directly made by him plus another one made by Gustavo who is his employee.
The query that I have so far is the following:
WITH cte_org AS (
SELECT
id,
nombre,
gerenteId,
0 as EmpLevel
FROM
dbo.empleados
WHERE gerenteId IS NULL
UNION ALL
SELECT
e.id,
e.nombre,
e.gerenteId,
o.EmpLevel + 1
FROM
dbo.empleados e
INNER JOIN cte_org o
ON o.id = e.gerenteId
WHERE e.gerenteId IS NOT NULL
)
SELECT cte.id, SUM(s.orderValue)
FROM cte_org cte, dbo.sales s
WHERE (cte.id = s.employeeId AND cte.gerenteId is null)
OR
(cte.id = s.employeeId AND cte.EmpLevel <> 0 AND
cte.gerenteId in (select ee.id from dbo.empleados ee where ee.gerenteId is null)
)
--AND
--(cte.gerenteId in (select ee.id from dbo.empleados ee where ee.gerenteId is null)
--OR
--cte.gerenteId is null)
--AND cte.gerenteId = NULL
group by cte.id
;
Could anybody help me with this?
This is traversing a hierarchy, starting with the highest level managers, and then joining in the sales:
with cte as (
select id, nombre, id as manager
from empleados e
where gerenteid is null
union all
select e.id, e.nombre, cte.manager
from cte join
empleados e
on cte.id = e.gerenteid
)
select cte.manager, sum(valororden)
from cte join
ventas v
on cte.id = v.empleadoid
group by cte.manager;
Here is a db<>fiddle. The Fiddle uses SQL Server, because that is consistent with the syntax that you are using.
In oracle you can do this using below query:
SQLFiddle
select manager, sum(amount) as total_amount from
(
select level, CONNECT_BY_ROOT employeeid Manager, a.* from
(
SELECT a.id as employeeid, a.nombre as name , a.GERENTEID as manager_id,
b.EMPLEADOID as sales_id, b.VALORORDEN amount
from empleados a left outer join ventas b
on (a.id = b.empleadoId)
) a
start with manager_id is null
connect by prior employeeid = manager_id) x
group by manager;

A Better Way for Conditional Counting?

Imagine I have a CTE that creates a result set containing all of the information I need and need to do a bunch of conditional counting on the result set. Is there a better way to do it than a bunch of subqueries?
I can't use count() over () either as I need to sometimes do a distinct count on values and using a case when val=true then 1 else null end to conditionally count doesn't let me distinctly count, not to mention that it is basically the same as doing a bunch of subqueries.
Any recommendations, or is creating a bunch of subqueries the way to go?
(example SQL Fiddle)
Table Definitions
create table person (id int, name varchar2(20), age int, cityID int);
create table city (id int, name varchar2(20), stateID int);
create table state (id int, name varchar2(20));
insert into person values(1, 'Bob', 45, 1);
insert into person values(2, 'Joe', 33, 1);
insert into person values(3, 'Craig', 20, 1);
insert into person values(4, 'Alex', 45, 2);
insert into person values(5, 'Kevin', 33, 3);
insert into city values(1, 'Chicago', 1);
insert into city values(2, 'New York', 2);
insert into city values(3, 'Los Angeles', 3);
insert into state values(1, 'Illinois');
insert into state values(2, 'New York');
insert into state values(3, 'California');
SQL Query Example
with cte as (
select p.name pName
, p.age pAge
, c.name cName
, s.name sName
from person p
inner join city c
on p.cityID = c.ID
inner join state s
on c.stateID = s.ID
)
select distinct
(select count(*) from cte) totalRows
, (select count(*) from cte where pAge = 45) total45YO
, (select count(*) from cte where cName like 'Chicago') totalChicago
, (select count(distinct cName) from cte) totalCities
from cte
An example output I would hope for
TOTALROWS TOTAL45YO TOTALCHICAGO TOTALCITIES
------------------------------------------------------
5 2 3 3
Easiest is just as #jarlh mentions and use case/sum combinations to accomplish as follows.
SQL> select count(*) totalRows
2 , sum(case when p.age=45 then 1 else 0 end) total45YO
3 , sum(case when c.name like 'Chicago' then 1 else 0 end) totalChicago
4 , count(distinct c.name) totalCities
5 from person p
6 inner join city c
7 on p.cityID = c.ID
8 inner join state s
9 on c.stateID = s.ID;
TOTALROWS TOTAL45YO TOTALCHICAGO TOTALCITIES
____________ ____________ _______________ ______________
5 2 3 3
SQL>

stuck with producing a query for this?

Employee number 99 has phoned in sick. We need the full details of his supervisor.
I have an employee table with "empno", "name", "date_of_birth", "address", "postcode", "cinema_no", "super_empno" as fields.
Employee number 99's supervisor is the details of the "super_empno" which is also in employee table.
SELECT *
FROM employee
WHERE empno = (SELECT super_empno FROM employee WHERE empno = 99)
You need to do a self join:
I have created following table:
create table table1("empno" int, "name" varchar(33), "super_empno" int);
insert into table1 values(1, "Bob", 5);
insert into table1 values(2, "Megan", 7);
insert into table1 values(3, "Poul", 5);
insert into table1 values(4, "Eva", 6);
insert into table1 values(5, "Zane", 2);
insert into table1 values(6, "Ibrogim", 7);
insert into table1 values(99, "Nikolaus", 1);
As result data looks like this:
1|Bob|5
2|Megan|7
3|Poul|5
4|Eva|6
5|Zane|2
6|Ibrogim|7
99|Nikolaus|1
Supervisor for user 99 is user 1. To get information about 99th user's supervisor you need to do the self-join on a table1:
select b.* from table1 a, table1 b where a.empno=99 and a.super_empno = b.empno;
Result:
1|Bob|5

SQL: couple people who assisted to the same event

create table people(
id_pers int,
nom_pers char(25),
d_nais date,
d_mort date,
primary key(id_pers)
);
create table event(
id_evn int,
primary key(id_evn)
);
create table assisted_to(
id_pers int,
id_evn int,
foreign key (id_pers) references people(id_pers),
foreign key (id_evn) references event(id_evn)
);
insert into people(id_pers, nom_pers, d_nais, d_mort) values (1, 'A', current_date - integer '20', current_date);
insert into people(id_pers, nom_pers, d_nais, d_mort) values (2, 'B', current_date - integer '50', current_date - integer '20');
insert into people(id_pers, nom_pers, d_nais, d_mort) values (3, 'C', current_date - integer '25', current_date - integer '20');
insert into event(id_evn) values (1);
insert into event(id_evn) values (2);
insert into event(id_evn) values (3);
insert into event(id_evn) values (4);
insert into event(id_evn) values (5);
insert into assisted_to(id_pers, id_evn) values (1, 5);
insert into assisted_to(id_pers, id_evn) values (2, 5);
insert into assisted_to(id_pers, id_evn) values (2, 4);
insert into assisted_to(id_pers, id_evn) values (3, 5);
insert into assisted_to(id_pers, id_evn) values (3, 4);
insert into assisted_to(id_pers, id_evn) values (3, 3);
I need to find couples who assisted to the same event on any particular day.
I tried:
select p1.id_pers, p2.id_pers from people p1, people p2, assisted_event ae
where ae.id_pers = p1.id_pers
and ae.id_pers = p2.id_pers
But returns 0 rows.
What am I doing wrong?
Try this:
select distint ae.id_evn,
p1.nom_pers personA, p2.nom_pers PersonB
from assieted_to ae
Join people p1
On p1.id_pers = ae.id_pers
Join people p2
On p2.id_pers = ae.id_pers
And p2.id_pers > p1.id_pers
This generates all pairs of people [couples] who assisted on the same event. With your schema, there is no way to restrict the results to cases where they assisted on the same day. The assumption is that if they assisted on the same event, then that event can only have occurred on one day.
You select two persons, so you need to select two assisted_event rows as well, because each person has its own assignment row in the assisted_event table. The idea is to build a link between p1 and p2 through a pair of assisted_event rows sharing the same id_evn
select p1.id_pers, p2.id_pers
from people p1, people p2
where exists (
select *
from assisted_event e1
join assisted_event e2 on e1.id_evn=e2.id_evn
where e1.id_pers=p1.id_pers and e2.id_pers=p2.id_pers
)
When re-phrased into ANSI JOIN syntax so I can read it, your query reads:
select p1.id_pers, p2.id_pers
from assisted_event ae
inner join people p1 ON (ae.id_pers = p1.id_pers)
inner join people p2 ON (ae.id_pers = p2.id_pers)
Since id_pers is the primary key of p1, it is impossible for ae.id_pers to be simultaneously equal to p1.id_pers and p2.id_pers. You'll need to find another approach.
You don't need to join on people at all for this, though you'll probably want to in order to populate their details. You need to self-join the people-to-events join table not the people table in order to get the desired results, filtering the self-join to include only rows where the event ID is the same but the people are different. Using > rather than <> means you don't have to use another pass to filter out the (a,b) vs (b,a) pairings.
Something like:
select ae1.id_evn event_id, ae1.id_pers id_pers1, ae2.id_pers id_pers2
from assisted_to ae1
inner join assisted_to ae2
on (ae2.id_evn = ae1.id_evn and ae1.id_pers > ae2.id_pers)
You can now, if desired, add additional joins on the event and persion tables to populate details. You'll need to join people twice with different aliases to populate the two different "sides". See Charles Bretana's example.