UPDATE statement using two arrays at the same index in WHERE clause - postgresql

I am trying to update a table, entities with a column, contacts that is an array of ids from another table, contacts. The contacts table has the columns first_name and last_name, and I have an array of first names, firstNames and last names, lastNames to pass in.
How would you update the contacts column in the entities table with one query that properly gets all of the contacts with first name firstNames[0] AND last name lastNames[0], and all of the contacts with first name firstNames[1] AND last name lastNames[1], and [...] all of the contacts with first name firstNames[n] AND last name lastNames[n]?
My initial thought was something like UPDATE entities SET contacts = (SELECT id FROM contacts WHERE first_name = ANY(firstNames) AND last_name = ANY(lastNames).
The problem with this arrises when the contacts table is like this:
first_name | last_name
----------------------
Bob | Jones
Bob | Miller
David | Miller
If I wanted to set the contacts column to the Ids for Bob Jones and David Miller, but NOT Bob Miller, and I passed in ['Bob', 'David'] for firstNames and ['Jones', 'Miller'] for lastNames in the above query, Bob Miller would also get added to the contacts column.

May be you look for something like this:
WITH x AS (
SELECT 'Bob'::text AS firstName, 'Jones'::text AS lastName
UNION SELECT 'David', 'Miller'
UNION SELECT 'Bob', 'Miller'
)
SELECT *
FROM x
WHERE (firstName, lastName) = ANY (ARRAY [
('Bob'::text, 'Jones'::text),
('David'::text, 'Miller'::text)
]);
Yet another way:
WITH x AS (
SELECT 'Bob'::text AS firstName, 'Jones'::text AS lastName
UNION SELECT 'David', 'Miller'
UNION SELECT 'Bob', 'Miller'
)
SELECT *
FROM x
WHERE EXISTS (
SELECT 1
FROM (SELECT ARRAY [
['Bob', 'Jones'],
['David', 'Miller']]::text[][] AS n
) AS n
JOIN LATERAL generate_series(1, array_upper(n, 1)) AS i ON true
WHERE firstName = n[i][1]
AND lastName = n[i][2]
);

Related

How can I return groups of rows in Postgresql that have the same value in one column?

I'm trying to write a query that will return rows grouped together by same first name, where last name is LIKE "some search string %"
For example, if I searched for last name LIKE 'Smi%' I want to get this result back
{
[{1, tofu_spice, Joe, Smith}, {2, jsmith, Joe, Smithy}],
[{3, smirthy11, Jack, Smirth}, {5, jackal, Jack, Smiles}],
}
id
username
f_name
l_name
1
tofu_spice
Joe
Smith
2
jsmith
Joe
Smithy
3
smirthy11
Jack
Smirth
5
jackal
Jack
Smiles
6
kevs
Jack
Allie
7
rb2015
Rob
Brown
8
luigi191
Rob
Bran
where the first array in my example result has all the rows with same first name "Joe" and second array has all rows with same first name "Jack".
Is it possible to return such a result? I know GROUP BY is used with aggregate functions, but I don't want to perform any such operations. I just want all the rows for each group returned. The query I've come up with so far (which I know is wrong) is SELECT * FROM users WHERE l_name LIKE "Smi%" GROUP BY f_name
Manual reference: https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-AGGREGATES
BEGIN;
CREATE temp TABLE users (
id bigint,
username text,
f_name text,
l_name text
);
INSERT INTO users
VALUES (1, 'tofu_spice', 'Joe', 'Smith'),
(2, 'jsmith', 'Joe', 'Smithy'),
(3, 'smirthy11', 'Jack', 'Smirth'),
(5, 'jackal', 'Jack', 'Smiles'),
(6, 'kevs', 'Jack', 'Allie'),
(7, 'rb2015', 'Rob', 'Brown'),
(8, 'luigi191', 'Rob', 'Bran');
COMMIT;
SELECT
array_agg(users.* ORDER BY id) AS grouped_user
FROM
users
WHERE
l_name LIKE 'Smi%'
GROUP BY
f_name
HAVING
cardinality(array_agg(users.* ORDER BY id)) > 1;

PostgreSQL - How to display a corresponding string on every entry in string_agg()?

I have 2 tables:
Employee
ID Name
1 John
2 Ben
3 Adam
Employer
ID Name
1 James
2 Rob
3 Paul
I want to string_agg() and concatenate the two tables in one record as a single column. Now I wanted another column than will determine that if that string is from "Employee" table, it will display "Employee" and "Employer" if the data comes from the "Employer" table.
Here's my code for displaying the table:
SELECT string_agg(e.Name, CHR(10)) || CHR(10) || string_agg(er.Name, CHR(10)), PERSON_STATUS
FROM Employee e, Employer er
Here's my expected output:
ID Name PERSON_STATUS
1 John Employee
Ben Employee
Adam Employee
James Employer
Rob Employer
Paul Employer
NOTE: I know this can be done by adding another column in the table but that's not the case of this scenario. This is just an example to illustrate my problem.
Based on your sample, I'd say that you need UNION ALL rather than an aggregate:
SELECT id, name, 'Employee'::text AS person_status
FROM employee
UNION ALL
SELECT id, name, 'Employer'::text
from employer;
SELECT 1 AS id, STRING_AGG(name, E'\r\n') AS name, STRING_AGG(person_status, E'\r\n') AS person_status
FROM (
SELECT name, 'Employee' AS person_status
FROM employee
UNION ALL
SELECT name, 'Employer'
FROM employer
) data
Returns:
Ok, so first we merge our 2 tables into 3 columns. We can select arbitrary values this way.
select
"ID", -- Double quotes are necesary for capitalised aliases
"Name",
'Employee' as "PERSON_STATUS"
from
employee
union
select
"ID",
"Name",
'Employer'
from
employer
We then subquery this and perform our string operations as required.
select
string_agg(concat(people."Name", ' ', people."PERSON_STATUS"), chr(10))
from
(
select
"ID",
"Name",
'Employee' as "PERSON_STATUS"
from
employee
union
select
"ID",
"Name",
'Employer'
from
employer
) as people

Using a WHERE EXISTS statement for two contradictory conditions in Redshift/Postgres?

I'm looking to write a query that, given an id, first name, and last name, returns IDs corresponding to every ID that has at least one row containing a first name 'Steve' and a last name 'Smith', in addition to at least one row that corresponds to a first name 'Steve' and does not correspond to a last name 'Smith'. I tried the below query but it returns 0 rows.
SELECT DISTINCT id FROM t
WHERE EXISTS (SELECT 1 FROM t WHERE first_name = 'Steve' AND last_name != 'Smith')
AND NOT EXISTS (SELECT 1 FROM t WHERE first_name = 'Steve' AND last_name = 'Smith')
I suspect it's because within a single row, both conditions cannot simultaneously be true, even though they can both be true across multiple rows for the same ID.
How should I modify or rewrite this query to return the IDs of interest?
Apparently you could write this another way. Give me all IDs where there's at least one Steve Smith but not all of them are Steve Smiths.
select id
from t
group by id
having count(case when first_name = 'Steve' and last_name = 'Smith' then 1 end)
between 1 and count(last_name) - 1

Postgres id to name mapping in an array while creating CSV file

I have a table with id to group name mapping.
1. GroupA
2. GroupB
3. GroupC
.
.
.
15 GroupO
And I have user table with userId to group ID mapping, group ID is defined as array in user table
User1 {1,5,7}
User2 {2,5,9}
User3 {3,5,11,15}
.
.
.
I want to combine to table in such a way to retrieve userID and groupName mapping in CSV file.
for example: User1 {GroupA, GroupE, GroupG}
Essentially group ID should get replace by group name while creating CSV file.
Setup:
create table mapping(id int, group_name text);
insert into mapping
select i, format('Group%s', chr(i+ 64))
from generate_series(1, 15) i;
create table users (user_name text, user_ids int[]);
insert into users values
('User1', '{1,5,7}'),
('User2', '{2,5,9}'),
('User3', '{3,5,11,15}');
Step by step (to understand the query, see SqlFiddle):
Use unnest() to list all single user_id in a row:
select user_name, unnest(user_ids) user_id
from users
Replace user_id with group_name by joining to mapping:
select user_name, group_name
from (
select user_name, unnest(user_ids) id
from users
) u
join mapping m on m.id = u.id
Aggregate group_name into array for user_name:
select user_name, array_agg(group_name)
from (
select user_name, group_name
from (
select user_name, unnest(user_ids) id
from users
) u
join mapping m on m.id = u.id
) m
group by 1
Use the last query in copy command:
copy (
select user_name, array_agg(group_name)
from (
select user_name, group_name
from (
select user_name, unnest(user_ids) id
from users
) u
join mapping m on m.id = u.id
) m
group by 1
)
to 'c:/data/example.txt' (format csv)
Say you have two tables in this form:
Table groups
Column | Type
-----------+---------
groupname | text
groupid | integer
Table users
Column | Type
----------+----------
username | text
groupids | integer[] <-- group ids as inserted in table groups
You can query the users replacing the group id with group names with this code:
WITH users_subquery AS (select username,unnest (groupids) AS groupid FROM users)
SELECT username,array_agg(groupname) AS groups
FROM users_subquery JOIN groups ON users_subquery.groupid = groups.groupid
GROUP BY username
If you need the groups as string (useful for the csv export), surround the query with a array_to_string statement:
SELECT username, array_to_string(groups,',') FROM
(
WITH users_subquery AS (select username,unnest (groupids) AS groupid FROM users)
SELECT username,array_agg(groupname) AS groups
FROM users_subquery JOIN groups ON users_subquery.groupid = groups.groupid
GROUP BY username
) as foo;
Result:
username | groups
----------+-----------------
user1 | group1,group2
user2 | group2,group3

Cross Reference TSQL Join

Say i have Four tables
1) Studnets:
Student_ID
First Name
Last Name
2) Contact (Will take the latest item)
Contact_ID
Address
ZipCode
DateAdded
3) Phone (Will take the last three items)
Contact_ID
PhoneNumber
DateAdded
4) StudentContactRef
Student_ID
Contact_ID
How can I query this table? I want to have the fields as shows below:
Student_ID
First Name
Last Name
Address
ZipCode
PhoneNumber1
PhoneNumber2
PhoneNumber3
select
s.Student_ID,
s.FirstName,
s.LastName,
c.Contact_ID,
c.Address,
c.ZipCode,
p.PhoneNumber1,
p.PhoneNumber2,
p.PhoneNumber3
from
Students s
inner join StudentContactRef r on
s.Student_ID = r.StudentID
inner join Contact c on
r.Contact_ID = c.Contact_ID
inner join
(select top 3 Contact_ID, PhoneNumber from Phone
pivot (PhoneNumber for PhoneNumber IN
(PhoneNumber1, PhoneNumber2, PhoneNumber3)
where Contact_ID = r.Contact_ID order by DateAdded desc) p on
r.Contact_ID = p.Contact_ID
Update: That should get you what you're looking for!