sql opposite of join - sql

I have two tables which are combined via a MAP table
Table ANIMAL:
+------+--------------+
| id | description |
+------+--------------+
| 2 | Ape |
| 3 | Lion |
+------+--------------+
Table MAP:
+-----------+---------+
| animal_id | legs_id |
+-----------+---------+
| 2 | 11 |
+-----------+---------+
Table LEGS:
+------+--------------+
| id | legs |
+------+--------------+
| 10 | 4 |
| 11 | 2 |
+------+--------------+
I need the animals that have no map entry in the LEGS table, something like this:
!(select *
from ANIMAL as a
JOIN MAP as m ON (a.id = m.animal_id)
JOIN LEGS as l ON (m.legs_id = l.id) )
which should give me 'Lion' as result

use LEFT JOIN
SELECT a.*
FROM animal a
LEFT JOIN Map b
On a.id = b.animal_id
WHERE b.animal_id IS NULL
SQLFiddle Demo
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins

Try this :
SELECT a.*
FROM animal a
WHERE a.id NOT IN (SELECT animal_id FROM Map m JOIN Legs l
ON m.legs_id = l.id)

Select * from Animal A
left join Map M on A.id=M.animal_id
where M.animal_id is null;

Could you not do a simple query to return all animals with no associated Map record... i.e.
SELECT
*
FROM
Animal
WHERE
animal_id not in (SELECT animal_id FROM Map)

Related

Join two tables with no relation postgres?

I have this statement which you can see
SELECT t1.*, t2.* FROM
(SELECT m.* FROM microposts AS m) AS t1
FULL JOIN
(SELECT r.* FROM ratings AS r) AS t2
ON true
I am using Rails and connecting to the database raw, but the output removes duplicate named columns eg user_id etc from the second table and is still giving results in the second table in regards to the first even though there is no relation. Eg
+------+-----------+-------+--------+
| m.id | m.content | r.id | rating |
+------+-----------+-------+--------+
| 1 | "hello" | 10 | 5 |
+------+-----------+-------+--------+
There is no relation between table m and r
I would like A output of something like this
+------+-----------+------+---------+
| m.id | m.content | r.id | rating |
+------+-----------+------+---------+
| 1 | "hello" | null | null |
| null | null | 5 | 4 |
| 2 | "gday" | null | null |
+------+-----------+------+---------+
....................... etc
This is rather exotic way to say UNION ALL
SELECT t1.*, t2.*
FROM
(SELECT m.* FROM microposts AS m) AS t1
FULL JOIN
(SELECT r.* FROM ratings AS r) AS t2
ON false
Contrary, ON true will create a cartesian product.

PostgreSQL aggregate union, intersection and set differences

