Choosing one record ouf of different conditions - postgresql

Given these two tables:
studyprint:
create table studyprint(
idstudyprint serial not null,
empresa varchar(4),
remoteaddress varchar(100),
primary key(idstudyprint)
);
insert into studyprint(empresa, remoteaddress) values('TEST', '');
insert into studyprint(empresa, remoteaddress) values('GAM', '');
insert into studyprint(empresa, remoteaddress) values('GAM', '');
insert into studyprint(empresa, remoteaddress) values('TEST', '192.168.0.100');
insert into studyprint(empresa, remoteaddress) values('TEST', '192.168.0.25');
idstudyprint | empresa | remoteaddress
--------------+---------+---------------
1 | TEST |
2 | GAM |
3 | GAM |
4 | TEST | 192.168.0.100
5 | TEST | 192.168.0.25
printprofiles
create table printprofiles(
idprintprofile serial not null,
empresa varchar(4),
remoteaddress varchar(100),
primary key(idprintprofile)
);
insert into printprofiles(empresa, remoteaddress) values('PDF', '');
insert into printprofiles(empresa, remoteaddress) values('HPR', '');
insert into printprofiles(empresa, remoteaddress) values('GAM', '');
insert into printprofiles(empresa, remoteaddress) values('TEST', '192.168.0.100');
insert into printprofiles(empresa, remoteaddress) values('TEST', '');
idprintprofile | empresa | remoteaddress
----------------+---------+---------------
1 | PDF |
2 | HPR |
3 | GAM |
4 | TEST | 192.168.0.100
5 | TEST |
The first query I came up with is this:
select
sp.idstudyprint, sp.empresa, pp.idprintprofile, sp.remoteaddress
from studyprint sp
join printprofiles pp on pp.empresa=sp.empresa
where
pp.remoteaddress = sp.remoteaddress or(pp.remoteaddress = '');
Which results in:
idstudyprint | empresa | idprintprofile | remoteaddress
--------------+---------+----------------+---------------
1 | TEST | 5 |
2 | GAM | 3 |
3 | GAM | 3 |
4 | TEST | 5 | 192.168.0.100
4 | TEST | 4 | 192.168.0.100
5 | TEST | 5 | 192.168.0.25
As there are cases where a remoteaddress in studyprint doesn't match a remoteaddress in printprofiles, in those cases the selection must be the empresa that matches (in my example, empresa=5 has remoteaddress empty, there should fit all the remoteaddresses that doesn't match), for example:
idstudyprint | empresa | idprintprofile | remoteaddress
--------------+---------+----------------+---------------
1 | TEST | 5 |
2 | GAM | 3 |
3 | GAM | 3 |
4 | TEST | 4 | 192.168.0.100
5 | TEST | 5 | 192.168.0.25

Actually, you didn't write a full detailed explanation, but still, based on the data you wrote, I wrote the query. The result is what you want. Let me know if you have any other questions, I can help.
I changed my query:
select
t1.idstudyprint,
t1.empresa,
case when t2.idprintprofile is null then t3.idprintprofile else t2.idprintprofile end as idprintprofile,
t1.remoteaddress
from studyprint t1
left join
printprofiles t2
on (t1.empresa = t2.empresa and t1.remoteaddress = t2.remoteaddress)
left join
printprofiles t3
on t2.idprintprofile is null and t1.empresa = t3.empresa and t3.remoteaddress=''
--Result:
idstudyprint | empresa | idprintprofile | remoteaddress |
-------------+---------+----------------+---------------+
1 | TEST | 5 | |
2 | GAM | 3 | |
3 | GAM | 3 | |
4 | TEST | 4 | 192.168.0.100 |
5 | TEST | 5 | 192.168.0.25 |

Related

Create a PostgreSQL function that becomes a formula field of a table retrieving related data from other table

