Postgres join when only one row is equal - postgresql

I have two tables and I am wanting to do an inner join between table_1 and table_2 but only when there is one row in table_2 that meets the join criteria.
For example:
table_1
id | name | age |
-----------------+------------------+--------------+
1 | john jones | 10 |
2 | pete smith | 15 |
3 | mary lewis | 12 |
4 | amy roberts | 13 |
table_2
id | name | age | hair | height |
-----------------+------------------+--------------+--------------+--------------+
1 | john jones | 10 | brown | 100 |
2 | john jones | 10 | blonde | 132 |
3 | mary lewis | 12 | brown | 146 |
4 | pete smith | 15 | black | 171 |
So I want to do a join when name is equal, but only when there is one corresponding matching name in table_2
So my results would look like this:
id | name | age | hair |
-----------------+------------------+--------------+--------------+
2 | pete smith | 15 | black |
3 | mary lewis | 12 | brown |
As you can see, John Jones isn't in the results as there are two corresponding rows in table_2.
My initial code looks like this:
select tb.id,tb.name,tb.age,sc.hair
from table_1 tb
inner join table_2 sc
on tb.name = sc.name and tb.age = sc.age
Can I apply a clause within the join so that it only joins on rows which are unique matches?

Group by all columns and apply having count(*) = 1
select tb.id,tb.name,tb.age,sc.hair
from table_1 tb
join table_2 sc
on tb.name = sc.name and tb.age = sc.age
group by tb.id,tb.name,tb.age,sc.hair
having count(*) = 1
The interesting thing to note is that you don’t need the aggregate expression (in the case count(*) )in the select clause.

Related

How to list rows with duplicate columns

I have a table with the fields id, name, birthday, clinic:
id | name | birthday | clinic
1 | mary | 2020-01-01 | clin 1
2 | mary | 2020-01-01 | clin 1
3 | mary | 2020-01-01 | clin 2
4 | john | 2021-01-01 | clin 1
5 | pete | 2020-01-05 | clin 1
6 | pete | 2020-01-05 | clin 2
7 | pete | 2020-01-05 | clin 3
I want to get all records with name, birthday duplicate like:
id | name | birthday | clinic
1 | mary | 2020-01-01 | clin 1
2 | mary | 2020-01-01 | clin 1
3 | mary | 2020-01-01 | clin 2
5 | pete | 2020-01-05 | clin 1
6 | pete | 2020-01-05 | clin 2
7 | pete | 2020-01-05 | clin 3
Mary and Pete have more than one record with same name and birthday
Using COUNT() as an analytical function, we can try:
WITH cte AS (
SELECT *, COUNT(*) OVER (PARTITION BY name, birthday) cnt
FROM yourTable
)
SELECT id, name, birthday, clinic
FROM cte
WHERE cnt > 1;
try
select * from <table> where (name , birthday) in (
select name , birthday from <table> group by name, birthday having count(*)>1)
You can use an EXISTS condition:
select t1.*
from the_table t1
where exists (select *
from the_table t2
where t1.id <> t2.id
and (t1.name, t1.birthday) = (t2.name, t2.birthday));

Make sure every distinct value of Column1 has a row with every distinct value of Column2, by populating a table with 0s - postgresql

Here's a crude example I've made up to illustrate what I want to achieve:
table1:
| Shop | Product | QuantityInStock |
| a | Prod1 | 13 |
| a | Prod3 | 13 |
| b | Prod2 | 13 |
| b | Prod3 | 13 |
| b | Prod4 | 13 |
table1 becomes:
| Shop | Product | QuantityInStock |
| a | Prod1 | 13 |
| a | Prod2 | 0 | -- new
| a | Prod3 | 13 |
| a | Prod4 | 0 | -- new
| b | Prod1 | 0 | -- new
| b | Prod2 | 13 |
| b | Prod3 | 13 |
| b | Prod4 | 13 |
In this example, I want to represent every Shop/Product combination
every Shop {a,b} to have a row with every Product {Prod1, Prod2, Prod3, Prod4}
QuantityInStock=13 has no significance, I just wanted a placeholder number :)
Use a calendar table cross join approach:
SELECT s.Shop, p.Product, COALESCE(t1.QuantityInStock, 0) AS QuantityInStock
FROM (SELECT DISTINCT Shop FROM table1) s
CROSS JOIN (SELECT DISTINCT Product FROM table1) p
LEFT JOIN table1 t1
ON t1.Shop = s.Shop AND
t1.Product = p.Product
ORDER BY
s.Shop,
p.Product;
The idea here is to generate an intermediate table containing of all shop/product combinations via a cross join. Then, we left join this to table1. Any shop/product combinations which do not have a match in the actual table are assigned a zero stock quantity.

PostgreSQL COUNT DISTINCT on one column while checking duplicates of another column

I have a query that results in such a table:
guardian_id | child_id | guardian_name | relation | child_name |
------------|----------|---------------|----------|------------|
1 | 1 | John Doe | father | Doe Son |
2 | 1 | Jane Doe | mother | Doe Son |
3 | 2 | Peter Pan | father | Pan Dghter |
4 | 2 | Pet Pan | mother | Pan Dghter |
1 | 3 | John Doe | father | Doe Dghter |
2 | 3 | Jane Doe | mother | Doe Dghter |
So from these results, I need to count the families. That is, distinct children with the same guardians. From the results above, There are 3 children but 2 families. How can I achieve this?
If I do:
SELECT COUNT(DISTINCT child_id) as families FROM (
//larger query
)a
I'll get 3 which is not correct.
Alternatively, how can I incorporate a WHERE clause that checks DISTINCT guardian_id's? Any other approaches?
Also note that there are instances where a child may have one guardian only.
To get the distinct family you can try the following approach.
select distinct array_agg(distinct guardian_id)
from family
group by child_id;
The above query will return the list of unique families.
eg.
{1,2}
{3,4}
Now you can apply the count on top of it.

