Postgresql query with many phones for one people [duplicate] - postgresql

This question already has answers here:
How to concatenate strings of a string field in a PostgreSQL 'group by' query?
(14 answers)
Closed 8 months ago.
I have two tables, one for people and another for telephone, and some people may have more than one telephone. In a query with LEFT JOIN, it is returning duplicated lines according to the number of telephones. I would like to generate a query that returns something like the table below. Is it possible?
id | names | phones
-----------------------------------
id1 | Name1 | phone1,phone2,phone3
id2 | Name2 | phone1
id3 | Name3 | phone1
id4 | Name4 | phone1,phone2

Use function string_agg
select p.pid,p.name,string_agg(t.tval,',') from people p
left join telephone t
on p.tid = t.tid
group by p.pid, p.name
order by p.pid;
Refer fiddle here.

Related

PostgreSQL and inheritance [duplicate]

This question already has answers here:
Find Parent Recursively using Query
(2 answers)
Closed 3 years ago.
I have a table with this format:
Table name : identities
code |parent_code|
_______|___________|
AAA | Null |
AAB | AAA |
AAC | Null |
AAD | AAC |
AAE | AAB |
And I need a way to obtain the highest parent of any "code" in the table.
For instance, if I wanted to get the highest parent of the "code" AAE I would get as a result AAA, since the parent of AAE is AAB and the parent of AAB is AAA, and AAA would be the highest because it has no parent assosiated.
My problem is that I can´t modify the tables and I don´t know how to use "with recursive".
Thank you in advance.
You can use a recursive cte as follows:
with recursive cte as (
select code, parent_code, 1 lvl from identities where code = 'AAE'
union all
select i.code, i.parent_code, lvl + 1
from identities i
inner join cte c on c.parent_code = i.code
)
select code
from cte
where lvl = (select max(lvl) from cte)
Demo on DB Fiddle:
| code |
| :--- |
| AAA |

Postgresql how to concatenate strings but only when values are different?

I want to create a view that is aggregating my column "country". My table looks like this:
project_ref | country
----------------------
1 | Italy
1 | Italy
2 | France
2 | Italy
Currently, I run the following query:
CREATE VIEW a AS
SELECT project_ref,
string_agg(country, ', ') AS country
FROM b GROUP BY project_ref ORDER BY project_num ASC;
and I get the following table as a result:
project_ref | country
----------------------------
1 | Italy, Italy
2 | France, Italy
Is there a way to remove the duplicated values "Italy, Italy" in order to have "Italy" mentioned only once?
I would like to have the following table instead:
project_ref | country
---------------------------
1 | Italy
2 | France, Italy
But I can't find the way to get there... Any ideas?
I'm using PostgreSQL 9.4.5 version.
Thanks a lot in advance!
Just add distinct inside string_agg:
string_agg(distinct country, ', ')
You can use a subquery to remove the duplicate records and create an array with them. If you want to store the country collections as text separated by comma, use the function ARRAY_TO_STRING as follows:
CREATE VIEW a AS
SELECT project_ref,
ARRAY_TO_STRING(ARRAY(SELECT DISTINCT country
FROM b q2
WHERE q1.project_ref = q2.project_ref),',') AS country
FROM b q1
GROUP BY project_ref
And here is your view without the duplicates:
db=# SELECT * FROM a;
project_ref | country
-------------+-----------------
1 | Italy
2 | France,Italy
(2 Zeilen)
An advantage of this approach is that you can run your DISTINCT with more than one column, by means of using DISTINCT ON (colmun1, column2, ...).

How to use COUNT() in more that one column?

