SQL for joining 2 tables but a bit complicated for my understanding - oracle-sqldeveloper

Got a situation thats a bit beyond my understanding.
Table A has the Product, Country and Factory
Table B has the Product, Factory and city.
The scenario is such that sales forecast data flows from the country level via the factory and then to city level. We have factories only in Rotterdam and Amsterdam. The issue is such that the factories in Table A need to be the same as the factory in table B.
I have to clean data for situations C&D where the factories in Table A are wrong and need cleaning. I therefore first need to identify these wrong records:
Here is what I got so far by joining Table A and B
select A.Prod,A.country,A.factory,B.Prod,B.factory,B.City from Table1 A, Table2 B where and A.Prod=B.Prod and A.Factory <>B.Factory
Of course I can find a specific known wrong record by using below SQL, but I need to find for all wrong records without specifying any product or
select A.Prod,A.country,A.factory,B.Prod,B.factory,B.City from Table1 A, Table2 B where A.Prod=B.Prod and A.Factory <>B.Factory
and A.Country ='Norway' and A.Factory ='Rotterdam' and B.City ='Oslo'
Situation 1
Table A
Product Country Factory
ProdA Switzerland Rotterdam
Table B
Product Factory City
ProdA Rotterdam Geneva
Situation 2
Table A
Product Country Factory
Prod Germany Rotterdam
Table B
Product Factory City
ProdB Rotterdam Dresden
Situation 3
Table A
Product Country Factory
ProdC Norway Rotterdam
Table B
Product Factory City
ProdC Amsterdam Oslo
Situation 4
Table A
Product Country Factory
ProdD Finland Rotterdam
Table B
Product Factory City
ProdD Amsterdam Helsinki

From what I understand Your projection for the country in Table A, has to be for a city in table B, which exists in the country in table A.
So, in situation 1, we have
Table A country = Switzerland, Table B city = Geneva.
Since, Geneva is in Switzerland, this is fine
In situation 2, we have
Table A country = Germany, Table B city = Dresden
Since Dresden is in Germany this is fine.
This gives us a clue on how we can attack the problem.
Step 1. Setup a table for your expected country/city
CREATE TABLE COUNTRY_CITY (COUNTRY VARCHAR(60), CITY VARCHAR(60));
STEP 2. Insert the values for expected country/city into table
INSERT INTO COUNTRY_CITY(COUNTRY,CITY) VALUES('GERMANY','DRESDEN');
INSERT INTO COUNTRY_CITY(COUNTRY,CITY) VALUES('SWITZERLAND','GENEVA');
INSERT INTO COUNTRY_CITY(COUNTRY,CITY) VALUES('NORWAY','OSLO');
INSERT INTO COUNTRY_CITY(COUNTRY,CITY) VALUES('FINLAND','HELSINKI');
STEP 3.
select A.Prod,A.country, A.factory, B.Prod, B.factory, B.City,
COUNTRY_CITY.CITY
from
Table1 A
INNER JOIN Table2 B ON A.Prod=B.Prod
INNER JOIN COUNTRY_CITY ON A.COUNTRY = COUNTRY_CITY.COUNTRY
where COUNTRY_CITY.CITY = B.city and A.Factory <> B.Factory
So in step 3, we give the database the knowledge of which city belongs to which country, so that we can do a join from Table A to table B. Once you get that, then the condition on the not matching factories should be the records that you are looking for

Related

JPA discriminator from joined table

I need to map a subset of table A to an entity. Unfortunatelly, the discriminator in an other table B.
Is it possilbe to specify such an entity with annotations so a simple select without any conditions returns only the desired subset?
Exampe:
Table Kingdom
id, name
1, animal
2, plant
Table Lifeform
id, name, Kingdom_Id
1, dog, 1
2, cat, 1
3, tree, 2
4, grass, 2
Using these tables as exmple, I would like to have an entity called Plant.
The jpql query "select p from Plant p" should produce the same result as
select * from Lifeform l join Kingdom k on l.Kingdom_Id = k.id and k.name = 'plant';
I tried to accomplish this by using #DiscriminatorColumn, #Inheritance and #DiscriminatorValue annotations. But I think it is not possible that way.