The example above can be done on a SQL Server. It is a function that performs the calculation on another table while getting the current table field Id to list data from other table, return a single value.
Question: how to do the exact thing with PostgreSQL
SELECT TOP(5) * FROM Artists;
+------------+------------------+--------------+-------------+
| ArtistId | ArtistName | ActiveFrom | CountryId |
|------------+------------------+--------------+-------------|
| 1 | Iron Maiden | 1975-12-25 | 3 |
| 2 | AC/DC | 1973-01-11 | 2 |
| 3 | Allan Holdsworth | 1969-01-01 | 3 |
| 4 | Buddy Rich | 1919-01-01 | 6 |
| 5 | Devin Townsend | 1993-01-01 | 8 |
+------------+------------------+--------------+-------------+
SELECT TOP(5) * FROM Albums;
+-----------+------------------------+---------------+------------+-----------+
| AlbumId | AlbumName | ReleaseDate | ArtistId | GenreId |
|-----------+------------------------+---------------+------------+-----------|
| 1 | Powerslave | 1984-09-03 | 1 | 1 |
| 2 | Powerage | 1978-05-05 | 2 | 1 |
| 3 | Singing Down the Lane | 1956-01-01 | 6 | 3 |
| 4 | Ziltoid the Omniscient | 2007-05-21 | 5 | 1 |
| 5 | Casualties of Cool | 2014-05-14 | 5 | 1 |
+-----------+------------------------+---------------+------------+-----------+
The function
CREATE FUNCTION [dbo].[ufn_AlbumCount] (#ArtistId int)
RETURNS smallint
AS
BEGIN
DECLARE #AlbumCount int;
SELECT #AlbumCount = COUNT(AlbumId)
FROM Albums
WHERE ArtistId = #ArtistId;
RETURN #AlbumCount;
END;
GO
Now, (at SQL Server), after update the first table fields with ALTER TABLE Artists ADD AlbumCount AS dbo.ufn_AlbumCount(ArtistId); whe can list and get the following result.
+------------+------------------+--------------+-------------+--------------+
| ArtistId | ArtistName | ActiveFrom | CountryId | AlbumCount |
|------------+------------------+--------------+-------------+--------------|
| 1 | Iron Maiden | 1975-12-25 | 3 | 5 |
| 2 | AC/DC | 1973-01-11 | 2 | 3 |
| 3 | Allan Holdsworth | 1969-01-01 | 3 | 2 |
| 4 | Buddy Rich | 1919-01-01 | 6 | 1 |
| 5 | Devin Townsend | 1993-01-01 | 8 | 3 |
| 6 | Jim Reeves | 1948-01-01 | 6 | 1 |
| 7 | Tom Jones | 1963-01-01 | 4 | 3 |
| 8 | Maroon 5 | 1994-01-01 | 6 | 0 |
| 9 | The Script | 2001-01-01 | 5 | 1 |
| 10 | Lit | 1988-06-26 | 6 | 0 |
+------------+------------------+--------------+-------------+--------------+
but how to achieve this on postgresql?
Postgres doesn't support "virtual" computed column (i.e. computed columns that are generated at runtime), so there is no exact equivalent. The most efficient solution is a view that counts this:
create view artists_with_counts
as
select a.*,
coalesce(t.album_count, 0) as album_count
from artists a
left join (
select artist_id, count(*) as album_count
from albums
group by artist_id
) t on a.artist_id = t.artist_id;
Another option is to create a function that can be used as a "virtual column" in a select - but as this is done row-by-row, this will be substantially slower than the view.
create function album_count(p_artist artists)
returns bigint
as
$$
select count(*)
from albums a
where a.artist_id = p_artist.artist_id;
$$
language sql
stable;
Then you can include this as a column:
select a.*, a.album_count
from artists a;
Using the function like that, requires to prefix the function reference with the table alias (alternatively, you can use album_count(a))
Online example

TSQL - PIVOT but CONCATENATE Fields

in this thread I was assisted with my initial question. The answer supplied has been accepted because it was the actual answer to that question.
As an extention to that answer, please consider the same table:
+------------------------------------------------------------------------------+
| GUID | DeviceGUID | DetailGUID | sValue | iValue | gValue | DateStored |
| ENTRY1 | DEVICE1 | Detail1 | SN112 | | | 01/01/2020 |
| ENTRY2 | DEVICE1 | Detail4 | | 1241 | | 01/01/2020 |
| ENTRY3 | DEVICE1 | Detail7 | | | GUID12 | 01/01/2020 |
| ENTRY8 | DEVICE1 | Detail7 | | | GUID13 | 01/01/2020 |
| ENTRY9 | DEVICE1 | Detail7 | | | GUID14 | 01/01/2020 |
| ENTRY4 | DEVICE2 | Detail1 | SN111 | | | 01/01/2020 |
| ENTRY5 | DEVICE2 | Detail2 | RND123 | | | 01/01/2020 |
| ENRTY6 | DEVICE2 | Detail4 | | 2351 | | 03/01/2020 |
| ENTRY7 | DEVICE3 | Detail1 | SN100 | | | 02/01/2020 |
| [...] | [...] | [...] | | | | |
| | | | | | | |
+------------------------------------------------------------------------------+
The issue arises when there are multiple records with the same DetailGUID; PIVOT has been set to select 'MAX', I would not know exactly how that selects the actual record in this case, but that is not important.
Insteas of selecting one record and having it displayed, I need the records to be concatenated in a Comma Separated List, in the Pivot.
the current SQL query is as follows:
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX), #OrderGUID uniqueidentifier;
SET #OrderGUID = '1B470FFB-7410-4950-A3BC-B9D778C459D3';
SET #columns = N'';
SELECT #columns+=N', p.'+QUOTENAME([Name])
FROM
(
SELECT GUID AS [Name]
FROM [dbo].Details AS p
) AS x;
SET #sql =
N'
SELECT *
FROM
(
SELECT DeviceObjectGUID
,DetailGUID
,CONCAT(sValue, iValue, gValue) as [value]
,DateStored
FROM DeviceDetails
WHERE (DeviceObjectGUID IN (SELECT DeviceObjectGUID FROM DevicesPerOrder WHERE OrderGUID = ''' + CAST(#OrderGUID as nVarchar(MAX) )+ '''))
) DS
PIVOT
(MAX([value]) FOR DetailGUID IN ('+STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')+')) PVT';
EXEC sp_executesql #sql
this will dynamically select all the detailGUIDS and transform them to headers; but I am unsure where I would start to input the CONCAT or TO XML statements

Select common values when using group by [Postgres]

I have three main tables meetings, persons, hobbies with two relational tables.
Table meetings
+---------------+
| id | subject |
+----+----------+
| 1 | Kickoff |
| 2 | Relaunch |
| 3 | Party |
+----+----------+
Table persons
+------------+
| id | name |
+----+-------+
| 1 | John |
| 2 | Anna |
| 3 | Linda |
+----+-------+
Table hobbies
+---------------+
| id | name |
+----+----------+
| 1 | Soccer |
| 2 | Tennis |
| 3 | Swimming |
+----+----------+
Relation Table meeting_person
+-----------------+-----------+
| id | meeting_id | person_id |
+----+------------+-----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+------------+-----------+
Relation Table person_hobby
+----------------+----------+
| id | person_id | hobby_id |
+----+-----------+----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+-----------+----------+
Now I want to to find the common hobbies of all person attending each meeting.
So the desired result would be:
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
| | (Aggregated) | (Aggregated) |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer |
| 2 | John,Anna | Soccer,Tennis |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
My current work in progress is:
select
m.id as "meeting_id",
(
select string_agg(distinct p.name, ',')
from meeting_person mp
inner join persons p on mp.person_id = p.id
where m.id = mp.meeting_id
) as "persons",
string_agg(distinct h2.name , ',') as "common_hobbies"
from meetings m
inner join meeting_person mp2 on m.id = mp2.meeting_id
inner join persons p2 on mp2.person_id = p2.id
inner join person_hobby ph2 on p2.id = ph2.person_id
inner join hobbies h2 on ph2.hobby_id = h2.id
group by m.id
But this query lists not the common_hobbies but all hobbies which are at least once mentioned.
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer,Tennis,Swimming |
| 2 | John,Anna | Soccer,Tennis,Swimming |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
Does anyone have any hints for me, on how I could solve this problem?
Cheers
This problem can be solved by implement custom aggregation function (found it here):
create or replace function array_intersect(anyarray, anyarray)
returns anyarray language sql
as $$
select
case
when $1 is null then $2
when $2 is null then $1
else
array(
select unnest($1)
intersect
select unnest($2))
end;
$$;
create aggregate array_intersect_agg (anyarray)
(
sfunc = array_intersect,
stype = anyarray
);
So, the solution can be next:
select
meeting_id,
array_agg(ph.name) persons,
array_intersect_agg(hobby) common_hobbies
from meeting_person mp
join (
select p.id, p.name, array_agg(h.name) hobby
from person_hobby ph
join persons p on ph.person_id = p.id
join hobbies h on h.id = ph.hobby_id
group by p.id, p.name
) ph on ph.id = mp.person_id
group by meeting_id;
Look the example fiddle
Result:
meeting_id | persons | common_hobbies
-----------+-----------------------+--------------------------
1 | {John,Anna,Linda} | {Soccer}
3 | {John} | {Soccer,Tennis,Swimming}
2 | {John,Anna} | {Soccer,Tennis}

PostgreSQL limit by group, only show first 2 store options

I need to select first 2 lines where the store_name is different than one given for a given product
id | store_name | prod_name
----+------------+------
1 | 1 | A
2 | 1 | B
3 | 1 | C
4 | 1 | A
5 | 2 | E
6 | 2 | A
7 | 3 | G
8 | 2 | A
9 | 1 | A
10 | 3 | A
(10 rows)
result should be store_name <> 3 AND prod_name ='A'
id | store_name | prod_name
----+------------+------
1 | 1 | A
4 | 1 | A
6 | 2 | A
8 | 2 | A
Use the row_number() window function to accomplish this.
Query #1
with first_two as (
select *,
row_number() over (partition by store_name
order by id) as rn
from store_product
where store_name <> 3
and prod_name = 'A'
)
select id, store_name, prod_name
from first_two
where rn <= 2;
| id | store_name | prod_name |
| --- | ---------- | --------- |
| 1 | 1 | A |
| 4 | 1 | A |
| 6 | 2 | A |
| 8 | 2 | A |
View on DB Fiddle

Postgres values as columns

I am working with PostgreSQL 9.3, and I have this:
PARENT_TABLE
ID | NAME
1 | N_A
2 | N_B
3 | N_C
CHILD_TABLE
ID | PARENT_TABLE_ID | KEY | VALUE
1 | 1 | K_A | V_A
2 | 1 | K_B | V_B
3 | 1 | K_C | V_C
5 | 2 | K_A | V_D
6 | 2 | K_C | V_E
7 | 3 | K_A | V_F
8 | 3 | K_B | V_G
9 | 3 | K_C | V_H
Note that I might add K_D in KEY's, it's completely dynamic.
What I want is a query that returns me the following:
QUERY_TABLE
ID | NAME | K_A | K_B | K_C | others K_...
1 | N_A | V_A | V_B | V_C | ...
2 | N_B | V_D | | V_E | ...
3 | N_C | V_F | V_G | V_H | ...
Is this possible to do ? If so, how ?
Since there can be values missing, you need the "safe" form of crosstab() with the column names as second parameter:
SELECT * FROM crosstab(
'SELECT p.id, p.name, c.key, c."value"
FROM parent_table p
LEFT JOIN child_table c ON c.parent_table_id = p.id
ORDER BY 1'
,$$VALUES ('K_A'::text), ('K_B'), ('K_C')$$)
AS t (id int, name text, k_a text, k_b text, k_c text; -- use actual data types
Details in this related answer:
PostgreSQL Crosstab Query
About adding "extra" columns:
Pivot on Multiple Columns using Tablefunc