Join Same Column from Same Table Twice - postgresql

I am new to the SQL world. I would like to replace the Games.home_team_id and Games.away_team_id with the Corresponding entry in the Teams.name column.
First I start by initializing a small table of data:
CREATE TABLE Games (id,away_team_id INT,away_team_score INT,home_team_id INT, home_team_score INT);
CREATE TABLE
INSERT INTO Games (id,away_team_id,away_team_score,home_team_id,home_team_score)
VALUES
(1,1,1,2,4),
(2,1,3,3,2),
(3,1,1,4,1),
(4,2,0,3,2),
(5,2,3,4,1),
(6,3,5,4,2)
;
INSERT 0 6
Then I create a template of a reference table
CREATE TABLE Teams (id INT, name VARCHAR(63);
CREATE TABLE
INSERT INTO Teams (id, name)
VALUES
(1, 'Oogabooga FC'),
(2, 'FC Milawnchair'),
(3, 'Ron\'s Footy United'),
(4, 'Pylon City FC')
;
INSERT 0 4
I would like to have the table displayed as such:
| id | away_team_name | away_team_score | home_team_name | home_team_score |
-----+----------------+-----------------+----------------+------------------
| 1 | Oogabooga FC | 1 | FC Milawnchair | 4 |
...
I managed to get a join query to show the first value from Teams.name in the away_team_name field using this JOIN:
SELECT
Games.id,
Teams.name AS away_team_name,
Games.away_team_score,
Teams.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams ON Teams.id = Games.away_team_id
;
| id | away_team_name | away_team_score | home_team_name | home_team_score |
-----+----------------+-----------------+----------------+------------------
| 1 | Oogabooga FC | 1 | Oogabooga FC | 4 |
...
But now I am stuck when I call it twice as a JOIN it shows the error:
SELECT
Games.id,
Teams.name AS away_team_name,
Games.away_team_score,
Teams.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams ON Teams.id = Games.away_team_id
JOIN Teams ON Teams.id = Games.home_team_id
;
ERROR: table name "teams" specified more than once
How do you reference the same reference the same column of the same table twice for a join?

You need to specify an alias for at least one of the instances of the table; preferably both.
SELECT
Games.id,
Away.name AS away_team_name,
Games.away_team_score,
Home.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams AS Away ON Away.id = Games.away_team_id
JOIN Teams AS Home ON Home.id = Games.home_team_id
Explanation: As you are joining to the same table twice, the DBMS (in your case, PostgreSQL) is unable to identify which of the tables you're referencing to when using its fields; the way to solve this is to assign an alias to the joined tables the same way you assign aliases for your columns. This way you can specify which of the joined instances are you referencing to in your SELECT, JOIN and WHERE statements.

Related

How can I ensure that a join table is referencing two tables with a composite FK, one of the two column being in common on both tables?

I have 3 tables : employee, event, and these are N-N so the 3rd table employee_event.
The trick is, they can only N-N within the same group
employee
+---------+--------------+
| id | group |
+---------+--------------+
| 1 | A |
| 2 | B |
+---------+--------------+
event
+---------+--------------+
| id | group |
+---------+--------------+
| 43 | A |
| 44 | B |
+----
employee_event
+---------+--------------+
| employee_id | event_id |
+-------------+--------------+
| 1 | 43 |
| 2 | 44 |
+---------+--------------+
So the combination employee_id=1 event_id=44 should not be possible, because employee from group A can not attend an event from group B. How can I secure my DB with this?
My first idea is to add the column employee_event.group so that I can make my two FK (composite) with employee_id + group and event_id + group respectively to the table employee and event. But is there a way to avoid adding a column in the join table for the only purpose of FKs?
Thx!
You may create a function and use it as a check constraint on table employee_event.
create or replace function groups_match (employee_id integer, event_id integer)
returns boolean language sql as
$$
select
(select group from employee where id = employee_id) =
(select group from event where id = event_id);
$$;
and then add a check constraint on table employee_event.
ALTER TABLE employee_event
ADD CONSTRAINT groups_match_check
CHECK groups_match(employee_id, event_id);
Still bear in mind that rows in employee_event that used to be valid may become invalid but still remain intact if certain changes in tables employee and event occur.

Postgres: Query for list of ids in a mapping table and create If they don't exist

Assume we have the following table whose purpose is to autogenerate a numeric id for distinct (name, location) tuples:
CREATE TABLE mapping
(
id bigserial PRIMARY KEY,
name text NOT NULL,
location text NOT NULL,
);
CREATE UNIQUE INDEX idx_name_loc on mapping(name location)
What is the most efficient way to query for a set of (name, location) tuples and autocreate any mappings that don't already exist, with all mappings (including the ones we created) being returned to the user.
My naive implementation would be something like:
SELECT id, name, location
FROM mappings
WHERE (name, location) IN ((name_1, location_1)...(name_n, location_n))
do something with the results in a programming language of may choice to work out which results are missing.
INSERT
INTO mappings (name, location)
VALUES (missing_name_1, missing_loc_1), ... (missing_name_2, missing_loc_2)
ON CONFLICT DO NOTHING
This gets the job done but I get the feeling there's probably something that can a) be done in pure sql and b) is more efficient.
You can use DISTINCT to get all possible values for the two columns, and CROSS JOIN to get their Carthesian product.
LEFT JOIN with the original table to get the actual records (if any):
CREATE TABLE mapping
( id bigserial PRIMARY KEY
, name text NOT NULL
, location text NOT NULL
, UNIQUE (name, location)
);
INSERT INTO mapping(name, location) VALUES ('Alice', 'kitchen'), ('Bob', 'bedroom' );
SELECT * FROM mapping;
SELECT n.name, l.location, m.id
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
LEFT JOIN mapping m ON m.name = n.name AND m.location = l.location
;
Results:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
(2 rows)
name | location | id
-------+----------+----
Alice | kitchen | 1
Alice | bedroom |
Bob | kitchen |
Bob | bedroom | 2
(4 rows)
And if you want to physically INSERT the missing combinations:
INSERT INTO mapping(name, location)
SELECT n.name, l.location
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
WHERE NOT EXISTS(
SELECT *
FROM mapping m
WHERE m.name = n.name AND m.location = l.location
)
;
SELECT * FROM mapping;
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
3 | Alice | bedroom
4 | Bob | kitchen
(4 rows)

Join and Concatenate rows from table into into string

I have 2 tables consider table named as fp and batch, I have to join 2 tables based on fp[primary key] of 1st table and fp_inst_id from 2nd table such that my output is :
First table all columns and 2nd table one column which is concatenated string of all the rows from join of table 1 and table 2 on fp.id and batch.fp_inst_id.
Note :
[there will be multiple fp_inst_id(of table 2) for unique ID(of table 1)]
Let me give you an example :
Created tables :
CREATE TABLE fp (
PersonID int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
);
CREATE TABLE batch (
batchID int,
fp_inst_id int,
xyz varchar(255),
abc varchar(255)
);
insert into fp values(1,'savan','nahar','abc','xyz');
insert into fp values(2,'mmm','asmd','aawd','12k3mn');
insert into batch values(1,1,'garbage1', 'abc1');
insert into batch values(2,1,'garbage2', 'abc2');
insert into batch values(3,1,'garbage3', 'abc3');
insert into batch values(4,2,'garbage9', 'abc9');
If i do normal join like this :
select * from fp join batch on fp.PersonID = batch.fp_inst_id;
What I want is :
Batch columns can be different like it's ok if it has some other delimiter of not surrounded by [] and separated on ';' or something.
What I have tried:
The same thing can be done using MYSQL using STUFF, FOR XML PATH
But it seems to be difficult in POSTGRES SQL as it doesn't support these things,
In POSTGRES SQL I tried string_agg, but it says me to group by everything
2nd thing I was trying was :
Using with clause first create the concatenated strings of table 2 group by on fp_inst_id, but in POSTGRES SQL, it allows group by on primary key(which is normal select) or it asks to use the aggregate function
I'm trying to do this in POSTGRES SQL through a query.
Thanks for the help in advance
Use array_agg to combine the batch rows and group-by to bracket the combination.
select personid,lastname,firstname,address,city,
array_agg(batch)
from fp
join batch on fp.PersonID = batch.fp_inst_id
group by personid,lastname,firstname,address,city;
eg:
jasen=# select personid,lastname,firstname,address,city,array_agg(batch) from fp join batch on fp.PersonID = batch.fp_inst_id group by 1,2,3,4,5;
personid | lastname | firstname | address | city | array_agg
----------+----------+-----------+---------+--------+---------------------------------------------------------------------
2 | mmm | asmd | aawd | 12k3mn | {"(4,2,garbage9,abc9)"}
1 | savan | nahar | abc | xyz | {"(1,1,garbage1,abc1)","(2,1,garbage2,abc2)","(3,1,garbage3,abc3)"}
here the batch column technically contains an array of tuples, but the sting representation seems acceptable.
Alternatively you can use concat_ws() to concat the values and then group by
select personid,lastname,firstname, address,city, array_agg(batch_columns) as batch_columns
from
(select fp.*, concat_ws(' / ',batch.batchid,batch.fp_inst_id, batch.xyz,batch.abc)::text as batch_columns
from fp
join batch
on fp.personid=batch.fp_inst_id)as table1
group by 1,2,3,4,5;
personid | lastname | firstname | address | city | batch_columns
----------+----------+-----------+---------+--------+---------------------------------------------------------------------------------
1 | savan | nahar | abc | xyz | {"1 / 1 / garbage1 / abc1","2 / 1 / garbage2 / abc2","3 / 1 / garbage3 / abc3"}
2 | mmm | asmd | aawd | 12k3mn | {"4 / 2 / garbage9 / abc9"}

How to use COUNT() in more that one column?

Let's say I have this 3 tables
Countries ProvOrStates MajorCities
-----+------------- -----+----------- -----+-------------
Id | CountryName Id | CId | Name Id | POSId | Name
-----+------------- -----+----------- -----+-------------
1 | USA 1 | 1 | NY 1 | 1 | NYC
How do you get something like
---------------------------------------------
CountryName | ProvinceOrState | MajorCities
| (Count) | (Count)
---------------------------------------------
USA | 50 | 200
---------------------------------------------
Canada | 10 | 57
So far, the way I see it:
Run the first SELECT COUNT (GROUP BY Countries.Id) on Countries JOIN ProvOrStates,
store the result in a table variable,
Run the second SELECT COUNT (GROUP BY Countries.Id) on ProvOrStates JOIN MajorCities,
Update the table variable based on the Countries.Id
Join the table variable with Countries table ON Countries.Id = Id of the table variable.
Is there a possibility to run just one query instead of multiple intermediary queries? I don't know if it's even feasible as I've tried with no luck.
Thanks for helping
Use sub query or derived tables and views
Basically If You You Have 3 Tables
select * from [TableOne] as T1
join
(
select T2.Column, T3.Column
from [TableTwo] as T2
join [TableThree] as T3
on T2.CondtionColumn = T3.CondtionColumn
) AS DerivedTable
on T1.DepName = DerivedTable.DepName
And when you are 100% percent sure it's working you can create a view that contains your three tables join and call it when ever you want
PS: in case of any identical column names or when you get this message
"The column 'ColumnName' was specified multiple times for 'Table'. "
You can use alias to solve this problem
This answer comes from #lotzInSpace.
SELECT ct.[CountryName], COUNT(DISTINCT p.[Id]), COUNT(DISTINCT c.[Id])
FROM dbo.[Countries] ct
LEFT JOIN dbo.[Provinces] p
ON ct.[Id] = p.[CountryId]
LEFT JOIN dbo.[Cities] c
ON p.[Id] = c.[ProvinceId]
GROUP BY ct.[CountryName]
It's working. I'm using LEFT JOIN instead of INNER JOIN because, if a country doesn't have provinces, or a province doesn't have cities, then that country or province doesn't display.
Thanks again #lotzInSpace.

Find all multipolygons from one table within another

So, I've got two tables - PLUTO (pieces of land), and NYZMA (rezoning boundaries). They look like:
pluto nyzma
id | geom name | geom
-------------------- -------------------
1 | MULTIPOLYGON(x) A | MULTIPOLYGON(a)
2 | MULTIPOLYGON(y) B | MULTIPOLYGON(b)
And I want it to spit out something like this, assuming that PLUTO record 1 is in multipolygons A and B, and PLUTO record 2 is in neither:
pluto_id | nyzma_id
-------------------
1 | [A, B]
2 |
How do I, for every PLUTO record's corresponding geometry, cycle through each NYZMA record, and print the names of any whose geometry matches?
Join the two tables using the spatial function ST_Contains. Than use GROUP BY and ARRAY_AGG in the main query:
WITH subquery AS (
SELECT pluto.id, nyzma.name
FROM pluto LEFT OUTER JOIN nyzma
ON ST_Contains(nyzma.geom, pluto.geom)
)
SELECT id, array_agg(name) FROM subquery GROUP BY id;