Differentiate null and empty in Postgres JOIN query - sql

If I have tables like this:
t1:
id | name
----+------
1 | a
2 | b
3 | c
4 | d
t2:
id | value
----+-------
10 | xxx
20 | yyy
30 | zzz
t_join:
t1_id | t2_id
-------+-------
1 | 10
2 | 20
3 | 30
A SELECT query for t1.id=1 looks like:
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=1;
And of course returns:
id | name | value
----+------+-------
1 | a | xxx
If I do the same thing with id=4, I get nothing.
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=4;
id | name | value
----+------+-------
(0 rows)
And, if I do the same thing with a nonsense id=1234132, I also get nothing.
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=1234132;
id | name | value
----+------+-------
(0 rows)
Is there a way I can differentiate between having an empty result (id=4) vs something that's null (id=1234132)? I guess I want verification that the id I'm checking exists without a separate query. Is this possible?

How about a left join:
SELECT t1.id, t1.name, t2.value
FROM t1 LEFT JOIN
t_join
ON t1.id = t_join.t1_id LEFT JOIN
t2
ON t2.id = t_join.t2_id
WHERE t1.id = 1;
If t.id is not found, you'll get no rows. If there are no matches in t2, then you'll get a NULL value.
Also, a simple rule: Never use commas in the FROM clause. Always, always use proper, explicit JOIN syntax.

Related

Take data from two tables and show in one row without duplicates with a where condition

I want to take the data from two tables and output them in one row .
output will have two columns "to" and "from" where the condition is "from" will be having data from second table where type is true and "to" column will have data from second table where type is false . FK_ID in second table is linked to ID on the first table . Please help with the query.
I was trying to do with inner joins and union was not able to make it work . Thanks in advance .
TABLE 1
ID | PATH|
1 | ABC |
2 | EFG |
TABLE 2
ID | FK_ID | NUMBER | TYPE
20 | 1 | 123 | TRUE
21 | 1 | 456 | FALSE
28 | 2 | 888 | FALSE
29 | 2 | 939 | TRUE
OUTPUT SHOULD BE:
ID | PATH | TO | FROM
1 | ABC | 456 | 123
2 | EFG | 888 | 939
Use aggregation with pivoting logic to identify the "to" and "from" components of each path:
SELECT
t1.ID,
t1.PATH,
MAX(CASE WHEN t2.TYPE = 'FALSE' THEN t2.NUMBER END) AS "TO",
MAX(CASE WHEN t2.TYPE = 'TRUE' THEN t2.NUMBER END) AS "FROM"
FROM table1 t1
LEFT JOIN table2 t2
ON t1.ID = t2.FK_ID
GROUP BY
t1.ID,
t1.PATH
ORDER BY
t1.ID;
If performance is an issue, you might find a lateral join to be faster:
SELECT t1.*, t2.*
FROM table1 t1 LEFT JOIN LATERAL
(SELECT SUM(T2.NUMBER) FILTER (WHERE NOT t2.TYPE) as num_to,
SUM(T2.NUMBER) FILTER (WHERE t2.TYPE) as num_from
FROM table2 t2
WHERE t1.ID = t2.FK_ID
) t2
ORDER BY t1.ID;
This avoids the outer GROUP BY and probably the sorting as well (assuming that ID is the primary key).
It also assumes that TYPE is a Postgres boolean type. If not, use string comparisons for the WHERE clauses.

How to do an outer join with full result between two tables

