Trying to GROUP BY with CASE WHEN THEN - postgresql - sql

I have three tables PATH, ELEMENTS and ELEMENT_DETAILS with the following structure:
PATH:
ID
1
2
3
ELEMENTS:
ID | PATH_ID | DIRECTION | ELEMENT_DETAILS_ID
1 1 'left' 1
2 1 'right' 2
3 2 'left' 3
4 2 'right' 2
ELEMENT_DETAILS:
ID | NAME
1 'Henry'
2 'Mark'
3 'John'
I would like the result to be like so:
ID | left | right
1 'Henry' 'Mark'
2 'John' 'Mark'
This is the SQL I have come up with so far:
SELECT path.id,
CASE WHEN elements.direction='left' THEN element_details.name
ELSE NULL END
as left,
CASE WHEN elements.direction='right' THEN element_details.name
ELSE NULL END
as right,
FROM elements
INNER JOIN path on elements.path_id = path.id
LEFT JOIN element_details on elements.element_details_id = element_details.id
GROUP BY path.id
ORDER BY path.id
However, this does not work since postgres gives me an error saying elements.direction should be in group by. And including elements.direction in group_by does not give me aggregation at path.id level.
Stuck on this. Can someone please help.
I am using Postgres version 9.5

You need aggregation. Here is one method:
SELECT p.id,
MAX(CASE WHEN e.direction = 'left' THEN ed.name
END) as left,
MAX(CASE WHEN e.direction = 'right' THEN ed.name
END) as right
FROM elements e INNER JOIN
path p
ON e.path_id = p.id LEFT JOIN
element_details ed
ON e.element_details_id = ed.id
GROUP BY p.id
ORDER BY p.id

Related

SQL - must return 0 if GROUP_CONCAT value is null

I just want to not return any row if no result!
I'm so null in sql that it depresses me, please help..
I have 2 tables with 1 association table :
shop table:
id
name
adress
1
name1
1 street..
2
name2
2 street..
3
name3
3 street..
activity table:
id
title
1
Distribution
2
Importation
3
Préparation
4
Production
shop_activity association table:
shop_id
activity_id
1
1
2
2
3
3
3
4
I found this request to concat activities:
SELECT s.*, GROUP_CONCAT(a.title SEPARATOR ',') AS activities
FROM shop s
LEFT JOIN shop_activity sa
ON s.id = sa.shop_id
LEFT JOIN activity a
ON sa.activity_id = a.id
WHERE s.id = ?
and it does the job
id
name
adress
activities
3
name3
3 street..
Production, Importation
but if s.id doesn't exist in table shop, currently I have this result:
id
name
adress
activities
NULL
NULL
NULL
NULL
but I don't want any row if all is null to return a message, currently I make a condition on id column and I know that it isn't very clean!
Try just join instead of LEFT:
SELECT s.*, GROUP_CONCAT(a.title SEPARATOR ',') AS activities
FROM shop s
JOIN shop_activity sa
ON s.id = sa.shop_id
JOIN activity a
ON sa.activity_id = a.id
WHERE s.id = ?
I've found: just group by
SELECT s.*, GROUP_CONCAT(a.title SEPARATOR ',') AS activities
FROM shop s
LEFT JOIN shop_activity sa ON s.id = sa.shop_id
LEFT JOIN activity a ON sa.activity_id = a.id
WHERE s.id = ?
GROUP By s.id

join two SQL rows in a single one

I have three tables in Postgresql, for a biological classification system.
table lang (languages)
id name
1 português
2 english
-------------------------------
table taxon (biological groups)
id name
...
101 Mammalia
-------------------------------
table pop (popular names)
id tax lang pop
...
94 101 1 mamíferos
95 101 2 mammals
I want to get
id name namePT nameEN
101 Mammalia mamíferos mammals
but my join is giving me
id name pop
101 Mammalia mamíferos
101 Mammalia mammals
select t.id,name,pop from taxon t
left join pop p on p.tax = t.id
where t.id = 101
How can I get the desired result in a single row?
If you are happy to change query every time you add a new language then this query will do the trick:
select t.id,name,pe.pop as eng_pop, pp.pop as port_pop
from taxon t
left join pop pe on pe.tax = t.id and pe.lang = 1
left join pop pp on pp.tax = t.id and pp.lang = 2
where t.id = 101
You could use this
SELECT t.id, t.name,
MAX(CASE WHEN p.lang = 1 THEN p.pop END) AS namePT,
MAX(CASE WHEN p.lang = 2 THEN p.pop END) AS nameEN
FROM taxon t
LEFT JOIN pop p
ON p.tax = t.id
GROUP BY t.id, t.name;
Here's how I got the results:
with base as (
select t.id, t.name,
case when lang = 1 then 'mamiferos' else null end as namePT,
case when lang = 2 then 'mamals' else null end as nameEN
from taxon t
left join pop p on t.id = p.tax
group by 1,2,3, p.lang
)
select
distinct id,
name,
coalesce(namept,'mamiferos',null) as namept,
coalesce(nameen,'mamals',null) as nameen
from base
where id = 101
group by id, name, namept, nameen;
id | name | namept | nameen
-----+----------+-----------+--------
101 | Mammalia | mamiferos | mamals
(1 row)

Select all categories with COUNT of sub-categories

