Calculating average for each student - sql

Suppose I have the following tables:
-- table student
create table Student(
num int primary key identity,
firstName varchar(30) not null,
lastName varchar(30)
)
-- table module
create table Module(
code int primary key identity,
name varchar(30) not null,
coefficient int not null)
-- table notation
create table Notation(
stud int references student,
Mod int references Module,
DateExam datetime default getdate(),
Note float check (Note between 0 and 20)
primary key(stud , Mod ))
What I want is to display student names, the student num and averages, ranked from best to worst.
Update:
average = sum (ni*ci)/ sum (ci); c: coefficient. n: note

Don't think you need module. and this assumes note is the field you want to average.
SELECT FirstName, LastName, Note, avg(note) over (partition by s.Num) AvgNote
FROM Student S
LEFT JOIN Notation N
on S.Num = N.Stud
GROUP BY FirstName, LastName, S.Num
ORDER BY as AvgNote Desc
Also, float as a data type when dealing with grades is a bad idea. Float is imprecise by design to support a smaller datastorage footprint. This doesn't matter when you're dealing with scientific notation and precision isn't necessary, but in this case I think decimal would be a better choice.

This gets both the note for each module for each student, and their average across all modules
select s1.FirstName, s1.LastName, m2.name as module_name, n3.Note, x1.av_note
from student s1
left join notation n3
on n3.stud = s1.num
left join module m2
on m2.code = n3.mod
left join
(
select stud, avg(note) as av_note
from notation
group by stud
) x1
on s1.num = x1.stud
order by av_note, lastname desc

Here is my solution:
select tab.num,tab.firstName, sum(noteMultiCoef)/sum(coefficient) as average from
(select st.num,st.firstName, coefficient, note*coefficient as noteMultiCoef
from notation nt, student st, Module md
where st.num= nt.stud
and nt.code = md.mod) tab
group by tab.num, tab.firstName
order by average desc

Related

How to join Views with aggregate functions?

My problem:
In #4, I'm having trouble joining two Views because the other has an aggregate function. Same with #5
Question:
Create a view name it as studentDetails, that would should show the student name, enrollment date, total price per unit and subject description of students who are enrolled on the subject Science or History.
Create a view, name it as BiggestPrice, that will show the subject id and highest total price per unit of all the subjects. The view should show only the highest total price per unit that are greater than 1000.
--4.) Create a view name it as studentDetails, that would should show the student name,
-- enrollment date the total price per unit and subject description of students who are
-- enrolled on the subject Science or History.
CREATE VIEW StudentDetails AS
SELECT StudName, EnrollmentDate
--5.) Create a view, name it as BiggestPrice, that will show the subject id and highest total
-- price per unit of all the subjects. The view should show only the highest total price per unit
-- that are greater than 1000.
CREATE VIEW BiggestPrice AS
SELECT SubjId, SUM(Max(Priceperunit)) FROM Student, Subject
GROUP BY Priceperunit
Here is my table:
CREATE TABLE Student(
StudentId char(5) not null,
StudName varchar2(50) not null,
Age NUMBER(3,0),
CONSTRAINT Student_StudentId PRIMARY KEY (StudentId)
);
CREATE table Enrollment(
EnrollmentId varchar2(10) not null,
EnrollmentDate date not null,
StudentId char(5) not null,
SubjId Number(5) not null,
constraint Enrollment_EnrollmentId primary key (EnrollmentId),
constraint Enrollment_StudentId_FK foreign key (StudentId) references Student(StudentId),
constraint Enrollment_SubjId_Fk foreign key (SubjId) references Subject(SubjId)
);
Create table Subject(
SubjId number(5,0) not null,
SubjDescription varchar2(200) not null,
Units number(3,0) not null,
Priceperunit number(9,0) not null,
Constraint Subject_SubjId_PK primary key (SubjId)
);
Since this appears to be a homework question.
You need to use JOINs. Your current query:
CREATE VIEW StudentDetails AS
SELECT StudName, EnrollmentDate
Does not have a FROM clause and the query you have for question 5 uses the legacy comma join syntax with no WHERE filter; this is the same as a CROSS JOIN and will connect every student to every subject and is not what you want.
Don't use the legacy comma join syntax and use ANSI joins and explicitly state the join condition.
SELECT <expression list>
FROM student s
INNER JOIN enrollment e ON ...
INNER JOIN subject j ON ...
Then you can fill in the ... based on the relationships between the tables (typically the primary key of one table = the foreign key of another table).
Then for the <expression list> you need to include the columns asked for in the question: student name and enrolment date and subject name would just be those columns from the appropriate tables; and total price-per-unit (which I assume is actually total-price-per-subject) would be a calculation.
Then for the last part of question 4.
who are enrolled on the subject Science or History.
Add a WHERE filter to only include rows for those subjects.
For question 5, you do not need any JOINS as the question only asks about details in the SUBJECT table.
You need to add a WHERE filter to show "only the highest total price per unit that are greater than 1000". This is a simple multiplication and then you can filter by comparing if it is > 1000.
Then you need to limit the query to return only the row with the "highest total price per unit of all the subjects". From Oracle 12, this would be done with an ORDER BY clause in descending order of total price and then using FETCH FIRST ROW ONLY or FETCH FIRST ROW WITH TIES.
Not sure if i get it fully, but i think its this :
Notes:
Always use Id's to filter records:
where su.SubjId in (1,2)
You can find max record using max() at subquery and join it with main query like this :
where su2.SubjId = su.SubjId
You cannot use alias as filter so you can filter it like:
( su.Units * su.Priceperunit ) > 1000
CREATE VIEW StudentDetails AS
select s.StudName,
e.EnrollmentDate,
su.SubjDescription,
su.Units * su.Priceperunit TotalPrice
from student s
inner join Enrollment e
on e.StudentId = s.StudentId
inner join Subject su
on su.SubjId = e.SubjId
where su.SubjId in (1,2)
CREATE VIEW BiggestPrice AS
select su.SubjId, ( su.Units * su.Priceperunit ) TotalPrice
from Subject su
where ( su.Units * su.Priceperunit ) =
(
select max(su2.Units * su2.Priceperunit)
from Subject su2
where su2.SubjId = su.SubjId
)
and ( su.Units * su.Priceperunit ) > 1000

