Postgres Array Prefix Matching - sql

I have an array search in Postgres hat matches at least one tag as this:
SELECT * FROM users WHERE tags && ['fun'];
| id | tags |
| 1 | [fun,day] |
| 2 | [fun,sun] |
It is possible to match on prefixes? Something like:
SELECT * FROM users WHERE tags LIKE 'f%';
| id | tags |
| 1 | [fun,day] |
| 2 | [fun,sun] |
| 3 | [far] |
| 4 | [fin] |

try this
create table users (id serial primary key, tags text[]);
insert into users (tags)
values
('{"fun", "day"}'),
('{"fun", "sun"}'),
('{"test"}'),
('{"fin"}');
select *
from users
where exists (select * from unnest(tags) as arr where arr like 'f%')
SQL FIDDLE EXAMPLE

Here's a working example that should get you more or less what you're after. Note that I am not claiming that this approach will scale...
create table users (
id serial primary key,
tags text[] not null
);
insert into users (tags) values
('{"aaaa","bbbb","cccc"}'::text[]),
('{"badc","dddd","eeee"}'::text[]),
('{"gggg","ffbb","attt"}'::text[]);
select *
from (select id,unnest(tags) arr from users) u
where u.arr like 'a%';
id | arr
----+------
1 | aaaa
3 | attt

Related

select values or NULL if it does not exists

I have a table user with columns id (primary key) and name (unique constraint) containing:
+----+------+
| id | name |
+----+------+
| 1 | u1 |
| 2 | u2 |
| 3 | u3 |
+----+------+
I would like a make a query, something like SELECT id, name FROM user WHERE name IN (?,?), with parameters ("u2", "u4"), that I can reuse in CTE, but which returns:
+------+------+
| id | name |
+------+------+
| 2 | u2 |
| NULL | u4 |
+------+------+
(edit: s/NULL/u4).
Is it possible? I'm interested in MariaDB and PostgreSQL.
One use case would be to insert the ids result in another table where NULL is not accepted, so it would insert the names and check for invalid values in only 1 request. (no need for 2 separates requests + application code).
There are different ways.. An easy one in Postgresql could be:
select a, "name"
from unnest(ARRAY['u2', 'u4']::text[]) AS a
LEFT JOIN "user" AS u ON a = u."name";

How to make this sql with query array_agg?

I want to make a query
select * from projects where user_id = 3;
and depending on it's result r, I need to make n queries, where n is the length l of r. eg:
| id | project_name | description | user_id |
| 1 | Project A | lorem ipsu | 3 |
| 4 | Project B | lorem ipsu | 3 |
l => 2 then:
select * from images where project_id = 1;
select * from images where project_id = 4;
Ok, you can see where this is going if l is too big. Too many selects, too many access to the database. Is there a better way to achieve an end result like so:
| id | project_name | description | user_id | images |
| 1 | Project A | lorem ipsu | 3 | {imgX,imgY,imgZ} |
| 4 | Project B | lorem ipsu | 3 | {imgA,imgB} |
I heard about array_agg function on postgres. Maybe that's the answer? Anyways, these are my table descriptions:
Table "public.projects"
Column | Type | Modifiers
-------------+--------------------------+-------------------------------------------------------
id | integer | not null default nextval('projects_id_seq'::regclass)
name | character varying(255) |
description | character varying(255) |
user_id | integer |
created_at | timestamp with time zone |
updated_at | timestamp with time zone |
Table "public.images"
Column | Type | Modifiers
------------+--------------------------+-----------------------------------------------------
id | integer | not null default nextval('images_id_seq'::regclass)
name | character varying(255) |
url | character varying(255) |
project_id | integer |
created_at | timestamp with time zone |
updated_at | timestamp with time zone |
And thank you in advance :D
array_agg is like any other aggregate function (count, sum), but returns an array instead of a scalar value. What you need can be achieved simply by joining and grouping the 2 tables.
SELECT p.id, p.name, p.description, p.user_id, array_agg(i.name) images
FROM projects p
LEFT JOIN images i ON p.id = i.project_id
GROUP BY p.id, p.name, p.description, p.user_id
You want records from projects plus records in image names as array matching by project_id :
SELECT *
FROM projects
LEFT JOIN LATERAL (SELECT array_agg(name) AS images FROM images WHERE project_id = projects.project_id) x ON true
WHERE user_id = '3'
sqlfiddle
Probably the easiest solution for you is a sub-select. This comes closest to the individual SELECT statements that you mention earlier:
SELECT * FROM images
WHERE project_id IN (
SELECT project_id FROM projects
WHERE user_id = 3);

SQL - get from table and join with same table