I need to select all categories with count of its sub-categories.
Assume here are my tables:
categories
id | title
----------
1 | colors
2 | animals
3 | plants
sub_categories
id | category_id | title | confirmed
------------------------------------
1 1 red 1
2 1 blue 1
3 1 pink 1
4 2 cat 1
5 2 tiger 0
6 2 lion 0
What I want is :
id | title | count
------------------
1 colors 3
2 animals 1
3 plants 0
What I have tried so far:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id
WHERE c.confirmed = 't' AND s.confirmed='t'
GROUP BY c.id, c.title
ORDER BY count DESC
The only problem with this query is that this query does not show categories with 0 sub categories!
You also can check that on SqlFiddle
Any help would be great appreciated.
The reason you don't get rows with zero counts is that WHERE clause checks s.confirmed to be t, thus eliminating rows with NULLs from the outer join result.
Move s.confirmed check into join expression to fix this problem:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id AND s.confirmed='t'
WHERE c.confirmed = 't'
GROUP BY c.id, c.title
ORDER BY count DESC
Adding Sql Fiddle: http://sqlfiddle.com/#!17/83add/13
I think you can try this too (it evidence what column(s) you are really grouping by):
SELECT c.id, c.title, RC
from categories c
LEFT JOIN (SELECT category_id, COUNT(*) AS RC
FROM sub_categories
WHERE confirmed= 't'
GROUP BY category_id) s on c.id = s.category_id
WHERE c.confirmed = 't'
ORDER BY RC DESC

how to prevent duplicates with inner join query (Postgres)

I am trying to understand how to create a query to filter out some results based on an inner join.
Consider the following data:
formulation_batch
-----
id project_id name
1 1 F1.1
2 1 F1.2
3 1 F1.3
4 1 F1.all
formulation_batch_component
-----
id formulation_batch_id component_id
1 1 1
2 2 2
3 3 3
4 4 1
5 4 2
6 4 3
7 4 4
I would like to select all formulation_batch records with a project_id of 1, and has a formulation_batch_component with a component_id of 1 or 2. So I run the following query:
SELECT formulation_batch.*
FROM formulation_batch
INNER JOIN formulation_batch_component
ON formulation_batch.id = formulation_batch_component.formulation_batch_id
WHERE formulation_batch.project_id = 1
AND ((formulation_batch_component.component_id = 2
OR formulation_batch_component.component_id = 1 ))
However, this returns a duplicate entry:
1;"F1.1"
2;"F1.2"
4;"F1.all"
4;"F1.all"
Is there a way to modify this query so that I only get back the unique formulation_batch records which match the criteria?
EG:
1;"F1.1"
2;"F1.2"
4;"F1.all"
Thanks for your time!
In this case it is possible to apply the distinct before the join possibly making it more performant:
select fb.*
from
formulation_batch fb
inner join
(
select distinct formulationbatch_id
from formulation_batch_component
where component_id in (1, 2)
) fbc on fb.id = fbc.formulationbatch_id
where fb.project_id = 1
Notice how to use alias for the table names to make the query clearer. Also then in operator is very handy. The use of double quotes with those identifiers is not necessary.
One way would be to use distinct:
SELECT distinct "formulation_batch".*
FROM "formulation_batch"
INNER JOIN "formulation_batch_component"
ON "formulation_batch"."id" = "formulation_batch_component"."formulationBatch_id"
WHERE "formulation_batch"."project_id" = 1
AND (("formulation_batch_component"."component_id" = 2
OR "formulation_batch_component"."component_id" = 1 ))
I know the question asks how to prevent duplicates with inner join but could use an IN clause in the predicate.
SELECT "formulation_batch".*
FROM "formulation_batch" fb
ON "formulation_batch"."id" = "formulation_batch_component"."formulationBatch_id"
WHERE "formulation_batch"."project_id" = 1
AND fb.id IN (SELECT "formulation_batch"."id"
FROM formulation_batch_component
WHERE (("formulation_batch_component"."component_id" = 2
OR "formulation_batch_component"."component_id" = 1 ))

SQL simple query

I have the following table, Persons_Companies, that shows a relation between persons and companies knowns by these persons:
PersonID | CompanyID
1 1
2 1
2 2
3 2
4 2
Imagining that company 1 = "Google" and company 2 is = "Microsoft", I would like to know the query to have the following result:
PersonID | Microsoft | Google
1 0 1
2 1 1
3 1 0
4 1 0
Until this moment I have something similar:
select PersonID,
case when CompanyID=1 then 1 else 0
end as Google,
case when EmpresaID=2 then 1 else 0
end as Microsoft
from Persons_Companies
My problem is with the persons that knows both companies, I can't imagine how could this query be.
What is the SQL query?
select PersonID,
case when EXISTS (
SELECT 1
FROM Persons_Companies pc1
WHERE pc.PersonID = pc1.PersonID and pc1.CompanyID = 1 ) then 1 else 0
end as Google,
case when EXISTS (
SELECT 1
FROM Persons_Companies pc2
WHERE pc.PersonID = pc2.PersonID and pc2.CompanyID = 2 ) then 1 else 0
end as Microsoft
from Persons_Companies pc
SELECT personId, sum(case companyId when 1 then 1 else 0 end) google,
sum(case companyId when 2 then 1 else 0 end) microsoft
from Persons_Companies
group by personId
order by personId;
I think this is what you want: http://pastie.org/881092
select
p.person_id,
if(ms.company_id is null,0,1) as 'microsoft',
if(ora.company_id is null,0,1) as 'oracle',
if(mysql.company_id is null,0,1) as 'mysql'
from
person p
left outer join person_company ms on p.person_id = ms.person_id and ms.company_id = 1
left outer join person_company ora on p.person_id = ora.person_id and ora.company_id = 2
left outer join person_company mysql on p.person_id = mysql.person_id and mysql.company_id = 3
order by
p.person_id;
There is a problem with both answers, because there is an assumption that Google and Microsoft will always be the only companies on the table. I believe the query should be generic.
I am not too sure but I think a combination of a cross tab and CTE will work well.