How to count missing rows in left table after right join?

There are two tables:
Table education_data (list of countries with values by year per measured indicator).
create table education_data
(country_id int,
indicator_id int,
year date,
value float
);
Table indicators (list of all indicators):
create table indicators
(id int PRIMARY KEY,
name varchar(200),
code varchar(25)
);
I want to find the indicators for which the highest number of countries lack information entirely
i.e. max (count of missing indicators by country)
I have solved the problem in excel (by counting blanks in a pivot table by country)
pivot table with count for missing indicators by country
I haven't figured our yet the SQL query to return the same results.
I am able to return the number of missing indicators for a set country , read query below, but not for all countries.
SELECT COUNT(*)
FROM education_data AS edu
RIGHT JOIN indicators AS ind ON
edu.indicator_id = ind.id and country_id = 10
WHERE value IS NULL
GROUP BY country_id
I have tried with a cross join without success so far.
You will have to join on the contries as well, otherwise you can not tell if a contry has no entry in education_data at all:
create table countries(id serial primary key, name varchar);
create table indicators
(id int PRIMARY KEY,
name varchar(200),
code varchar(25)
);
create table education_data
(country_id int references countries,
indicator_id int references indicators,
year date,
value float
);
insert into countries values (1,'USA');
insert into countries values (2,'Norway');
insert into countries values (3,'France');
insert into indicators values (1,'foo','xxx');
insert into indicators values (2,'bar', 'yyy');
insert into education_data values(1,1,'01-01-2020',1.1);
SELECT count (c.id), i.id, i.name
FROM countries c JOIN indicators i ON (true) LEFT JOIN education_data e ON(c.id = e.country_id AND i.id = e.indicator_id)
WHERE indicator_id IS NULL
GROUP BY i.id;
count | id | name
-------+----+------
3 | 2 | bar
2 | 1 | foo
(2 rows)
I want to find the indicators for which the highest number of countries lack information entirely i.e. max (count of missing indicators by country)
That's a logical contradiction. The ...
count of missing indicators by country
.. cannot be pinned on any specific indicators, since those countries don't have an indicator.
The counts per country with "missing indicator" (i.e. indicator_id IS NULL):
SELECT country_id, count(*) AS ct_indicator_null
FROM education_data
WHERE indicator_id IS NULL
GROUP BY country_id
ORDER BY count(*) DESC;
Or, more generally, without valid indicator, which also includes rows where indicator_id has no match in table indicators:
SELECT country_id, count(*) AS ct_no_valid_indicator
FROM education_data e
WHERE NOT EXISTS (
SELECT FROM indicators i
WHERE i.id = e.indicator_id
)
GROUP BY country_id
ORDER BY count(*) DESC;
NOT EXISTS is one of four basic techniques that apply here (LEFT / RIGHT JOIN, like you tried being another one). See:
Select rows which are not present in other table
You mentioned a country table. Countries without any indicator entries in education_data are not included in the result above. To find those, too:
SELECT *
FROM country c
WHERE NOT EXISTS (
SELECT
FROM education_data e
JOIN indicators i ON i.id = e.indicator_id -- INNER JOIN this time!
WHERE e.country_id = c.id
);
Reports countries without valid indicator (none, or not valid).
If every country should have a valid indicator, after cleaning up existing data, consider:
1: adding an FOREIGN KEY constraint to disallow invalid entries in education_data.indicator_id.
2: setting education_data.indicator_id NOT NULL to also disallow NULL entries.
Or add a PRIMARY KEY on (country_id, indicator_id), which makes both columns NOT NULL automatically.
.. which brings you closer to a valid many-to-many implementation. See:
How to implement a many-to-many relationship in PostgreSQL?