I have a table of pairs to aggregate as follows:
+---------+----------+
| left_id | right_id |
+---------+----------+
| a | b |
+---------+----------+
| a | c |
+---------+----------+
And a table of values as so:
+----+-------+
| id | value |
+----+-------+
| a | 1 |
+----+-------+
| a | 2 |
+----+-------+
| a | 3 |
+----+-------+
| b | 1 |
+----+-------+
| b | 4 |
+----+-------+
| b | 5 |
+----+-------+
| c | 1 |
+----+-------+
| c | 2 |
+----+-------+
| c | 3 |
+----+-------+
| c | 4 |
+----+-------+
For each pair, I would like to calculate the length of the union, intersection and set differences (each way) comparing the values, so that the output would look like this:
+---------+----------+-------+--------------+-----------+------------+
| left_id | right_id | union | intersection | left_diff | right_diff |
+---------+----------+-------+--------------+-----------+------------+
| a | b | 5 | 1 | 2 | 2 |
+---------+----------+-------+--------------+-----------+------------+
| a | c | 4 | 3 | 0 | 1 |
+---------+----------+-------+--------------+-----------+------------+
What would be the best way to approach this using PostgreSQL?
UPDATE: here is a rextester link with data https://rextester.com/RWID9864
You need scalar sub-queries that do that.
The UNION can also be expressed by an OR which makes that query somewhat shorter to write. But for the intersection you need a query that is a bit longer.
To calculate the "diff", use the except operator:
SELECT p.*,
(select count(distinct value) from values where id in (p.left_id, p.right_id)) as "union",
(select count(*)
from (
select v.value from values v where id = p.left_id
intersect
select v.value from values v where id = p.right_id
) t) as intersection,
(select count(*)
from (
select v.value from values v where id = p.left_id
except
select v.value from values v where id = p.right_id
) t) as left_diff,
(select count(*)
from (
select v.value from values v where id = p.right_id
except
select v.value from values v where id = p.left_id
) t) as right_diff
from pairs p
I don't know what causes your slowness, as I cannot see table sizes and/or explain plans. Presuming both tables are large enough to make nested loops inefficient and to not dare thinking about joining values to itself, I'd try to rewrite it free from scalar subqueries like this:
select p.*,
coalesce(stats."union", 0) "union",
coalesce(stats.intersection, 0) intersection,
coalesce(stats.left_cnt - stats.intersection, 0) left_diff,
coalesce(stats.right_cnt - stats.intersection, 0) right_diff
from pairs p
left join (
select left_id,
right_id,
count(*) "union",
count(has_left and has_right) intersection,
count(has_left) left_cnt,
count(has_right) right_cnt
from (
select p.*,
v."value" the_value,
true has_left
from pairs p
join "values" v on v.id = p.left_id
) l
full join (
select p.*,
v."value" the_value,
true has_right
from pairs p
join "values" v on v.id = p.right_id
) r using(left_id, right_id, the_value)
group by left_id,
right_id
) stats on p.left_id = stats.left_id
and p.right_id = stats.right_id;
Each join condition here allows hash and/or merge join, so the planner will have a chance to avoid nested loops.

SQL Server join where not exist on other table

+-------------------+ +-------------------+ +---------------------+
| Service | | Asset | | AssetService |
+-------------------+ +-------------------+ +---------------------+
| Id | Name | | Id | Name | | AssetId | ServiceId |
|-------------------| |-------------------| |---------------------|
| 1 | Service 1 | | 1 | Asset 1 | | 1 | 1 |
| 2 | Service 2 | | 2 | Asset 2 | | 1 | 2 |
| 3 | Service 3 | | 3 | Asset 3 | | 2 | 2 |
+-------------------+ +-------------------+ | 2 | 3 |
+---------------------+
So I have these tables. I want to get the Services that is not on AssetService where AssetId = 1
Like this:
+-------------------+
| Service |
| Id | Name |
+-------------------+
| 3 | Service 3 |
+-------------------+
Is this possible with just inner/left/right join? because I already tried different combinations of inner join but it's not working, like this inner join Asset a on a.Id != as.AssetId. I event tried left and right join.
Can somebody help me?
Thanks.
You can you use an intelligent left join to return non-matching rows only from left table(Service)
SELECT S.Id, S.Name FROM [Service] S
LEFT JOIN ServiceAsset SA
ON S.Id = SA.ServiceId
WHERE SA.ServiceId IS NULL
Note: INNER JOIN returns the matching rows whereas you want the non matching rows then use LEFT JOIN instead
The simplest I can think of:
select * from Service
where Id not in (
select ServiceId
from AssetService
where AssetId = 1);
SQLFiddle link
I don't think it's possible using inner join, because that would only retrieve records that match some criteria and you are looking for records that do not match.
It is, however, possible to do it with left join as Ctznkane525 shows in his answer.
Edit
As jarlh pointed out in the comments, not in might lead to surprising results when there are nulls in the subquery. So, here is the not exists version:
select Id, Name
from Service s
where not exists (
select *
from AssetService a
where AssetId = 1
and ServiceId = s.Id);
SQLFiddle link
Try this:
select * from Service where Id not in (
select ServiceId from AssetService where AssetId = 1
-- we have to filter out NULLs, in case of NULL values query result will be empty
and ServiceId not null
)
It doesn't require any join.
Here is solution with join:
select Id, Name from Service
except
select S.Id, S.Name from Service S join AssetService [AS] on S.Id = [AS].ServiceId
where [AS].AssetId = 1

Conditional SQL Join from several tables

