PostgreSQL query with variable result column - postgresql

Hoping i'm missing something simple here, and saying it clearly..
I have a table that i need to join to 1 of 2 possible other tables, depending on user input of the id found in the primary table.
t1:
primary_id | t2_id | t3_id
--------------------------
1 | x | null
2 | null | y
t2:
id | value
----------
x | a
t3:
id | value
----------
y | b
I want to do the following in a single query:
select primary_id, t2_value from t1, t2
where t1.t2_id=t2.id
or
select primary_id, t3_value from t1, t3
where t1.t3_id=t3.id
here's the full join:
with t1 as (select * from t1),
t2 as (select * from t2),
t3 as (select * from t3),
select * from
t1
left join
t2
on t1.t2_id=t2.id
left join
t3
on t1.t3_id=t3.id
where t1.t2_id is not null
when i run this, i'd like to get back just t1 and t2 columns, not t3..
please and thank you!

You can use COALESCE():
select t1.primary_id,
coalesce(t1.t2_id, t1.t3_id) id,
coalesce(t2.value, t3.value) "value"
from t1
left join t2 on t1.t2_id=t2.id
left join t3 on t1.t3_id=t3.id
where t1.t2_id is not null
See the demo.
Results:
| primary_id | id | value |
| ---------- | --- | ----- |
| 1 | x | a |
If you change the condition to:
where t1.t3_id is not null
you will get:
| primary_id | id | value |
| ---------- | --- | ----- |
| 2 | y | b |

Related

how to unnest multiple arrays and transpose table in postgres 9.5

Lets suppose i have this table:
project |leader|coordination|staff |support |
---------|------|------------|-------------------|-----------|
project_a|Bob |{Alice} |{Terry,Mandy,James}|{Bob,Peter}|
project_b|Alice |{Terry, Bob}|{Mandy,James} |{Maggie} |
project_c|Maggie|{Bob} |{Terry,Peter} |{Alice,Bob}|
and i want to transform it to something like this:
person|project_1|role_1|project_2|role_2 |project_3|role_3 |project_4|role_4 |
------|---------|------|---------|------------|---------|-------|---------|------------|
Alice |project_b|leader|project_a|coordination|project_c|support| | |
Bob |project_a|leader|project_a|support |project_b|staff |project_c|coordination|
Terry...
Maggie...
...
Big thanks to #abelisto for pointing me in the right direction in the comments.
The problem with crosstab is, that i need the project also in the row:
SELECT *
FROM crosstab(
'
select
person,
project,
role
from
tmp_projects
cross join lateral (
select
\'leader\'::text as role, leader as person
union all
select
\'coordination\', x
from
unnest(coordination) as x
union all
select
\'staff\', x
from
unnest(staff) as x
union all
select
\'support\', x
from
unnest(support) as x) as t order by 1,2' -- needs to be "ORDER BY 1,2" here
, 'SELECT DISTINCT project FROM tmp_projects ORDER BY 1'
) AS ct ("Person" text, "Project_1" text,"Project_2" text,"Project_3" text);
#Abelisto had the correct idea and a solution could be:
create view tmp_view_projects_unnest as select
person,
row_number() OVER (PARTITION by person) AS rn,
project,
role
from
tmp_projects
cross join lateral (
select
'leader'::text as role, leader as person
union all
select
'coordination', x
from
unnest(coordination) as x
union all
select
'staff', x
from
unnest(staff) as x
union all
select
'support', x
from
unnest(support) as x) as t order by 1,2
person|rn|project |role |
------|--|---------|------------|
Alice | 1|project_a|leader |
Alice | 2|project_d|coordination|
Alice | 3|project_c|support |
Bob | 1|project_d|coordination|
Bob | 2|project_c|coordination|
Bob | 3|project_c|support |
Maggie| 1|project_c|leader |
Mandy | 1|project_d|leader |
Marry | 1|project_d|staff |
Peter | 1|project_d|support |
Peter | 2|project_c|staff |
Peter | 3|project_a|coordination|
Terry | 1|project_a|coordination|
Terry | 2|project_c|staff |
select tvp.person,
tvp.project as project_1, tvp.role as role_1 ,
tvp2.project as project_2, tvp2.role as role_2,
tvp3.project as project_3, tvp3.role as role_3,
tvp4.project as project_4, tvp4.role as role_4
from tmp_view_projects_unnest tvp
left join tmp_view_projects_unnest tvp2 on tvp2.person = tvp.person and tvp2.rn =2
left join tmp_view_projects_unnest tvp3 on tvp3.person = tvp.person and tvp3.rn =3
left join tmp_view_projects_unnest tvp4 on tvp4.person = tvp.person and tvp4.rn =4
where tvp.rn=1;
person|project_1|role_1 |project_2|role_2 |project_3|role_3 |project_4|role_4|
------|---------|------------|---------|------------|---------|------------|---------|------|
Alice |project_a|leader |project_d|coordination|project_c|support | | |
Bob |project_d|coordination|project_c|coordination|project_c|support | | |
Maggie|project_c|leader | | | | | | |
Mandy |project_d|leader | | | | | | |
Marry |project_d|staff | | | | | | |
Peter |project_d|support |project_c|staff |project_a|coordination| | |
Terry |project_a|coordination|project_c|staff | | | | |

How to return IDs with different values between field rows