Show the subjects per StudentNo and the count of number of subjects per student

Error: Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
SELECT Subject, StudentNo, SUM(COUNT(DISTINCT Subject)) AS NumOfSubjectPerStudent
FROM Subjects AS S
INNER JOIN STUDENTS AS ST ON S.ID = ST.ID
WHERE S.ID = ST.ID
GROUP BY ST.StudentNo, S.Subject
ORDER BY ST.StudentNo DESC
I think you're almost there but without knowing the structure of the Students and Subjects tables, I can only assume it should be something like this:
SELECT ST.StudentNo, S.Subject, SUM(COUNT(DISTINCT S.Subject)) AS NumOfSubjectPerStudent
FROM Subjects AS S
INNER JOIN STUDENTS AS ST ON S.StudentId = ST.ID
GROUP BY ST.StudentNo, S.Subject
ORDER BY ST.StudentNo DESC
This assumption is based on the Subjects table having a StudentId field that links to the Students Id field.
I also am assuming that the Subjets Id field is the unique identifier/primary key for that Subject and shouldn't be used to JOIN against the Subjects ID field.
If I am wrong with my assumptions, then can you please clarify the columns in each table, and also provide an example of data in each table to better make sense of how to help you.

How to join two tables in PostgreSQL with similar columns and leave unmatching fields NULL or untouched

I have a main table Grade. I already joined this table with the Class table. Now I want to join another table, Instructor.
Grade
ClassID
AverageGrade
1
A
2
B
3
B+
Class
ID
Class
1
Math
2
English
3
History
4
Spanish
5
Science
Instructor
Class
Instructor
1
Alice
2
Bob
4
Charlie
The current query I have is
select * from Grade left join Class on Grade.ClassID = Class.ID
Is there any way I can left join Instructor on top of this query, such that my table has the following?
ID
Class
AverageGrade
Instructor
1
Math
A
Alice
2
English
B
Bob
3
History
B+
4
Spanish
Charlie
Not including 5-Science as it has neither a grade nor an instructor.
Due to other reasons, I have to join Grade first.
Thanks!
No, you can not LEFT JOIN Instructor on top of the query you have, because the first table (Grade) limits the amount of records you get in result when using left joins. You can't have NULL from Grade table in result if you are only left join'ing.
Other options:
Having to start from Grade limits some most obvious options, but I still can think of two:
Using right join to join class and then joining instructor. This way I need to filter out classes without instructors and grades in WHERE part:
select
Class.ID,
Class.Class,
Grade.AverageGrade,
Instructor.Instructor
from
Grade
right join Class on Grade.ClassID = Class.ID
left join Instructor on Class.ID = Instructor.Class
where
Grade.AverageGrade is not null
or instructor.Instructor is not null
Using full join of grade and instructor returns the records I need, so no filtering is needed later, but the class should be joined using id from either grade or instructor as one of them may be NULL.
select
Class.ID,
Class.Class,
Grade.AverageGrade,
Instructor.Instructor
from
Grade
full join Instructor on Grade.ClassID = Instructor.Class
join Class on coalesce(Grade.ClassID, Instructor.class) = Class.ID
They both return the same result.

show records that have only one matchin row in another table