Design a database and write a SQL query for score application

I am trying to design a database and write an SQL query for scoring devices based on certain criteria and options available.
Criteria here for example:
Hardware Strength
Software Security
Device OS
Options for each criteria are given in the table Master. Based on each option there is a score available.
I am trying to think of a SQL query using joins, but completely clueless as I am a JavaScript developer.
Is this even doable in a single SQL query?
You can do it straightforwardly if you tidy up your database a little:
create table phones (
id SERIAL PRIMARY KEY,
model varchar(20) NOT NULL UNIQUE,
hardware_strength INT NOT NULL,
software_security INT NOT NULL,
os INT NOT NULL
);
create table hardware_strength (
id SERIAL PRIMARY KEY,
description varchar(20) NOT NULL UNIQUE,
score INT NOT NULL
);
create table software_security (
id SERIAL PRIMARY KEY,
description varchar(20) NOT NULL UNIQUE,
score INT NOT NULL
);
create table os (
id SERIAL PRIMARY KEY,
description varchar(20) NOT NULL UNIQUE,
score INT NOT NULL
);
with this model in place, the query is simple:
SELECT
a.*,
b.score + c.score + d.score as score
FROM phones a, hardware_strength b, software_security c, os d
WHERE
a.hardware_strength = b.id AND
a.software_security = c.id AND
a.os = d.id
;
You can play with it in this sqlfiddle.
Assuming you want the sum of the scores for the final score, you can use joins. Here is one method:
select d.*,
(coalesce(mh.score, 0) + coalesce(ms.score, 0) + coalesce(md.score, 0)) as total_score
from data d left join
master mh
on mh.field = 'Hardware Strength' and
mh.option = d.hardware_strength left join
master ms
on msfield = 'Sofware Security' and
ms.option = d.software_security left join
master md
on mh.field = 'Device OS' and
mh.option = d.device_os;
The conventional way of setting up a structure with an arbitrary number of criteria would be something like the following:
create table phones (pid int identity primary key, pnam varchar(64));
insert into phones (pnam) values ('Google X'),('Samsung P');
create table props (id int, crit varchar(32),
val varchar(32));
create table scores (critsc varchar(32), valsc varchar(32), score int);
insert into props values (1,'hw','metal'),(1,'sec','high'),(1,'os','android'), (2,'hw','diamond'),(2,'sec','low'),(2,'os','windows');
insert into scores values ('hw','plastic',3),('hw','glas',5),('hw','metal',8),('hw','diamond',10),('sec','low',2),('sec','high',5),('os','windows',4),('os','java',6),('os','meego',7),('os','android',10);
select coalesce(pnam,'ALL MODELS') pnam,
coalesce(crit,'total score') crit,
case when crit>'' then max(val) else '' end val, sum(score) score
from phones
inner join props on pid=id
inner join scores on critsc=crit and valsc=val
group by pnam,crit with rollup
Output:
pnam crit val score
Google X hw metal 8
Google X os android 10
Google X sec high 5
Google X total score 23
Samsung P hw diamond 10
Samsung P os windows 4
Samsung P sec low 2
Samsung P total score 16
ALL MODELS total score 39
demo: https://rextester.com/NMKGY74929
Admittedly, this is not the shortest way of doing it, but it allows for all kinds of criteria-and-value schemes to be used without ever having to change the table structure.

Making Queries for relaional model