I have two tables:
TABLE1
id_attr
-------
1
2
3
TABLE2
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
As a result I want a table that show:
RESULT
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
So I want the row with id=10 and id_attr=3 also when id_Attr=3 is missing in TABLE2 (and I know that because I have a NULL value (or something else) in the val column of RESULT.
NB: I could have others ids in table2. For example, after insert this row on table2: {11,1,A}, as RESULT I want:
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
11 | 1 | A
11 | 2 | NULL
11 | 3 | NULL
So, for every id, I want always the match with all id_attr.
Your specific example only has one id, so you can use the following:
select t2.id, t2.id_attr, t2.val
from table2 t2
union all
select 10, t1.id_attr, NULL
from table1 t1
where not exists (select 1 from table2 t2 where t2.id_attr = t1.id_attr);
EDIT:
You can get all combinations of attributes and ids in the following way. Use a cross join to create all the rows you want and then a left join to bring in the data you want:
select i.id, t1.id_attr, t2.val
from (select distinct id from table2) i cross join
table1 t1 left join
table2 t2
on t2.id = i.id and t2.id_attr = t1.id_attr;
It sounds like you want to do just an outer join on id_attr instead of id.
select * from table2 t2
left outer join table1 t1 on t2.id_attr = t1.id_attr;

Query that countes pairs with same values depending on third column

I have three columns: Team_Code, ID, times_together.
I'm trying to count how many times ID's have the same "Team_Code" and add times_together to it.
In other words- I'm trying to write all the pairs of one column, check how many times they have the same value in other raw, and add third raw to it.
The simple way to ask this question is picture so:
Values can appear twice (for example
1110 with 8888
and then
8888 with 1110).
You could self join the table on team_code and sum the times_together:
SELECT t1.id, t2.id, SUM(t1.times_together)
FROM mytable t1
JOIN mytable t2 ON t1.team_code = t2.team_code AND t1.id != t2.id
If you want to make sure each pair only appears once, you could add a condition to always take the lower id on the left:
SELECT t1.id, t2.id, SUM(t1.times_together)
FROM mytable t1
JOIN mytable t2 ON t1.team_code = t2.team_code AND t1.id < t2.id
I would suggest this self-joining SQL which takes all possible ID pairs (but only where the first is smaller than the second), and uses a CASE to sum the times_together when the persons played in the same team:
select t1.id,
t2.id,
sum(case when t1.Team_Code = t2.Team_Code
then t1.times_together
else 0
end) times_together
from t as t1
inner join t as t2
on t1.id < t2.id
group by t1.id, t2.id
order by 1, 2
Output in the example case is:
| id | id | times_together |
|------|------|----------------|
| 1028 | 1110 | 0 |
| 1028 | 2220 | 0 |
| 1028 | 8888 | 0 |
| 1110 | 2220 | 1 |
| 1110 | 8888 | 1 |
| 2220 | 8888 | 6 |

PostgreSQL LEFT OUTER JOIN query syntax

Lets say I have a table1:
id name
-------------
1 "one"
2 "two"
3 "three"
And a table2 with a foreign key to the first:
id tbl1_fk option value
-------------------------------
1 1 1 1
2 2 1 1
3 1 2 1
4 3 2 1
Now I want to have as a query result:
table1.id | table1.name | option | value
-------------------------------------
1 "one" 1 1
2 "two" 1 1
3 "three"
1 "one" 2 1
2 "two"
3 "three" 2 1
How do I achieve that?
I already tried:
SELECT
table1.id,
table1.name,
table2.option,
table2.value
FROM table1 AS table1
LEFT outer JOIN table2 AS table2 ON table1.id = table2.tbl1fk
but the result seems to omit the null vales:
1 "one" 1 1
2 "two" 1 1
1 "one" 2 1
3 "three" 2 1
SOLVED: thanks to Mahmoud Gamal: (plus the GROUP BY)
Solved with this query
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1fk
AND t1.option = t2.option
group by t1.id, t1.name, t2.option, t2.value
ORDER BY t1.id, t1.name
You have to use CROSS JOIN to get every possible combination of name from the first table with the option from the second table. Then LEFT JOIN these combination with the second table. Something like:
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1_fk
AND t1.option = t2.option
SQL Fiddle Demo
Simple version: option = group
It's not specified in the Q, but it seems like option is supposed to define a group somehow. In this case, the query can simply be:
SELECT t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(option)) AS option FROM table2) o
CROSS JOIN table1 t1
LEFT JOIN table2 t2 ON t2.option = o.option AND t2.tbl1_fk = t1.id
ORDER BY o.option, t1.id;
Or, if options are not numbered in sequence, starting with 1:
...
FROM (SELECT DISTINCT option FROM table2) o
...
Returns:
id | name | option | value
----+-------+--------+-------
1 | one | 1 | 1
2 | two | 1 | 1
3 | three | |
1 | one | 2 | 1
2 | two | |
3 | three | 2 | 1
Faster and cleaner, avoiding the big CROSS JOIN and the big GROUP BY.
You get distinct rows with a group number (grp) per set.
Requires Postgres 8.4+.
More complex: group indicated by sequence of rows
WITH t2 AS (
SELECT *, count(step OR NULL) OVER (ORDER BY id) AS grp
FROM (
SELECT *, lag(tbl1_fk, 1, 2147483647) OVER (ORDER BY id) >= tbl1_fk AS step
FROM table2
) x
)
SELECT g.grp, t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(grp)) AS grp FROM t2) g
CROSS JOIN table1 t1
LEFT JOIN t2 ON t2.grp = g.grp AND t2.tbl1_fk = t1.id
ORDER BY g.grp, t1.id;
Result:
grp | id | name | option | value
-----+----+-------+--------+-------
1 | 1 | one | 1 | 1
1 | 2 | two | 1 | 1
1 | 3 | three | |
2 | 1 | one | 2 | 1
2 | 2 | two | |
2 | 3 | three | 2 | 1
-> SQLfiddle for both.
How?
Explaining the complex version ...
Every set is started with a tbl1_fk <= the last one. I check for this with the window function lag(). To cover the corner case of the first row (no preceding row) I provide the biggest possible integer 2147483647 the default for lag().
With count() as aggregate window function I add the running count to each row, effectively forming the group number grp.
I could get a single instance for every group with:
(SELECT DISTINCT grp FROM t2) g
But it's faster to just get the maximum and employ the nifty generate_series() for the reduced CROSS JOIN.
This CROSS JOIN produces exactly the rows we need without any surplus. Avoids the need for a later GROUP BY.
LEFT JOIN t2 to that, using grp in addition to tbl1_fk to make it distinct.
Sort any way you like - which is possible now with a group number.
try this
SELECT
table1.id, table1.name, table2.option, table2.value FROM table1 AS table11
JOIN table2 AS table2 ON table1.id = table2.tbl1_fk
This is enough:
select * from table1 left join table2 on table1.id=table2.tbl1_fk ;