Let's say I have this 3 tables
Countries ProvOrStates MajorCities
-----+------------- -----+----------- -----+-------------
Id | CountryName Id | CId | Name Id | POSId | Name
-----+------------- -----+----------- -----+-------------
1 | USA 1 | 1 | NY 1 | 1 | NYC
How do you get something like
---------------------------------------------
CountryName | ProvinceOrState | MajorCities
| (Count) | (Count)
---------------------------------------------
USA | 50 | 200
---------------------------------------------
Canada | 10 | 57
So far, the way I see it:
Run the first SELECT COUNT (GROUP BY Countries.Id) on Countries JOIN ProvOrStates,
store the result in a table variable,
Run the second SELECT COUNT (GROUP BY Countries.Id) on ProvOrStates JOIN MajorCities,
Update the table variable based on the Countries.Id
Join the table variable with Countries table ON Countries.Id = Id of the table variable.
Is there a possibility to run just one query instead of multiple intermediary queries? I don't know if it's even feasible as I've tried with no luck.
Thanks for helping
Use sub query or derived tables and views
Basically If You You Have 3 Tables
select * from [TableOne] as T1
join
(
select T2.Column, T3.Column
from [TableTwo] as T2
join [TableThree] as T3
on T2.CondtionColumn = T3.CondtionColumn
) AS DerivedTable
on T1.DepName = DerivedTable.DepName
And when you are 100% percent sure it's working you can create a view that contains your three tables join and call it when ever you want
PS: in case of any identical column names or when you get this message
"The column 'ColumnName' was specified multiple times for 'Table'. "
You can use alias to solve this problem
This answer comes from #lotzInSpace.
SELECT ct.[CountryName], COUNT(DISTINCT p.[Id]), COUNT(DISTINCT c.[Id])
FROM dbo.[Countries] ct
LEFT JOIN dbo.[Provinces] p
ON ct.[Id] = p.[CountryId]
LEFT JOIN dbo.[Cities] c
ON p.[Id] = c.[ProvinceId]
GROUP BY ct.[CountryName]
It's working. I'm using LEFT JOIN instead of INNER JOIN because, if a country doesn't have provinces, or a province doesn't have cities, then that country or province doesn't display.
Thanks again #lotzInSpace.

using LOWER with IN condition [duplicate]

This question already has answers here:
PostgreSQL: Case insensitive string comparison
(6 answers)
Closed 6 years ago.
assume I have a table named comodity_group and the structure looks like:
+----------+-------+
| group_id | name |
+----------+-------+
| 1 | Data1 |
+----------+-------+
| 2 | Data2 |
+----------+-------+
| 3 | data3 |
+----------+-------+
and I have the following query
SELECT * FROM comodity_group WHERE name IN('data1','data2','data3')
the query return 0 result, because condition is all in lowercase (note that the condition is also dynamic, meaning it can be Data1 or daTa1, etc)
so I want to make both condition and field name in lowercase, in other word case insensitive.
You can use ILIKE and an array:
select *
from comodity_group
where name ilike any (array['Data1', 'data2', 'dATA3']);
Note that this won't be really fast as the ILIKE operator can't make use of a regular index on the name column.
You can convert your name data to lowercase
SELECT * FROM comodity_group WHERE lower(name) IN('data1','data2','data3')
Assuming you have control over the terms which appear in the IN clause of your query, then you should only need to lowercase the name column before making the comparison:
SELECT *
FROM commodity_group
WHERE LOWER(name) IN ('data1', 'data2', 'data3')
Off the top of my head, you could also join to an inline table containing the search terms:
WITH cte AS (
SELECT 'daTa1' AS name
UNION ALL
SELECT 'Data2'
UNION ALL
SELECT 'datA3'
)
SELECT *
FROM commodity_group t1
INNER JOIN cte t2
ON LOWER(t1.name) = LOWER(t2.name)
With the possible matches in an actual table, we now have the ability to lowercase both sides of the comparison.

postgres offset by value not number

I have a table with at least a "name" column and an "ordinal_position" column. I wish to loop each row starting from a certain row the user inputs. Let's say the user inputs "John", and that his ordinal_position is 6 (out of a 10 total). How do I loop only the last 4 rows without using a subquery? I've tried using the "OVER()" window function but it doesn't seem to work on the offset part of the query, and that same offset only takes numbers (as far as I know) not strings.
EDIT (in response to klin):
INSERT INTO foo(id,name,ordinal_position) VALUES
(DEFAULT,'Peter',1),
(DEFAULT,'James',2),
(DEFAULT,'Freddy',3),
(DEFAULT,'Mark',4),
(DEFAULT,'Jack',5),
(DEFAULT,'John',6),
(DEFAULT,'Will',7),
(DEFAULT,'Robert',8),
(DEFAULT,'Dave',9),
(DEFAULT,'Michael',10);
so in my FOR, since the user inputed "John" I want to loop through Will-Michael. Something like the following but without a subquery:
SELECT * FROM foo ORDER BY ordinal_position OFFSET
(SELECT ordinal_position FROM foo WHERE name='John');
Unfortunately, you have to query the table to find an ordinal_position for a given name.
However, do not use offset. You can do it in where clause, for large tables it will be much faster:
select *
from foo
where ordinal_position > (select ordinal_position from foo where name = 'John')
order by ordinal_position;
id | name | ordinal_position
----+---------+------------------
7 | Will | 7
8 | Robert | 8
9 | Dave | 9
10 | Michael | 10
(4 rows)