string_agg: more than two attributes concatenation - postgresql

I am using postgresql 9.0
I am wonder if its possible to concatenate three attributes together.
this is how I concatenate two attributes (book & the comma):
SELECT string_agg(book, ',') FROM authors where id = 1;
| book1,book2,book3|
--------------------
how can I do something like below:
SELECT string_agg(name, ':', book, ',') FROM authors where id = 1;
| Ahmad: book1,book2,book3|
----------------
can some one help? thanks.

Just concatenate the fields like this:
SELECT name || ':' || string_agg(book, ',') FROM authors where id = 1;
Edit:
If your SQL returns multiple names you need to group by name (if you have multiple authors with the same name it gets a bit more complicated. I won't cover that case in this answer):
SELECT name || ':' || string_agg(book, ',')
FROM authors where id = 1
GROUP BY name;
If you want the books in alphabetical order you can add an ORDER BY for the books:
SELECT name || ':' || string_agg(book, ',') WITHIN GROUP ORDER BY book
FROM authors where id = 1
GROUP BY name;

SELECT name || ': ' || string_agg(book, ',') FROM authors where id = 1 group by name ;

Related

DB2 Xml framing logics

SELECT XMLSERIALIZE(
XMLELEMENT(
NAME "row",
XMLFOREST(
A.TITLE AS "title",
A.TAG as "tag" )))
FROM ARTICLES A;
Expected Result:
Instead of Mentioned Column name(TITLE ,TAG) ,shall we able to keep '*' (means - select * from Articles)
because my table contains 150 columns.
As #data_henrik said, your question it not clear ...
But If I got you right, you want to create a xml doc for all columns from your base table, without having to code all 150 columns in the query.
I don't know a direct way of doing it, but you can do it in a 2 step way.
1st step will be a SELECT statement, that will BUILD your final SELECT based on the columns from your base table..
Let's take DEPARTMENT table from sample db as an example:
db2 => select * from department
DEPTNO DEPTNAME MGRNO ADMRDEPT LOCATION
------ ------------------------------------ ------ -------- ----------------
A00 SPIFFY COMPUTER SERVICE DIV. 000010 A00 -
B01 PLANNING 000020 A00 -
C01 INFORMATION CENTER 000030 A00 -
...
it has 5 columns. The following select will build your final SELECT ...
select
'SELECT XMLROW( ' ||
listagg(rtrim(colname) || ' AS "' || lcase(rtrim(colname)) || '"' , ', ')
|| ' ) FROM DEPARTMENT'
from syscat.columns
where tabname = 'DEPARTMENT'
------------------------------------------------------------------------------------------------------------
SELECT XMLROW( ADMRDEPT AS "admrdept", DEPTNAME AS "deptname", DEPTNO AS "deptno", LOCATION AS "location", MGRNO AS "mgrno" ) FROM DEPARTMENT
If you execute the resulting SELECT. it will produce the xml, similar to what you want.
----------------------------------------------------------------------------------------------------------
<row><admrdept>A00</admrdept><deptname>SPIFFY COMPUTER SERVICE DIV.</deptname><deptno>A00</deptno><mgrno>000010</mgrno></row>
<row><admrdept>A00</admrdept><deptname>PLANNING</deptname><deptno>B01</deptno><mgrno>000020</mgrno></row>
<row><admrdept>A00</admrdept><deptname>INFORMATION CENTER</deptname><deptno>C01</deptno><mgrno>000030</mgrno></row>
Note:
I have used XMLROW, for simplicity.. instead of yours XMLSERIALIZE( XMLELEMEENT( XMLFOREST ... just as an example.. so you get the idea..

At least one of the duplicate records has 'x'. Postgres sql EXISTS or INNER JOIN

I have the below postgres query that finds duplicate records in a database but I'm hoping to add in another condition so that I can say AT LEAST ONE of the duplicated records has the values of v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' (from a table called sierra_view.varfield v ON p.record_id = v.record_id).
I tried an INNER JOIN and am looking into EXISTS. Does anyone have any insight? Thank you.
SELECT
p.birth_date_gmt, 'p' || rm2.record_num || 'a' AS "patron",
n.last_name || ' ' || n.first_name || ' ' || n.middle_name as name,
count(*) as cnt
FROM
sierra_view.patron_record p
JOIN sierra_view.patron_record_fullname n ON p.record_id =
n.patron_record_id
JOIN sierra_view.record_metadata rm2 on p.record_id = rm2.id
/* JOIN sierra_view.varfield v on p.record_id =v.record_id */
WHERE p.birth_date_gmt BETWEEN '01-01-2001' AND '12-31-2017'
GROUP BY 1,2, 3
HAVING COUNT(1) > 1
ORDER BY 2,1
You can put these conditions in the HAVING section:
JOIN sierra_view.varfield v on p.record_id = v.record_id
WHERE ...
GROUP BY ...
HAVING COUNT(*) > 1 AND COUNT(CASE WHEN v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' THEN 1 END) > 0
So:
COUNT(*) > 1 = Only include duplicate records (you already do this)
COUNT(CASE WHEN v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' THEN 1 END) > 0 = Count the grouped records based on those two conditions; if a record matches, it gets a 1, otherwise a NULL (implicit), and NULLs are not counted. So if at least one of the grouped records matches the criteria, the whole "group" will be included in the results; if not, they won't be included.
Also worth double checking whether ~ 'Greendale student cards%' is correct; ~ is for a regex check, while % is a wildcard symbol for LIKE, unless of course you do mean to search for a literal % character.

Need help combining the results of two select statements

My first select statements is:
SELECT cRefNum, cName, cProgram || '-' || cCode || '-' || cSection as CourseDesc, cTimeStart, cTimeEnd, cDay, ct.cCampus, cBuildingSection || cRoom AS Room, cSchedType
from coursedetails cd inner join coursetimes ct
using (cRefNum)
where cRefNum = 3816;
Which results in:
My second select statements is:
select cRefNum, LISTAGG(fname|| ' ' || lname, ', ') within group (order by cRefNum) as Teachers
from teachers
where cRefNum = 3816
group by cRefNum;
Which results in:
What I'm trying to achieve is:
select a.cRefNum, cName, CourseDesc, cTimeStart, cTimeEnd, cDay, Campus, Room, cSchedType, Teachers
from
(select cRefNum, cName, cProgram || '-' || cCode || '-' || cSection as CourseDesc, cTimeStart, cTimeEnd, cDay, ct.cCampus as Campus, cBuildingSection || cRoom AS Room, cSchedType from coursedetails cd inner join coursetimes ct using (cRefNum) where cRefNum = 3816) a,
(select cRefNum, LISTAGG(fname|| ' ' || lname, ', ') within group (order by cRefNum) as Teachers from teachers where cRefNum = 3816 group by cRefNum) b
where a.cRefNum = b.cRefNum;
This fixes it, but is really long and I don't know enough to shorten it.

LIKE search of joined and concatenated records is really slow (PostgreSQL)

I'm returning a unique list of id's from the users table, where specific columns in a related table (positions) contain a matching string.
The related table may have multiple records for each user record.
The query is taking a really really long time (its not scaleable), so I'm wondering if I'm structuring the query wrong in some fundamental way?
Users Table:
id | name
-----------
1 | frank
2 | kim
3 | jane
Positions Table:
id | user_id | title | company | description
--------------------------------------------------
1 | 1 | manager | apple | 'Managed a team of...'
2 | 1 | assistant | apple | 'Assisted the...'
3 | 2 | developer | huawei | 'Build a feature that...'
For example: I want to return the user's id if a related positions record contains "apple" in either the title, company or description columns.
Query:
select
distinct on (users.id) users.id,
users.name,
...
from users
where (
select
string_agg(distinct users.description, ', ') ||
string_agg(distinct users.title, ', ') ||
string_agg(distinct users.company, ', ')
from positions
where positions.users_id::int = users.id
group by positions.users_id::int) like '%apple%'
UPDATE
I like the idea of moving this into a join clause. But what I'm looking to do is filter users conditional on below. And I'm not sure how to do both in a join.
1) finding the keyword in title, company, description
or
2) finding the keyword with full-text search in an associated string version of a document in another table.
select
to_tsvector(string_agg(distinct documents.content, ', '))
from documents
where users.id = documents.user_id
group by documents.user_id) ## to_tsquery('apple')
So I was originally thinking it might look like,
select
distinct on (users.id) users.id,
users.name,
...
from users
where (
(select
string_agg(distinct users.description, ', ') ||
string_agg(distinct users.title, ', ') ||
string_agg(distinct users.company, ', ')
from positions
where positions.users_id::int = users.id
group by positions.users_id::int) like '%apple%')
or
(select
to_tsvector(string_agg(distinct documents.content, ', '))
from documents
where users.id = documents.user_id
group by documents.user_id) ## to_tsquery('apple'))
But then it was really slow - I can confirm the slowness is from the first condition, not the full-text search.
Might not be the best solution, but a quick option is:
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
JOIN positions p ON (
p.user_id = u.id
AND ( description || title || company )
LIKE '%apple%'
);
Basically got rid of the subquery, unnecessary string_agg usage, grouping on position table etc.
What it does is doing conditional join and removing duplicate is covered by distinct on.
PS! I used table aliases u and p to shorten the example
EDIT: adding also WHERE example as requested
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
JOIN positions p ON ( p.user_id = u.id )
WHERE ( p.description || p.title || p.company ) LIKE '%apple%'
OR ...your other conditions...;
EDIT2: new details revealed setting new requirements of the original question. So adding new example for updated ask:
Since you doing lookups to 2 different tables (positions and uploads) with OR condition then simple JOIN wouldn't work.
But both lookups are verification type lookups - only looking does %apple% exists, then you do not need to aggregate and group by and convert the data.
Using EXISTS that returns TRUE for first match found is what you seem to need anyway. So removing all unnecessary part and using with LIMIT 1 to return positive value if first match found and NULL if not (latter will make EXISTS to become FALSE) will give you same result.
So here is how you could solve it:
SELECT DISTINCT ON ( u.id ) u.id,
u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM positions p
WHERE p.users_id = u.id::int
AND ( description || title || company ) LIKE '%apple%'
LIMIT 1
)
OR EXISTS (
SELECT 1
FROM uploads up
WHERE up.user_id = u.id::int -- you had here reference to table 'document', but it doesn't exists in your example query, so I just added relation to 'upoads' table as you have in FROM, assuming 'content' column exists there
AND up.content LIKE '%apple%'
LIMIT 1
);
NB! in your example queries have references to tables/aliases like documents which doesn't reflect anywhere in the FROM part. So either you have cut in your example real query with wrong naming or you have made other way typo is something you need to verify and adjust my example query accordingly.

postgres concat in group by

One question about Postgresql selects. This works as it should:
SELECT
name,SUM(cash)
FROM
costumers
GROUP BY (name)
but how can I concat two (or more) fields in the GROUP BY clause?
This is what I tried:
SELECT
name,SUM(cash)
FROM
costumers
GROUP BY (name || ' ' || nickname)
That will work, except that you need to select the expression you group by:
SELECT
(name || ' ' || nickname) AS name_and_nickname,
SUM(cash) AS total_cash
FROM costumers
GROUP BY (name || ' ' || nickname)
Another option is to group by two fields by separating them with a comma:
SELECT
name, nickname, SUM(cash) AS total_cash
FROM costumers
GROUP BY name, nickname
Note that these two are not exactly equivalent. In particular these two rows will end up in the same group with the first version and in different groups in the second version:
name | nickname | cash
--------+-----------+----
foo | bar baz | 10
foo bar | baz | 20
The second option is probably what you mean.