Add Default Rows in Postgresql - postgresql

I want to insert default rows into a result set if the LEFT JOIN is NULL.
For example if Jane has no roles, I want to return some default ones in the results.
A query like this will return the following:
SELECT * FROM employees LEFT OUTER JOIN roles ON roles.employee_id = employees.id
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | NULL
I want to return:
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | Admin
2 | Jane | NULL | Standard
Is there a good way to do this in PostgreSQL?

I think you're looking for
SELECT e.*, r.*
FROM employees e
JOIN roles r ON r.employee_id = e.id
UNION ALL
SELECT e.*, NULL, default_name
FROM employees e
JOIN (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
I don't think there's a (good) way around the UNION because a LEFT JOIN introduces only a single row per unmatched row. You might be able to lift out the join against the employees table though:
SELECT e.*, r.*
FROM employees e,
LATERAL (
SELECT r.id, r.name
FROM roles r
WHERE r.employee_id = e.id
UNION ALL
SELECT NULL, default_name
FROM (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
)

Related

Transforming information in postgresql

So, I have 2 tables,
In the 1st table, there is an Information of users
user_id | name
1 | Albert
2 | Anthony
and in the other table, I have information
where some users have address information where it can either be home, office or both home and office
user_id| address_type | address
1 | home | a
1 | office | b
2 | home | c
and the final result I want is this
user_id | name | home_address | office_address
1 | Albert | a | b
2 | Anthony | c | null
I have tried using left join and json_agg but the information that way is not readable,
any suggestions on how I can do this?
You can use two outer joins, one for the office address and one for the home address.
select t1.user_id, t1.name,
ha.address as home_address,
oa.address as office_address
from table1 t1
left join table2 ha on ha.user_id = t1.user_id and ha.address_type = 'home'
left join table2 oa on oa.user_id = t1.user_id and ha.address_type = 'office';
A solution using JSON could look like this
select t1.user_id, t1.name,
a.addresses ->> 'home' as home_address,
a.addresses ->> 'office' as office_address
from table1 t1
left join (
select user_id, jsonb_object_agg(address_type, address) as addresses
from table2
group by user_id
) a on a.user_id = t1.user_id;
Which might be a bit more flexible, because you don't need to add a new join for each address type. The first query is likely to be faster if you need to retrieve a large number of rows.

postgres one to many flattened return

I am trying to write a postgres query that uses 3 tables: people, attribute, and a people_attribute join
people table:
id, name
attribute table:
id, name, attr_group
people_attribute join:
people_id, attribute_id
desired output:
name | fav_colors | fav_music | fav_foods
-----------------------------------------------------------------
michael | red,blue,green | pop,hip-hop,jazz | pizza,burgers,tacos
bob | orange,green | null | tacos,steak,fish
...etc
The tags can vary from none to ~12 for each attr_group
Here is the query I am working with:
select
p.id,
p.name,
(case when a.attr_group like 'fav_colors' then string_agg(a.name, ',') else null end) as fav_colors,
(case when a.attr_group like 'fav_music' then string_agg(a.name, ',') else null end) as fav_music,
(case when a.attr_group like 'fav_foods' then string_agg(a.name, ',') else null end) as fav_foods,
from people as p
join people_attribute as pa on pa.people_id = p.id
join "attribute" as a on a.id = pa.attribute_id
group by 1,2,a.attr_group
order by 1 asc;
which returns:
name | fav_colors | fav_music | fav_foods
-----------------------------------------------------------------
michael | red,blue,green | null | null
michael | null | pop,hip-hop,jazz | null
michael | null | null | pizza,burgers,tacos
bob | null | null | null
bob | orange,green | null | null
bob | null | null | tacos,steak,fish
I feel like I'm getting close, but am unsure how to flatten this out to achieve the desired output as shown above. Any help would be greatly appreciated!
You want to use filter for this:
select p.id,
p.name,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_color') as fav_colors,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_music') as fav_music,
string_agg(a.name, ',') filter (where a.attr_group = 'fav_foods') as fav_foods,
from people as p
join people_attribute as pa
on pa.people_id = p.id
join "attribute" as a
on a.id = pa.attribute_id
group by p.id, p.name
order by 1 asc;
Using filter passes only values that match the filter where condition into the aggregation.
The reason yours was showing three rows per people record is because you added attribute.attr_group to your group by. You had no choice since you were using attribute.attr_group in your case conditionals.
Using filter makes attribute.attr_group part of the aggregation, so you do not have to include it in your group by list.

EAV data in SQL Server

I have no control over the data or the database structure. I have this EAV type of data where a consultant can speak one or many languages and he can travel to 1 or many countries in Europe and he has many skills indeed.
FYI there are 10 different main categories in my data.
Some consultants speak 10 languages while other speak only one.
The data looks a bit like this
____________________________________________
| ConsultantID | Category | Value |
--------------------------------------------
| 1 | Language | English |
| 1 | Language | French (fluent) |
| 1 | Language | Spanish (working)|
| 1 | Country | Ireland |
| 1 | Country | Italy |
| 1 | Country | Germany |
| 1 | Country | Belgium |
| 456 | Language | French (working) |
| 456 | Country | Belgium |
| 847 | Language | English |
| 847 | Country | Belgium |
--------------------------------------------
I want to list all consultants willing to travel to Belgium and who speak French (working or fluent). Based on my current example that would be #1 and #456
I wrote the query below which list all values matching a category for a consultant (note this is not dynamic as the number of value in my example is set to 5 max - so already a poor design).
SELECT
ID, category,
MAX(CASE seq WHEN 1 THEN value ELSE '' END ) +
MAX(CASE seq WHEN 2 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 3 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 4 THEN ',' + value ELSE '' END ) +
MAX(CASE seq WHEN 5 THEN ',' + value ELSE '' END )
FROM
(SELECT
p1.ID, p1.category, p1.value,
(SELECT COUNT(*)
FROM tblWebPracticeInfo p2
WHERE p2.category = p1.category
AND p2.ID = P1.ID
AND p2.value <= p1.value)
FROM
tblWebPracticeInfo p1) D (ID, category, value, seq )
GROUP BY
ID, category
ORDER BY
ID;
I would then need to query this table...
But without even a where clause it takes already 2 seconds to execute
I have something else more basic (but similarly not efficient)
select *
from tblWebMemberInfo m
where
m.ID in (select p.id from tblWebPracticeInfo p
where p.category = 'Language' and p.value like 'French%')
and m.ID in (select p.id from tblWebPracticeInfo p
where p.category = 'Country' and p.value = 'Belgium')
order by m.ID
That's basically where I am. As you can see nothing genius and nothing which is really working.
Can you point me to the right track.
I'm using SQL Server 2005 - v9.00.1
Many thanks in advance for your time & help
If you just need to list the consultants then you can use exists():
select p.Id ...
from Person p /* Assuming you have a regular table for people,
if not, use distinct or group by */
where exists (
select 1
from tblWebPracticeInfo l
where l.Id = p.Id
and l.Category = 'Language'
and l.Value = 'French'
)
and exists (
select 1
from tblWebPracticeInfo c
where c.Id = p.Id
and c.Category = 'Country'
and c.Value = 'Belgium'
)
You could also use aggregation and having like so:
select ConsultantID
from tblWebMemberInfo m
where (p.category = 'Language' and p.value like 'French%')
or (p.category = 'Country' and p.value = 'Belgium')
group by ConsultantID
having count(*) = 2 /* number of conditions to match is 2 */

PostgreSQL - Count Function

I've got two Tables Called Manu and Cars
Manufacturer | Employees | id
Toyota | 102346 | 1
Subaru | 284608 | 2
Kia | 268244 | 3
Suzuki | 228624 | 4
The second table Cars
Car | id
Corolla | 1
camry | 1
alto | 4
vitara | 4
forester | 2
impreza | 2
xv | 2
cerato | 3
celica | 1
Now the table Cars references back to table Manu through Id
Im trying to return manufacturers that have produced 2 or more models of cars.
So far what I have tried is
Select m.id, m.manufacturer
from Manu m
inner join Cars n on m.id = n.id
group by m.id having count(n.id) >= 2;
it tells me that column m.id must appear in group by clause or be used in an aggregate function. Very confused.
Thanks
First we'll get the number of cars by manufacturer:
SELECT id,COUNT(id) FROM cars GROUP BY id;
Next, we'll use that data to get the manufacturers that make more than two models:
SELECT s.id,s.count,m.manufacturer FROM
(SELECT id,COUNT(id) FROM cars GROUP BY id) s
JOIN manu m USING (id) WHERE s.count >= 2;
Actually you have already give the answer. just you didnt group by m.manufactorer;
Select m.id, m.manufactorer
from Manu m
inner join Cars n using(id)
group by m.id,m.manufactorer
having count(*) >= 2;

Update Count column in Postgresql

I have a single table laid out as such:
id | name | count
1 | John |
2 | Jim |
3 | John |
4 | Tim |
I need to fill out the count column such that the result is the number of times the specific name shows up in the column name.
The result should be:
id | name | count
1 | John | 2
2 | Jim | 1
3 | John | 2
4 | Tim | 1
I can get the count of occurrences of unique names easily using:
SELECT COUNT(name)
FROM table
GROUP BY name
But that doesn't fit into an UPDATE statement due to it returning multiple rows.
I can also get it narrowed down to a single row by doing this:
SELECT COUNT(name)
FROM table
WHERE name = 'John'
GROUP BY name
But that doesn't allow me to fill out the entire column, just the 'John' rows.
you can do that with a common table expression:
with counted as (
select name, count(*) as name_count
from the_table
group by name
)
update the_table
set "count" = c.name_count
from counted c
where c.name = the_table.name;
Another (slower) option would be to use a co-related sub-query:
update the_table
set "count" = (select count(*)
from the_table t2
where t2.name = the_table.name);
But in general it is a bad idea to store values that can easily be calculated on the fly:
select id,
name,
count(*) over (partition by name) as name_count
from the_table;
Another method : Using a derived table
UPDATE tb
SET count = t.count
FROM (
SELECT count(NAME)
,NAME
FROM tb
GROUP BY 2
) t
WHERE t.NAME = tb.NAME