I'd like to return IDs with different values between segments:
| id | segment | quantity |
|-------|-------------|----------|
| 12345 | Control - A | 1 |
| 12345 | Control - B | 1 |
| 98765 | Control - A | 0 |
| 98765 | Control - B | 1 |
Output:
| id |
|-------|
| 98765 |
I have tried CASE logic, partitioning, etc. but am wondering the optimal approach.
I find using the EXISTS clause to be the easiest to read in these types of cases, but the other answer with the self-join should also work just fine.
select distinct id
from t1 where exists
(select 1
from t1 t1_alias
WHERE t1.id = t1_alias.id and
t1.segment != t1_alias.segment and
t1.quantity != t1_alias.quantity)
;
Probably not the optimal approach, but you haven't mentioned it, I would try something like this:
select
distinct t1.id
from
table t1 inner join
table t2 on t1.id = t2.id and t1.quantity != t2.quantity and t1.segment != t2.segment

How to use join with aggregate function in postgresql?

I have 4 tables
Table1
id | name
1 | A
2 | B
Table2
id | name1
1 | C
2 | D
Table3
id | name2
1 | E
2 | F
Table4
id | name1_id | name2_id | name3_id
1 | 1 | 2 | 1
2 | 2 | 2 | 2
3 | 1 | 2 | 1
4 | 2 | 1 | 1
5 | 1 | 1 | 2
6 | 2 | 2 | 1
7 | 1 | 1 | 2
8 | 2 | 1 | 1
9 | 1 | 2 | 1
10 | 2 | 2 | 1
Now I want to join all tables with 4 and get this type of output
name | count
{A,B} | {5, 5}
{C,D} | {5, 6}
{E,F} | {7, 3}
I tried this
select array_agg(distinct(t1.name)), array_agg(distinct(temp.test))
from
(select t4.name1_id, (count(t4.name1_id)) "test"
from table4 t4 group by t4.name1_id
) temp
join table1 t1
on temp.name1_id = t1.id
I am trying to achieve this. Anybody can help me.
Calculate the counts for every table separately and union the results:
select
array_agg(name order by name) as name,
array_agg(count order by name) as count
from (
select 1 as t, name, count(*)
from table4
join table1 t1 on t1.id = name1_id
group by name
union all
select 2 as t, name, count(*)
from table4
join table2 t2 on t2.id = name2_id
group by name
union all
select 3 as t, name, count(*)
from table4
join table3 t3 on t3.id = name3_id
group by name
) s
group by t;
name | count
-------+-------
{A,B} | {5,5}
{C,D} | {4,6}
{E,F} | {7,3}
(3 rows)

Update a table from a union select statement

I have two tables as below:
tablea
k | 1 | 2
--------------------
a | mango | xx
b | orange| xx
c | xx | apple
d | xx | banana
a | xx | mango
tableb
k | 1 | 2
--------------------
a | |
b | |
c | |
d | |
How can I update tableb from tablea so I get the results below?
tableb
k | 1 | 2
--------------------
a | mango | mango
b | orange| xx
c | xx | apple
d | xx | banana
if in case I try to use a update statement like below
update tableb
set 1 = x.1,
2 = x.2
from
(
select * from tablea
) x
where tablea.k = x.k
Can I make the update statement to ignore xx if k is duplicate?
Thanks.
Here is the SELECT, hope you can make the update.
Try to search a match for every one on the left side with name <> 'xx'
Then union with the rest of rows I havent use it yet.
SQL Fiddle Demo
SELECT t1."k", t1."1", COALESCE(t2."2", 'xx') "2"
FROM tablea t1
LEFT JOIN tablea t2
ON t1."1" = t2."2"
WHERE t1."1" <> 'xx'
UNION ALL
SELECT t1."k", t1."1", t1."2"
FROM tablea t1
WHERE t1."1" = 'xx'
AND t1."2" NOT IN (SELECT t2."1" FROM tablea t2 WHERE t2."1" <> 'xx')

SQL - group by - limit clause - postgresql

I have a table which has two columns C1 and C2.
C1 has an integer data type and C2 has text.
Table looks like this.
---C1--- ---C2---
1 | a |
1 | b |
1 | c |
1 | d |
1 | e |
1 | f |
1 | g |
2 | h |
2 | i |
2 | j |
2 | k |
2 | l |
2 | m |
2 | n |
------------------
My question: i want a sql query which does group by on column C1 but with size of 3.
looks like this.
------------------
1 | a,b,c |
1 | d,e,f |
1 | g |
2 | h,i,j |
2 | k,l,m |
2 | n |
------------------
is it possible by executing SQL???
Note: I do not want to write stored procedure or function...
You can use a common table expression to partition the results into rows, and then use STRING_AGG to join them into comma separated lists;
WITH cte AS (
SELECT *, (ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2)-1)/3 rn
FROM mytable
)
SELECT C1, STRING_AGG(C2, ',') ALL_C2
FROM cte
GROUP BY C1,rn
ORDER BY C1
An SQLfiddle to test with.
A short explanation of the common table expression;
ROW_NUMBER() OVER (...) will number the results from 1 to n for each value of C1. We then subtract 1 and divide by 3 to get the sequence 0,0,0,1,1,1,2,2,2... and group by that value in the outer query to get 3 results per row.
Apart from Joachim Isaksson's answer,you try this method also
SELECT C1, string_agg(C2, ',') as c2
FROM (
SELECT *, (ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2)-1)/3 as row_num
FROM atable) t
GROUP BY C1,row_num
ORDER BY c2