select from multiple tables, when one is never used

I improved my question with example tables for a better understanding
I have 3 tables with following rows:
TABLE1 t1 TABLE t2 TABLE t3
ID NAME OBS ID HW_VER ID SERIAL
----------------- ----------- ------------
1 | Name1 | Obs1 1 | HWVer1 5 | Serial5
2 | Name2 | Obs2 2 | HWVer2 6 | Serial6
3 | Name3 | Obs3 3 | HWVer3 7 | Serial7
4 | Name4 | Obs4
5 | Name5 | Obs5
6 | Name6 | Obs6
7 | Name7 | Obs7
Now, I want to select the id, name and obs when 2 conditions are fulfilled:
the id is present in t2 or t3 (never in both);
it refers to either t2 or t3 attributes (eg. t2.HW_VER='HWVER1'), never on both
I did something like this, but it's wrong:
SELECT DISTINCT t1.id, t1.name, t1.obs
FROM table1 t1, table2 t2, table3 t3
WHERE t1.id IN (t2.id, t3.id) AND t3.serial='Serial6';
I cannot use unions, external tables or views for this.
Please let me know in case of further questions.
Thanks a lot for your answers, I really appreciate your time..
You need to select from T2 OR T3 but never both? I think you want something like this
select count(*)
from t1
where exists (
select 'x'
from t2
where MyPrimaryKey_Name = 'random_name'
and t2.id = t1.id
)
or exists (
select 'x'
from t3
where MyPrimaryKey_Name = 'random_name'
and t3.id = t1.id
)