Inner join with table that does not contain any record fails? - postgresql

I have query in which I have applied inner join, it works fine if the joining table on right side has some records, but inner joins return nothing when joining table has 0 (zero) records, and I understand it bcz it has nothing to join on.
I want the records from tbl11 if record has status=1 in tbl11 or if that record exist in tbl12 then it must have status=1, if tbl12.status=0, then that record is not needed.
Table structure
create table tbl11
(
tbl11_id serial primary key,
name character varying,
status integer
)
insert into tbl11(name, status) values('Tony',1),
('Jony',1),
('Sneha',1),
('Aakriti',0)
create table tbl12
(
tbl12_id serial primary key,
tbl11_id integer,
name character varying,
status integer
)
Here is what, I have tried till now
select t1.tbl11_id, t1.name, t1.status from tbl11 t1
inner join tbl12 t2 on t1.tbl11_id = t2.tbl11_id or t1.tbl11_id !=t2.tbl11_id
where t1.status=1 and t2.status=1
It gives no output as tbl12 doesn't have any data.
Then I read about case in postgres and tried this
select case
when
select exists (select 1 from tbl12)
Then
select t1.tbl11_id, t1.name, t1.status from tbl11 t1
inner join tbl12 t2 on t1.tbl11_id = t2.tbl11_id or t1.tbl11_id !=t2.tbl11_id
where t1.status=1 and t2.status=2
else
select tbl11_id, name, status from tbl11 where status=1
But it gives error as
ERROR: syntax error at or near "select"
LINE 3: select exists (select 1 from tbl12)

this is the simple query
select * from tbl11 where status=1 and tbl11_id not in (select tbl11_id from tbl12 where status=0);
DEMO

Related

Postgres join involving tables having join condition defined on an text array

I have two tables in postgresql
One table is of the form
Create table table1(
ID serial PRIMARY KEY,
Type []Text
)
Create table table2(
type text,
sellerID int
)
Now i want to get all the rows from table1 which are having type same that in table2 but the problem is that in table1 the type is an array.
In case the type in the table has an identifiable delimiter like ',' ,';' etc. you can rewrite the query as regexp_split_to_table(type,',') or versions later than 9.5 unnest function can be use too.
For eg.,
select * from
( select id ,regexp_split_to_table(type,',') from table1)table1
inner join
select * from table2
on trim(table1.type) = trim(table2.type)
Another good example can be found - https://www.dbrnd.com/2017/03/postgresql-regexp_split_to_array-to-split-string-using-different-delimiters/
SELECT
a[1] AS DiskInfo
,a[2] AS DiskNumber
,a[3] AS MessageKeyword
FROM (
SELECT regexp_split_to_array('Postgres Disk information , disk 2 , failed', ',')
) AS dt(a)
You can use the ANY operator in the JOIN condition:
select *
from table1 t1
join table2 t2 on t2.type = any (t1.type);
Note that if the types in the table1 match multiple rows in table2, you would get duplicates (from table1) because that's how a join works. Maybe you want an EXISTS condition instead:
select *
from table1 t1
where exists (select *
from table2 t2
where t2.type = any(t1.type));

PostgreSQL count other values of ID that have the same value of other column

Let's say we have the following table that stores id of an observation and its address_id. You can create the table with the following code:
drop table if exists schema.pl_address_cnt;
create table schema.pl_address_cnt (
id serial,
address_id int);
insert into schema.pl_address_cnt(address_id) values
(100), (101), (100), (101), (100), (125), (128), (200), (200), (100);
My task is to count for each id how many other ids (thus -1) have the same address_id. I've come up with a solution that turns out to be quite expensive (explain) on the original dataset. I wonder whether my solution can be somehow optimised.
with tmp_table as (select address_id
, count(distinct id) as id_count
from schema.pl_address_cnt
group by address_id
)
select id
, id_count - 1
from schema.pl_address_cnt as pac
left join tmp_table as tt on tt.address_id=pac.address_id;
You can try to omit the CTE and do a self left join on common address but different ID and then aggregate this.
SELECT pac1.id,
count(pac2.id)
FROM pl_address_cnt pac1
LEFT JOIN pl_address_cnt pac2
ON pac1.address_id = pac2.address_id
AND pac1.id <> pac2.id
GROUP BY pac1.id
ORDER BY pac1.id;
For performance you can try indexes on (address_id, id) and (id).

conditional join with input value in postgreslq function