I have three tables that are connected. I need to make a single SQL selection and I know I need to use join of some form but not quite how. I only want the objectID to be selected if the session for userID has access to that object´s area (user gets access if their company has access to the area) The tables I have are:
+----------+--------+ Objects
| objectID | areaID |
+----------+--------+
| 1 | 2 |
| 2 | 2 |
| 3 | 3 |
+----------+--------+
+-----------+--------+ Users
| companyID | userID |
+-----------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+-----------+--------+
+-----------+--------+ Access
| companyID | areaID |
+-----------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+-----------+--------+
Selected UserID and corresponding areaIDs in inner query using companyID. Then using this, selected distinct ObjectID from Objects table using inner join again with table formed through inner query
Try this query:-
Select distinct a.objectID
from
Objects a
inner join
(
Select a.*,b.areaID
from
Users a
inner join
Access b
on a.companyID=b.companyID
) b
on a.areaID=b.areaID
Where b.UserID= 1;
If I understand you right, it seems like two INNER JOIN functions would get the results you're looking for:
WITH ONE AS (
SELECT a.companyID, a.userID, b.areaID
FROM Users a
INNER JOIN Access b
ON a.companyID = b.companyID
)
SELECT a.companyID, a.userID, a.areaID, b.objectID
FROM ONE a
INNER JOIN Objects b
ON a.areaID = b.areaID;
EDIT: Reply to comment
If you want to limit the results to a specific userID value, you'll need a WHERE clause:
WITH ONE AS (
SELECT a.companyID, a.userID, b.areaID
FROM Users a
INNER JOIN Access b
ON a.companyID = b.companyID
)
SELECT a.companyID, a.userID, a.areaID, b.objectID
FROM ONE a
INNER JOIN Objects b
ON a.areaID = b.areaID
WHERE a.userID = ${specific_user_id};

Three table join where middle table has duplicate foreign keys

I am working with a database with a structure similar to the illustration below (except with more columns). Basically, each person has a unique person_id and alt_id. However, the only thing connecting table A to table C is table B, and table B has one to many rows for each person/alt_id.
I need to get rows with a person_id, their alt id and their associated shapes.
I could do this:
SELECT DISTINCT a.person_id, a.color, b.alt_id, c.shape
FROM a
JOIN b ON a.person_id = b.person_id
JOIN c ON b.alt_id = c.alt_id
However, that seems inefficient as it will take a Cartesian product of rows from B and C with the same alt_id before finally using DISTINCT to narrow the results down. What's the best/most efficient way to do this query?
Table A
+-----------+-------+
| person_id | color |
+-----------+-------+
| 10 | red |
| 11 | blue |
| 12 | green |
+-----------+-------+
Table B
+-----------+--------+
| person_id | alt_id |
+-----------+--------+
| 10 | 225 |
| 10 | 225 |
| 11 | 226 |
| 11 | 226 |
| 11 | 226 |
| 12 | 227 |
+-----------+--------+
Table C
+--------+----------+
| alt_id | shape |
+--------+----------+
| 225 | square |
| 226 | circle |
| 226 | rhombus |
| 226 | ellipse |
| 227 | triangle |
+--------+----------+
Join to (select distinct * from b) b rather than just the base table b.
SELECT
a.person_id, a.color, b.alt_id, c.shape
FROM
a
INNER JOIN (select distinct * from b) b
ON a.person_id = b.person_id
INNER JOIN c
ON b.alt_id = c.alt_id
You can get a distinct list of values from b before you do your joins.
SELECT DISTINCT a.person_id, a.color, b.alt_id, c.shape
FROM a
JOIN (Select Distinct person_id, alt_id from b) b ON a.person_id = b.person_id
JOIN c ON b.alt_id = c.alt_id
Note that because of indexes, and statistics, getting a DISTINCT list is not always a good idea. Look at the actual execution plan to evaluate how good this is, especially if you have a lot of data.
You could use aggregation along with a common table expression (or subquery, but a CTE might be neater):
WITH ab AS (
SELECT a.person_id, a.color, MAX(b.alt_id) AS alt_id
FROM a INNER JOIN b
ON a.person_id = b.person_id
GROUP BY a.person_id, a.color
)
SELECT ab.person_id, ab.color, ab.alt_id, c.shape
FROM ab INNER JOIN c ON ab.alt_id = c.alt_id;