Thread: combination join against multiple tables
Hi
I have a query involving multiple tables that I would like to return in a single query. That means returning multiple sets of the data from the first base table, but that's acceptable for the simplicity in grabbing all the data in one hit.
CREATE TABLE t1 (a int, b int, c int);
CREATE TABLE t2 (a int, b int, c int);
CREATE TABLE base (a int);
INSERT INTO t1 (a, b, c) VALUES (1, 1, 111), (1,2,112), (2,1,121), (4,1,141);
INSERT INTO t2 (a, b, c) VALUES (1, 1, 211), (2, 2, 222), (5,3,253);
INSERT INTO base(a) SELECT * FROM GENERATE_SERIES(1,10);
1 | 111 | 211
1 | 112 |
2 | 121 |
2 | | 222
3 | |
4 | 141 |
5 | | 253
6 | |
7 | |
8 | |
9 | |
10 | |
At the moment I'm doing
SELECT base.a, t1.c, t2.c
FROM base
I have a query involving multiple tables that I would like to return in a single query. That means returning multiple sets of the data from the first base table, but that's acceptable for the simplicity in grabbing all the data in one hit.
An example set:
CREATE TABLE t1 (a int, b int, c int);
CREATE TABLE t2 (a int, b int, c int);
CREATE TABLE base (a int);
INSERT INTO t1 (a, b, c) VALUES (1, 1, 111), (1,2,112), (2,1,121), (4,1,141);
INSERT INTO t2 (a, b, c) VALUES (1, 1, 211), (2, 2, 222), (5,3,253);
INSERT INTO base(a) SELECT * FROM GENERATE_SERIES(1,10);
Now the problem is that I would like to return all the rows from a, but with a single row where t2.b and t1.b match.
So the results I would like:
a | c | c
----+-----+-----So the results I would like:
a | c | c
1 | 111 | 211
1 | 112 |
2 | 121 |
2 | | 222
3 | |
4 | 141 |
5 | | 253
6 | |
7 | |
8 | |
9 | |
10 | |
At the moment I'm doing
SELECT base.a, t1.c, t2.c
FROM base
CROSS JOIN (SELECT b FROM t1 UNION SELECT b FROM t2 UNION SELECT -1) tmpset
LEFT JOIN t1 ON t1.a=base.a AND t1.b=tmpset.b
LEFT JOIN t2 ON t2.a=base.a AND t2.b=tmpset.b
WHERE t1.a IS NOT NULL
LEFT JOIN t1 ON t1.a=base.a AND t1.b=tmpset.b
LEFT JOIN t2 ON t2.a=base.a AND t2.b=tmpset.b
WHERE t1.a IS NOT NULL
OR t2.a IS NOT NULL
OR (tmpset.b=-1
AND NOT EXISTS (SELECT FROM t1 WHERE t1.a=base.a)
AND NOT EXISTS (SELECT FROM t2 WHERE t2.a=base.a)
);
but this seems like a really convoluted way to do it.
Is there a join style that will return the set I want without the pain?
but this seems like a really convoluted way to do it.
Is there a join style that will return the set I want without the pain?
I should be clear that the real world data is much more complex than this, but it describes the basic problem.
Thanks
Geoff
On Friday, January 31, 2020, Geoff Winkless <pgsqladmin@geoff.dj> wrote:
Now the problem is that I would like to return all the rows from a, but with a single row where t2.b and t1.b match.
So, the final,number of rows for each “a” is the larger row count of “b” and “c” having the same “a”. Furthermore for the first “n” rows “b” and “c” should be paired together by position. The smaller count column just gets nulls for the extra rows.
Probably the easiest way is to combine the matches for “b” and “c” into arrays the jointly unnest those arrays in the final result - with in the select list or maybe as part,of a lateral join, not sure without experimentation.
Otherwise you can add “row_number” to “b” and “c” and then left join on (a, row_number).
David J.
On Fri, 31 Jan 2020 at 15:25, David G. Johnston <david.g.johnston@gmail.com> wrote:
On Friday, January 31, 2020, Geoff Winkless <pgsqladmin@geoff.dj> wrote:Now the problem is that I would like to return all the rows from a, but with a single row where t2.b and t1.b match.So, the final,number of rows for each “a” is the larger row count of “b” and “c” having the same “a”. Furthermore for the first “n” rows “b” and “c” should be paired together by position. The smaller count column just gets nulls for the extra rows.Probably the easiest way is to combine the matches for “b” and “c” into arrays the jointly unnest those arrays in the final result - with in the select list or maybe as part,of a lateral join, not sure without experimentation.Otherwise you can add “row_number” to “b” and “c” and then left join on (a, row_number).
Thanks for the reply. Using array() hadn't occurred to me, I'll look at that.
I actually came up with this:
SELECT base.a, t1.c, t2.c
FROM base
LEFT JOIN (t1 FULL OUTER JOIN t2 ON t1.b=t2.b AND t1.a=t2.a)
ON COALESCE(t1.a, base.a)=base.a AND COALESCE(t2.a, base.a)=base.a;
which does solve the described problem; sadly I realise that I'd oversimplified my question: I haven't fully described the problem because in reality "t2" is joined to "base" with a different field, and I can't seem to get the join to do what I want without joining them together like this.
Geoff
On Fri, 31 Jan 2020 14:01:17 +0000 Geoff Winkless <pgsqladmin@geoff.dj> wrote: > Hi > > I have a query involving multiple tables that I would like to return > in a single query. That means returning multiple sets of the data > from the first base table, but that's acceptable for the simplicity > in grabbing all the data in one hit. > > An example set: > > CREATE TABLE t1 (a int, b int, c int); > CREATE TABLE t2 (a int, b int, c int); > CREATE TABLE base (a int); > INSERT INTO t1 (a, b, c) VALUES (1, 1, 111), (1,2,112), (2,1,121), > (4,1,141); > INSERT INTO t2 (a, b, c) VALUES (1, 1, 211), (2, 2, 222), (5,3,253); > INSERT INTO base(a) SELECT * FROM GENERATE_SERIES(1,10); > > Now the problem is that I would like to return all the rows from a, > but with a single row where t2.b and t1.b match. > > So the results I would like: > > a | c | c > ----+-----+----- > 1 | 111 | 211 > 1 | 112 | > 2 | 121 | > 2 | | 222 > 3 | | > 4 | 141 | > 5 | | 253 > 6 | | > 7 | | > 8 | | > 9 | | > 10 | | > > At the moment I'm doing > > SELECT base.a, t1.c, t2.c > FROM base > CROSS JOIN (SELECT b FROM t1 UNION SELECT b FROM t2 UNION SELECT -1) > tmpset LEFT JOIN t1 ON t1.a=base.a AND t1.b=tmpset.b > LEFT JOIN t2 ON t2.a=base.a AND t2.b=tmpset.b > WHERE t1.a IS NOT NULL > OR t2.a IS NOT NULL > OR (tmpset.b=-1 > AND NOT EXISTS (SELECT FROM t1 WHERE t1.a=base.a) > AND NOT EXISTS (SELECT FROM t2 WHERE t2.a=base.a) > ); > > > but this seems like a really convoluted way to do it. > > Is there a join style that will return the set I want without the > pain? > > I should be clear that the real world data is much more complex than > this, but it describes the basic problem. > > Thanks > > Geoff The join on T1 & t2 seems to just be a full outer join of t1 & t2 on a & b. Note that you cannot have two columns in the output with the same name (e.g., a+c+c, above, is not a valid table). Call them "c1" & "c2": A full outer join of t1 & t2 on a & b seems to give you all of the necessary combinations of c necessary; at which point an outer join on a associates base values with anything that mathes on a: select distinct base.a , z.c1 , z.c2 from base left join ( select distinct t1.a , t1.c "c1" , t2.c "c2" from t1 full outer join t2 on t1.a = t2.a and t1.b = t2.b ) z on base.a = z.a ; No idea what the real data looks like, but distinct likely to be helpful if real t's have more than three cols. -- Steven Lembark 3646 Flora Place Workhorse Computing St. Louis, MO 63110 lembark@wrkhors.com +1 888 359 3508
On Fri, 31 Jan 2020 14:01:17 +0000 Geoff Winkless <pgsqladmin@geoff.dj> wrote: > a | c | c > ----+-----+----- > 1 | 111 | 211 > 1 | 112 | > 2 | 121 | > 2 | | 222 > 3 | | > 4 | 141 | > 5 | | 253 > 6 | | > 7 | | > 8 | | > 9 | | > 10 | | The c's look just like a full outer join of t1 & t2 on a & b. Giving them saner names to avoid duplicate output col's, let's call them "c1" & "c2". At that point a left outer join on a gives you all of the base.a values with any t{1,2} rows that have a matching a: No idea what your data really looks like but if t1 or t2 has more than three col's distinct can save some annoying cross-products: select distinct base.a , z.c1 , z.c2 from base left join ( select t1.a , t1.c "c1" , t2.c "c2" from t1 full outer join t2 on t1.a = t2.a and t1.b = t2.b ) z on base.a = z.a ; -- Steven Lembark 3646 Flora Place Workhorse Computing St. Louis, MO 63110 lembark@wrkhors.com +1 888 359 3508