I need to write a sql code that probably is very simple but I am very new to it.
I need to find all the records from one table that have matching id (but no more than one) from the other table. eg. one table contains records of the employees and the second one with employees' telephone numbers. i need to find all employees with only one telephone no
Sample data would be nice. In absence of:
SELECT
employees.employee_id
FROM
employees
LEFT JOIN
(SELECT distinct on(employee_id) employee_id FROM emp_phone) AS phone
ON
employees.employee_id = phone.employee_id
WHERE
phone.employee_id IS NOT NULL;
You need a join of the 2 tables, group by employee and the condition in the having clause:
SELECT e.employee_id, e.name
FROM employees e INNER JOIN numbers n
ON e.employee_id = n.employee_id
GROUP BY e.employee_id, e.name
HAVING COUNT(*) = 1;
If there can be more than a few numbers per employee in the table with the employees' telephone numbers (calling it tel), then it's cheaper to avoid GROUP BY and HAVING which has to process all rows. Find employees with "unique" numbers using a self-anti-join with NOT EXISTS.
While you don't need more than the employee_id and their unique phone number, you don't even have to involve the employee table at all:
SELECT *
FROM tel t
WHERE NOT EXISTS (
SELECT FROM tel
WHERE employee_id = t.employee_id
AND tel_number <> t.tel_number -- or use PK column
);
If you need additional columns from the employee table:
SELECT * -- or any columns you need
FROM (
SELECT employee_id AS id, tel_number -- or any columns you need
FROM tel t
WHERE NOT EXISTS (
SELECT FROM tel
WHERE employee_id = t.employee_id
AND tel_number <> t.tel_number -- or use PK column
)
) t
JOIN employee e USING (id);
The column alias in the subquery (employee_id AS id) is just for convenience. Then the outer join condition can be USING (id), and the ID column is only included once in the result, even with SELECT * ...
Simpler with a smart naming convention that uses employee_id for the employee ID everywhere. But it's a widespread anti-pattern to use employee.id instead.
Related:
JOIN table if condition is satisfied, else perform no join

Join 2 tables where two sets of numbers overlap within the joining columns

I need to join 2 tables with postgresql where two sets of numbers overlap within the joining columns.
The image below explains it - I am needing to take a table of congresspeople and their party affiliation and join it with a table of districts (based on when the districts were drawn or redrawn). The result will be the rows that show the dates that the district, state and congressperson were the same. Wherever there are dates of a district that are known and the congressperson dates are unknown, the dates that are known for the district are filled for that portion, and the dates for the congressperson are left blank - and vice versa.
For example, for the first rows in the tables:
Congressperson Table:
Arkansas, District 5, Republican: 1940-1945
District Table:
Arkansas, District 5: 1942-1963
Results in the following combinations (Start_Comb and End_Comb):
1940-1942
1942-1945
And for the combination where the district is unknown (1940-1942), the district dates are left blank.
The final set of date columns (gray) is simply the combinations that are only for the district (this is super easy).
In case you're wondering what this is for, I am creating an animated map, kind of like this, but for congressional districts over time:
https://www.youtube.com/watch?v=vQDyn04vtf8
I'll end up with something where there is a map where for every known district, there is a known or unknown party.
Haven't got very far, this is what I did:
SELECT *
FROM congressperson
JOIN districts
ON Start_Dist BETWEEN Start_Cong AND End_Cong
WHERE district.A = district.B
OR End_Dist BETWEEN Start_Cong AND Start_Dist
OR Start_Cong = Start_Dist OR End_Cong= End_Dist;
The idea is to make list of unique dates from both tables first. Then for each such date find next date (in this particular case dates are grouped by state, district, and next date is looked for particular state, district).
So now we have list of ranges we are looking for. Now we can join (for this paticular task left join) other tables by required conditions:
select
r.state,
c.start_cong,
c.end_cong,
c.party,
coalesce(c.district, d.district) district,
d.start_dist,
d.end_dist,
start_comb,
end_comb,
case when d.district is not null then start_comb end final_start,
case when d.district is not null then end_comb end final_end
from (
with dates as (
select
*
from (
SELECT
c.state,
c.district,
start_cong date
FROM congressperson c
union
SELECT
c.state,
c.district,
end_cong
FROM congressperson c
union
SELECT
d.state,
d.district,
start_dist
FROM district d
union
SELECT
d.state,
d.district,
end_dist
FROM district d
) DATES
group by
state,
district,
date
order by
state,
district,
date
)
select
dates.state,
dates.district,
dates.date start_comb,
(select
d.date
from
dates d
where
d.state = dates.state and
d.district = dates.district and
d.date > dates.date
order by
d.date
limit 1
) end_comb
from
dates) r
left join congressperson c on
c.state = r.state and
c.district = r.district and
start_comb between c.start_cong and c.end_cong and
end_comb between c.start_cong and c.end_cong
left join district d on
d.state = r.state and
d.district = r.district and
start_comb between d.start_dist and d.end_dist and
end_comb between d.start_dist and d.end_dist
where
end_comb is not null
order by
r.state, coalesce(c.district, d.district), start_comb, end_comb, start_cong, end_cong