How to grab 2 products for each department?

I am trying to grab 2 products for each department. A department has many aisles, an aisle has many products.
Why isn't the LIMIT in the LATERAL working? At the moment, it is returning more that 2 products.
I've got:
SELECT d.id, p.* FROM departments d
JOIN aisles a ON d.id = a.department_id, LATERAL (
SELECT * FROM products p1 WHERE p1.aisle_id = a.id ORDER BY p1.id ASC LIMIT 2
) p
WHERE a.department_id IN (3,5);
Which returns:
id | id | aisle_id | name | image_filename | created_at | updated_at
----+-----+-----------+-----------------------------+-----------------+----------------------------+----------------------------
3 | 149 | 11 | Sleek Wool Watch | foo.png | 2015-10-30 10:03:21.107873 | 2015-10-30 10:03:21.107873
3 | 3 | 12 | Heavy Duty Granite Lamp | foo.png | 2015-10-30 10:03:20.637513 | 2015-10-30 10:03:20.637513
3 | 88 | 12 | Rustic Marble Shirt | foo.png | 2015-10-30 10:03:20.883394 | 2015-10-30 10:03:20.883394
3 | 56 | 13 | Enormous Linen Bottle | foo.png | 2015-10-30 10:03:20.786546 | 2015-10-30 10:03:20.786546
5 | 22 | 24 | Gorgeous Linen Clock | foo.png | 2015-10-30 10:03:20.687868 | 2015-10-30 10:03:20.687868
5 | 104 | 24 | Synergistic Leather Bag | foo.png | 2015-10-30 10:03:20.933504 | 2015-10-30 10:03:20.933504
5 | 80 | 25 | Enormous Leather Bag | foo.png | 2015-10-30 10:03:20.860636 | 2015-10-30 10:03:20.860636
5 | 117 | 25 | Aerodynamic Bronze Gloves | foo.png | 2015-10-30 10:03:21.015299 | 2015-10-30 10:03:21.015299
I hope you need to mention LEFT OUTER JOIN LATERAL and ON True,which will fix your issue .
SELECT d.id, p.* FROM departments d
JOIN aisles a ON d.id = a.department_id LEFT OUTER JOIN LATERAL (
SELECT * FROM products p1 WHERE p1.aisle_id = a.id ORDER BY p1.id ASC LIMIT 2
) p
ON True
WHERE a.department_id IN (3,5);
More information here
What is the difference between LATERAL and a subquery in PostgreSQL?

complex query in postgresql

Hello and warm greetings to you all,
I am having some challenges writing a complex query in posgresql. the fact is i'm having problem writing this particular query period, and your help is kindly needed. lets get to it.
here are my tables
[products_tb] [client_tb] [sales_tb]
+--------------+ +-------------+ +-------------+--------------+-----+
| product_name | | client_name | | client_name | product_name | qty |
+--------------+ +-------------+ +-------------+--------------+-----+
| shoe | | john | | john | shoe | 20 |
+--------------+ +-------------+ +-------------+--------------+-----+
| belt | | bob | | john | belt | 9 |
+--------------+ +-------------+ +-------------+--------------+-----+
| kim | | bob | shoe | 2 |
+-------------+ +-------------+--------------+-----+
| bob | belt | 98 |
+-------------+--------------+-----+
| kim | shoe | 46 |
+-------------+--------------+-----+
| kim | belt | 3 |
+-------------+--------------+-----+
[query output] # this output will be displayed using php and html
+----------+-------+------+-----+-----+
| products | Total | john | bob | kim |
+----------+-------+------+-----+-----+
| shoe | 68 | 20 | 2 | 46 |
+----------+-------+------+-----+-----+
| belt | 110 | 9 | 98 | 3 |
+----------+-------+------+-----+-----+
I am trying to write a query which will allow me to produce the table [query output]. Any help will be much appreciated.
Thank you very much.
A version without using cross_tab:
select p.product_name, sum(s.qty),
sum(CASE WHEN c.client_name='john' THEN s.qty END) as john,
sum(CASE WHEN c.client_name='bob' THEN s.qty END) as bob,
sum(CASE WHEN c.client_name='kim' THEN s.qty END) as kim
from products_tb AS p
JOIN sales_tb AS s ON p.product_name=s.product_name
JOIN clients_tb AS c ON c.client_name=s.client_name
GROUP BY p.product_name;
http://sqlfiddle.com/#!12/afc9a/10
Here's one way of doing it using standard SQL:
SELECT st.product_name AS products,
SUM(st.qty) AS Total,
(SELECT SUM(st_john.qty)
FROM sales_tb st_john
WHERE client_name = 'john' AND st_john.product_name = st.product_name) AS john,
(SELECT SUM(st_bob.qty)
FROM sales_tb st_bob
WHERE client_name = 'bob' AND st_bob.product_name = st.product_name) AS bob,
(SELECT SUM(st_kim.qty)
FROM sales_tb st_kim
WHERE client_name = 'kim' AND st_kim.product_name = st.product_name) AS kim
FROM sales_tb st
GROUP BY product_name
See SQL Fiddle Demo