I have some troubles with making queries for this task. anyone please help me.
Cars(License, Brand, Year);
Employees(EID, Firstname, Lastname, Wage);
Customers(CID, Firstname, Lastname, Phone);
OfficeStaff(EID, OfficeNumber);
Own(CID, License);
Mechanic(EID, HourlyPrice);
Repairs(License, EID, PartCost, Hours);
Create SQL statements performing the following tasks:
(a) Create the OfficeStaff-table while taking into account that the OfficeNumber may not be NULL and must be in
the range [1..10].
(b) Find the name(s), i.e. firstname(s) and lastname(s), of the owner(s) of the car(s), which have been repaired for
the most times.
(c) Find the average number of hours spent (i.e. Repairs.Hours) repairing cars of brand “Opel”.
(d) Update the HourlyPrice to be 20 EUR for all mechanics with a wage of 100 EUR or more.
My tries:
(a) Create the OfficeStaff-table while taking into account that the OfficeNumber may not be NULL and must be in the range [1..10].
CREATE TABLE OfficeStaff (
EID INT PRIMARY KEY,
Firstname TEXT,
Lastname TEXT,
Wage REAL,
OfficeNumber INT NOT NULL,
CONSTRAINT CK_OfficeNumber CHECK (OfficeNumber BETWEEN 1 AND 10)
b) no idea?!
(c) Find the average number of hours spent (i.e. Repairs.Hours) repairing cars of brand “Opel”.
SELECT AVG(R.Hours)
FROM Repairs R, Cars C
WHERE R.License = C.License AND C.Brand = “Opel”
(d) Update the HourlyPrice to be 20 EUR for all mechanics with a wage of 100 EUR or more.
UPDATE Mechanic
SET HourlyPrice = 20
WHERE Wage >= 100
(a) how to create can look here
CREATE TABLE OfficeStaff (
EID INT PRIMARY KEY,
Firstname varchar(100),
Lastname varchar(100),
Wage decimal(15,2),
OfficeNumber INT NOT NULL,
CONSTRAINT CK_OfficeNumber CHECK (OfficeNumber BETWEEN 1 AND 10)
)
(b) Not sure about this one, but you have to use rank to get not only 1 value among same values. For this you can look here
WITH cte AS (
SELECT a.Firstname, a.Lastname, rank() OVER (ORDER BY count(c.Hours)) AS rnk
from Customers as a
join Own as b
on a.CID=b.CID
join Repairs as c
on b.License = c.License
group by a.Firstname, a.Lastname
)
SELECT *
FROM cte
WHERE rnk <= 1;
(c) join usage here
SELECT AVG(R.Hours)
FROM Repairs R
join Cars C
on R.license=C.license
WHERE C.Brand = 'Opel'
(d) update usage here and here
UPDATE Mechanic
SET HourlyPrice = 20
from Employees
WHERE Mechanic.EID = Employees.EID
AND Employees.Wage >= 100

SQL DML Query AVG and COUNT

I am beginner at SQL and I am trying to create a query.
I have these tables:
CREATE TABLE Hospital (
hid INT PRIMARY KEY,
name VARCHAR(127) UNIQUE,
country VARCHAR(127),
area INT
);
CREATE TABLE Doctor (
ic INT PRIMARY KEY,
name VARCHAR(127),
date_of_birth INT,
);
CREATE TABLE Work (
hid INT,
ic INT,
since INT,
FOREIGN KEY (hid) REFERENCES Hospital (hid),
FOREIGN KEY (ic) REFERENCES Doctor (ic),
PRIMARY KEY (hid,ic)
);
The query is: What is the average in each country of the number of doctors working in hospitals of that country (1st column: each country, 2nd column: average)? Thanks.
You first need to write a query that counts the doctors per hospital
select w.hid, count(w.ic)
from work w
group by w.hid;
Based on that query, you can retrieve the average number of doctors per country:
with doctor_count as (
select w.hid, count(w.ic) as cnt
from work w
group by w.hid
)
select h.country, avg(dc.cnt)
from hospital h
join doctor_count dc on h.hid = dc.hid
group by h.country;
If you have an old DBMS that does not support common table expressions the above can be rewritten as:
select h.country, avg(dc.cnt)
from hospital h
join (
select w.hid, count(w.ic) as cnt
from work
group by w.hid
) dc on h.hid = dc.hid;
Here is an SQLFiddle demo: http://sqlfiddle.com/#!12/9ff79/1
Btw: storing date_of_birth as an integer is a bad choice. You should use a real DATE column.
And work is a reserved word in SQL. You shouldn't use that for a table name.