sql join problem - sql

hy!
I have 2 tables. The structure is the above:
students
idStudent name age job code
1 john 18 student 1
2 john 19 programmer 1
3 john 18 developer 2
4 mark 18 student 3
5 mark 19 programmer 1
infos
id address tel code
1 fdsf 00232 1
2 gffdfd 322 1
3 dsdd 1833 2
4 gffg 43333 3
5 fff 1933 1
I want to obtain a table with the structure:
idStudent name age address tel code
1 john 18 1
3 john 18 2
4 mark 18 3
So what I want to do is to obtain a table with unique code and all the other fields to be filled with the first or last (it has no importance for me) data which appear in the table. I don`t want to have duplicated code fields, this is important for me.

select S.IdStudent, MIN(Name) Name, MIN(Age) Age, MIN(Address) Address, MIN(Tel) Tel, MIN(Code) Code
FROM Students S Inner Join Info I ON S.IDStudent = I.Id
group by S.IdStudent

If all you want is to obtain the info in each table for each student, this is a simple JOIN Query:
SELECT e.idStudent, e.name, e.age, i.address, i.tel, e.code
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
If you have duplicated information, you can use a GROUP BY clausule to obtain the info grouped and MIN or MAX to obtain the values of each field you don't group.
EDITED to take in account the comment from user599977:
From your comment I imagine you have duplicated rows in info?, if that's the case you can do something like:
SELECT e.idStudent, max(e.name), max(e.age), max(i.address), max(i.tel), max(e.code)
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
GROUP BY e.idStudent
But I will recommend you to filter for some more predictable field, like last introduced record or something similar.
For example:
SELECT e.idStudent, e.name, e.age, i.address, i.tel, e.code
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
WHERE i.loadDate= (
SELECT max(loadDate)
FROM infos
WHERE id=e.idStudent
)
Assuming you have some column with a timestamp or some data that allows you to determine which row is the last that the user entered.

Related

PostgreSQL: how to delete duplicated rows grouped by the value of a column?

Given the following table, I need to delete every row corresponding to a certain "id" whenever all these rows are duplicated in a successive "id". Note that the deletion all rows for a specific "id" should happen only in case that every row between the two ids match (with the exception of the different "id" column).
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
2
Ann
Maths
9
2
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
For this specific input, the updated output table should be:
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
This because all records between id 1 and 2 are the exactly the same. This doesn't apply for "id" 1 and 3, as long as there's at least one row not in common between the two (id 1 has 8 in History while id 3 has 7 in the same subject).
So it is not as simple as deleting duplicated rows. Here's my attempt:
DELETE FROM table a
USING table b
WHERE a.name = b.name
AND a.subject = b.subject
AND a.score = b.score
AND a.ID < b.ID;
Can you help me?
You can first get all ids that shouldn't be deleted and then exclude them in the WHERE clause of the DELETE statement.
Step 1. In order to match unique ids that are not repeated for all rows, you can use PostgreSQL DISTINCT ON construct, that will allows you to get every row that is not duplicated on the fields "name", "subject", "score". Then retrieve these ids only once with a simple DISTINCT.
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
Step 2. Hence you can build the DELETE statement using the NOT IN operator inside the WHERE clause:
DELETE FROM tab
WHERE id NOT IN (
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
);
Check the demo here.

Combine 3 tables and get name according to average scores

I have a question about sql.
I have 3 tables.
Table 1 is student. It has student id and student name
Table 2 is school. It has school id and school name
Table 3 is scores. It has school id, student id and scores
I am trying to write a query where you select school name, if average of the scores for that school is above 70.
The SCORES table looks like this. I know, same student goes to 2 schools sounds stupid. Ignore that logic
SCORES
STUDENT_ID SCHOOL_ID SCORE
1 4 90
1 7 67
3 5 87
3 4 78
5 3 56
6 4 95
You can aggregate and filter with a having clause. If you just want the id of the school, you can get the result you want by looking at the scores table only:
select school_id, avg(scores) avg_score
from scores
group by school_id
having avg(scores) > 70
If you want the shool name, then use a join:
select sh.school_id, sh.school_name, avg(sc.scores) avg_score
from schools sh
inner join scores sc on sc.school_id = sh.school_id
group by sh.school_id, sh.school_name
having avg(sc.scores) > 70

Need to find the count of user who belongs to different depts

I have table with dept,user and so on, I need to find the number of count of user that belongs to different combinations of the dept.
Lets consider I've a table like this:
dept user
1 33
1 33
1 45
2 11
2 12
3 33
3 15
Then I've to find the uniq user and dept combination: something like this:
select distinct dept,user from x;
Which will give me result like :
Dept user
1 33
1 45
2 11
2 12
3 33
3 15
which actually removes the duplicates of the combination:
And here's the thing which i need to do :
My output should look like this:
dep_1_1 dep_1_2 dep_1_3 dep_2_2 dep_2_1 dep_2_3 Dep_3_1 Dep_3_2 Dep_3_3
2 0 1 2 0 0 1 0 2
So, Basically I need to find the count of common users between all the combinations of departments
Thanks for the help
You can get a row for each department combination using a self-join of your Distinct Select:
with cte as
(
select distinct dept,user from x
)
select t1.dept, t2.dept, count(*)
from cte a st1 join cte as t2
on t1.user = t2.user -- same user
and t1.dept < t2.dept -- different department
group by t1.dept, t2.dept
order by t1.dept, t2.dept

How to count entries in sql "like in a loop"

First thing, I'm very new to databases so this is probably a very simple question, but I didn't know how to google it or how to give it a good title. I'm using postgres from python, but the problem is to put together the right query.
The scenario is the following. I hava a table with columns: ID, Trial, Subject, Invalid. It comes from a behavioral experiment where many subjects perform a task that is composed of several trials. Their responses can be invalid for different reasons, and depending on the reason there is a different invalidation code (an integer). A valid response has code 0.
------------------------------
ID | SUBJECT | TRIAL | INVALID
------------------------------
1 Peter 1 0
2 Peter 2 0
3 Peter 3 1
4 Peter 4 3
5 Mary 1 3
6 Mary 2 2
7 Mary 3 0
8 Mary 4 2
I would like to do two things (which I'm not sure how to do in an elegant way).
a) For each subject, I would like to know how many responses are in total and how many are valid. Now I'm making a query for each subjects, with the condition, e.g., WHERE Subject='Peter', but I can imagine that there is a more elegant solution.
Sample answer:
Subject Valids Total
Peter 2 4
Mary 1 4
b) For each subjects, I would like to know how many responses were invalid for each of the invalidation codes. Ideally I would get a table like:
Subject Invalid Count
Peter 0 2
Peter 1 1
Peter 2 0
Peter 3 1
Mary 0 1
Mary 1 0
Mary 2 2
Mary 3 1
Query #1: You want one result row per subject, so you group by subject. Use COUNT to count all records for a subject and COUNT in combination with CASE to count conditionally (all valid ones).
select
subject,
count(*) as all_responses,
count(case when invalid = 0 then 1 end) as valid_responses
from mytable
group by subject;
Query #2: Here you want one result row per subject and code, so you group by these two. Then count with COUNT.
select
subject,
invalid,
count(*) as responses
from mytable
group by subject, invalid;
UPDATE: In your updated request you want query #2 to show all subject/code combinations even if they have a count of 0. In order to do this, you'd have to create the set of all valid combinations first and then outer join your response table:
select
s.subject,
c.code,
count(m.invalid) as responses
from subjects s
cross join codes c
left join mytable m on (m.subject = subjects.subject and m.invalid = codes.code)
group by s.subject, c.code;
If you don't have tables for subjects and code (which you should), you can get them from your responses table instead:
select
s.subject,
c.code,
count(m.invalid) as responses
from (select distinct subject from mytable) s
cross join (select distinct invalid as code from mytable) c
left join mytable m on (m.subject = subjects.subject and m.invalid = codes.code)
group by s.subject, c.code;

Cross Join for Missing table (Select all and Select or Insert the Missing Row Only)

I have two table and have to fill in a list of missing values in one of the table based on the other one. First table has student's information and the second table has Grade related info, Grade and Grade description.
Table One
ID Name yearWithUs Grade Course Level
1 Jim 2004 4 4
2 Jim 2004 4 1
2 Jim 2003 3 3
4 Jim 2002 2 3
4 Jim 2002 2 1
3 Jim 2001 1 2
3 Jim 2001 1 1
Table two -- logic is.. A Student in a higher Course Level can change to a lower Course Level at anytime during the semester. And It can only go downward 1 level at a time. Example: Jim in his second grade first was assigned to attend course in level 3. He need to attend course in level 2 first before he can attend course in level 1. Means. Row for course level 2 at jim's first grade is missing.
Table Two
ID Grade Grade_Desc Course Level Course Desc
1 1 First Grade 1 Basic
2 1 First Grade 2 Normal
3 1 First Grade 3 Hard
4 1 First Grade 4 Expert
5 2 Second Grade 1
6 2 Second Grade 2
7 2 Second Grade 3
8 2 Second Grade 4
. . .
. . .
. . .
Logic of Table Two
ID Grade Grade_Desc Course Level Possible Move
1 1 First Grade 1 Null
2 1 First Grade 2 1
3 1 First Grade 3 2
4 1 First Grade 4 3
Ouptput one ... how to use select statement to return Jim's Grade?
ID Name Grade_Desc Grade yerWithUs Course Level
1 Jim Fourth Grade 4 2004 4
2 Jim Fourth Grade 4 2004 3
3 Jim Fourth Grade 4 2004 2
4 Jim Fourth Grade 4 2004 1
5 Jim Third Grade 3 2003 3
6 Jim Second Grade 2 2002 3
7 Jim Second Grade 2 2002 2
8 Jim Second Grade 2 2002 1
9 Jim First Grade 2 2001 2
10 Jim First Grade 2 2001 1
Output Two..How to retrieve only the missing row into a new temp table?
ID Name Grade_Desc Grade yearWithUs Course Level
2 Jim Fourth Grade 4 2004 3
3 Jim Fourth Grade 4 2004 2
7 Jim Second Grade 2 2002 2
I am currently is using a messy Cursor Statement to do it. The structure looks really messy and hard to debug return errors. I did a lot of research, and saw people use Cross Join to fill the missing portion which looks really clean (See example below)... I have tried the script it myself in many different way by using the cross join example below...obviously, I failed. I found a similar question in stackoverflow..but I am not able to understand how does it work and why without looking at the data....I need help to understand how to use cross join to rerun missing row? and I am open to any other possible solution.
"SELECT calendar.Date,
Category.Cat,
Score = ISNULL(Scores.Score, 0)
FROM Calendar
CROSS JOIN Catogory
LEFT JOIN Scores
ON Scores.Cat = Category.Cat
AND Scores.Date = Calendar.Date
WHERE Calendar.DayOfMonth = 1;"
Inserting missing rows with a join
Thank You
This will produce that output:
select distinct name, grade, Grade_Desc
from one
cross join two
If select is all you want then:
Select row_number() over(order by (select 1)) as id, * from
(Select distinct name from t1)t1
cross join t2
Here is fiddle http://sqlfiddle.com/#!6/a8a42/3
Try this out:
Create #Temp
DECLARE #Name VARCHAR(100) = 'Jim'
SELECT ROW_NUMBER() OVER (ORDER BY B.Grade DESC,B.CourseLevel DESC) ID,
A.Name,
B.Grade_Desc,
B.Grade,
A.YearWithUs,
B.[Course Level]
INTO #temp
FROM
(
SELECT DISTINCT Name,YearWithUs,Grade
FROM TableOne
WHERE Name = #Name
) A
INNER JOIN TableTwo B
ON A.Grade = B.Grade
Output One
SELECT *
FROM #temp
Output Two into #OutputTwo(temp table)
SELECT A.* INTO #OutputTwo
FROM #temp A
LEFT JOIN TableOne B
ON A.Grade = B.Grade
AND A.[Course Level] = B.[Course Level]
WHERE A.Grade IS NULL AND A.[Course Level] IS NULL