I have two tables, ChatRoom and ChatRoomMap, I want to get a list of chatrooms a user belongs to, along with all the other users in each chatroom.
// this contains a map of user to chatroom, listing which user is in what room
CREATE TABLE ChatRoomMap
(
user_id bigint NOT NULL,
chatroom_id text NOT NULL,
CONSTRAINT uniq UNIQUE (userid, roomid)
)
// sample values
==========================
| user_id | chatroom_id |
| 1 | 7 |
| 1 | blue |
| 7 | red |
==========================
And
CREATE TABLE ChatRoom
(
id text NOT NULL,
admin bigint,
name text,
created timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT uniqid UNIQUE (id)
)
// sample values
======================================================
| id | admin | name | timestamp |
| blue | 7 | blue room | now() |
| red | 2 | red | now() |
| 7 | 11 | mine | now() |
======================================================
To get a list of rooms a user is in, I can do:
SELECT DISTINCT ON (id) id, userid, name, admin
FROM ChatRoomMap, ChatRoom WHERE ChatRoomMap.user_id = $1 AND ChatRoomMap.chatroom_id = ChatRoom.id
This will get me a distinct list of chat rooms a user is in.
I would like to get the distinct list of rooms along with all the users in each room (concatenation of all as a separate column), how can this be done?
Example result:
=======================================================
| user_id | chatroom_id | name | admin | other_users |
| 10 | 7 | One | 1 | 1, 2, 3, 8 |
| 10 | 4 | AAA | 10 | 7, 11, 15 |
=======================================================
First up, use proper joins - the explicit join syntax was introduced to the SQL92 standard and the major vendors implemented it in the early 2000's (and it's the only way to achieve an outer join).
Try this:
SELECT DISTINCT id, crm2.user_id, name, admin,
FROM ChatRoomMap crm1
JOIN ChatRoom ON crm1.chatroom_id = ChatRoom.id
LEFT JOIN ChatRoomMap crm2 ON crm2.chatroom_id = crm1.chatroom_id
AND crm2.user_id != crm1.user_id -- only other users
WHERE crm1.user_id = $1
The LEFT JOIN is needed in case there are no other users in the room it will still list the room (with a null for other user id).

Postgres: Expand JSON column into rows

In a Postgres 9.3 database I have a table in which one column contains JSON, as in the test table shown in the example below.
test=# create table things (id serial PRIMARY KEY, details json, other_field text);
CREATE TABLE
test=# \d things
Table "public.things"
Column | Type | Modifiers
-------------+---------+-----------------------------------------------------
id | integer | not null default nextval('things_id_seq'::regclass)
details | json |
other_field | text |
Indexes:
"things_pkey" PRIMARY KEY, btree (id)
test=# insert into things (details, other_field)
values ('[{"json1": 123, "json2": 456},{"json1": 124, "json2": 457}]', 'nonsense');
INSERT 0 1
test=# insert into things (details, other_field)
values ('[{"json1": 234, "json2": 567}]', 'piffle');
INSERT 0 1
test=# select * from things;
id | details | other_field
----+-------------------------------------------------------------+-------------
1 | [{"json1": 123, "json2": 456},{"json1": 124, "json2": 457}] | nonsense
2 | [{"json1": 234, "json2": 567}] | piffle
(2 rows)
The JSON is always an array containing a variable number of hashes. Each hash always has the same set of keys. I am trying to write a query which returns a row for each entry in the JSON array, with columns for each hash key and the id from the things table. I'm hoping for output like the following:
thing_id | json1 | json2
----------+-------+-------
1 | 123 | 456
1 | 124 | 457
2 | 234 | 567
i.e. two rows for entries with two items in the JSON array. Is it possible to get Postgres to do this?
json_populate_recordset feels like an essential part of the answer, but I can't get it to work with more than one row at once.
select id,
(details ->> 'json1')::int as json1,
(details ->> 'json2')::int as json2
from (
select id, json_array_elements(details) as details
from things
) s
;
id | json1 | json2
----+-------+-------
1 | 123 | 456
1 | 124 | 457
2 | 234 | 567

Insert into row if value exists in a different table

I have two tables:
Users:
+---------------------+---------------------+
| UserId | ValueToUpdate |
+---------------------+---------------------+
| 1 | |
| 2 | |
| 3 | |
+---------------------+---------------------+
Subscribers:
+---------------------+
| UserId |
+---------------------+
| 1 |
| 2 |
+---------------------+
I need to write a SQL query that will insert some value into the Users table, column (ValueToUpdate) if the user id in the Users table exists in the Subscribers table.
Essentially I'm looking for something like this
UPDATE Users
SET ValueToUpdate = "some value"
WHERE (UserId from the Users table exists in the Subscribers table);
How could I achieve this?
You can do this using an exists clause:
UPDATE Users u
SET ValueToUpdate = 'some value'
WHERE EXISTS (SELECT 1 FROM Subscribers S WHERE s.userid = u.userid);