I have three tables:
create table id_table (
id integer
);
insert into id_table values (1),(2),(3);
create table alphabet_table (
id integer,
letter text
);
insert into alphabet_table values (1,'a'),(2,'b'),(3,'c');
create table greek_table (
id integer,
letter text
);
insert into greek_table values (1,'alpha'),(2,'beta');
I like to create a function that join id_table with either alphabet_table or greek_table on id. The choice of the table depends on an input value specified in the function. I wrote:
CREATE OR REPLACE FUNCTION choose_letters(letter_type text)
RETURNS table (id integer,letter text) AS $$
BEGIN
RETURN QUERY
select t1.id,
case when letter_type = 'alphabet' then t2.letter else t3.letter end as letter
from id_table t1,
alphabet_table t2 ,
greek_table t3
where t1.id = t2.id and t1.id = t3.id;
END;
$$LANGUAGE plpgsql;
I ran select choose_letter('alphabet'). The problem with this code is that when id_table joins with alphabet_table, it does not pick up id, No 3. It seems that inner joins are done for both alphabet_table and greek_table (so it only picks up the common ids, 1 and 2). To avoid this problem, I wrote:
CREATE OR REPLACE FUNCTION choose_letters(letter_type text)
RETURNS table (id integer, letter text) AS $$
BEGIN
RETURN QUERY
select t1.id,
case when letter_type = 'alphabet' then t2.letter else t3.letter end as letter
from id_table t1
left join alphabet_table t2 on t1.id=t2.id
left join greek_table t3 on t1.id=t3.id
where t2.letter is not null or t3.letter is not null;
END;
$$LANGUAGE plpgsql;
Now it pick up all the 3 ids when id_table and alphabet_table join. However, When I ran select choose_letter('greek'). The id no. 3 appears with null value in letter column despite the fact that I specified t3.letter is not null in where clause.
What I'm looking for is that when I ran select choose_letters('alphabet'), the output needs to be (1,'a'), (2,'b'),(3,'c'). select choose_letters('greek') should produce (1,'alpha'),(2,'beta). No missing values nor null. How can I accomplish this?
Learn to use proper, explicit JOIN syntax. Simple rule: Never use commas in the FROM clause.
You can do what you want with LEFT JOIN and some other logic:
select i.id,
coalesce(a.letter, g.letter) as letter
from id_table i left join
alphabet_table a
on i.id = a.id and letter_type = 'alphabet' left join
greek_table g
on i.id = g.id and letter_type <> 'alphabet'
where a.id is not null or g.id is not null;
The condition using letter_type needs to be in the on clauses. Otherwise, alphabet_table will always have a match.
Gordon Linoff's answer above is certainly correct, but here is an alternative way to write the code.
It may or may not be better from a performance perspective, but it is logically equivalent. If performance is a concern you'd need to run EXPLAIN ANALYZE on the query an inspect the plan, and do other profiling.
Some good parts about this are the inner join makes the join clause and where clause simpler and easier to reason about. It's also more straight forward for the execution engine to parallelize the query.
On the downside it looks like code duplication, however, the DRY principle is often misapplied to SQL. Repeating code is less important than repeating data reads. What you're aiming to do is not scan the same data multiple times.
If there is no index on the fields you are joining or the letter_type then this could end up doing a full table scan twice, and be worse. If you do have the indexes then it can do it with index range scans nicely.
SELECT
i.id,
a.letter
FROM id_table i
INNER JOIN alphabet_table a
ON i.id = a.id
WHERE letter_type = 'alphabet'
UNION ALL
SELECT
i.id,
g.letter
FROM id_table i
INNER JOIN greek_table g
ON i.id = g.id
WHERE letter_type <> 'alphabet'
The first problem is your tables or not structured properly, You would have created single table like char_table (id int, letter text, type text) type will specify whether it is alphabet or Greek.
Another solution is you can write two SQL queries one in if condition other one is in else part

Same name attributes in select list in pg-promise

Is it possible to get the same name attributes in the select list (as JSON deduplicates them)?
For instance:
CREATE TABLE t1 (
id int;
);
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2);
CREATE TABLE t2 (
id int;
);
INSERT INTO t2 VALUES(1);
SELECT *
FROM t1 LEFT JOIN t2 ON t1.id = t2.id
should return:
id id
-----
1 1
2 null
but will return instead:
id
---
1
null
I'm trying to build a web-based SQL editor, and this is kind of a showstopper.
Sorry, found it, it was solved in:
pg: https://github.com/brianc/node-postgres/pull/393
and subsequently in pg-promise: https://github.com/vitaly-t/pg-promise/releases/tag/v.4.0.5
One can use rowMode argument to get results as an array:
http://vitaly-t.github.io/pg-promise/PreparedStatement.html#rowMode

PostgreSQL compare with substring doesn't work

I want to select from 2 table where 2nd id is equal with first 4 character from first id
SELECT a.*, b.*, substring(a.my_id from 1 for 4)::integer as number
FROM table1 as a
INNER Join table2 as b ON(b.id_2=number) where my_id = 101
^
It produces an error here |
ERROR: column "number" does not exist
SQL state: 42703
Character: 189
You can't use an alias like that, you need a derived table:
select *
from (
SELECT t1.*,
substring(t1.my_id::text from 1 for 4)::integer as number
FROM table1 t1
) as a
inner join table2 as b ON (b.id_2 = a.number)
where my_id = 101
Storing a number that is used as a foreign key as a part in a varchar column is a really, really ugly design. That number should be a column of its own in table1.