Update value inside json matching a criteria - sql

I have entries (json datatype) in my database with some typos. I want to update them with a correct one
I'm using a PostgreSQL 9.4
This is what I'm trying:
UPDATE table
SET infos = infos || '{ "key1": { "key2": "new text with no typo" } }'
WHERE contact_id = (SELECT contact_id FROM table WHERE infos::TEXT LIKE '%criteria%');
DDL:
CREATE TABLE public.timeline (
contact_id serial NOT NULL,
infos json NULL,
CONSTRAINT contacts_pkey PRIMARY KEY (contact_id)
);
I expect to automatically update all the lines containing the typo.
Solution:
UPDATE table
SET infos = jsonb_set(to_jsonb(infos), '{key1,key2}', '{"key2": "new value to replace"}', false)
WHERE contact_id IN (SELECT contact_id FROM table WHERE infos->'key1'->>'key2' LIKE '%word_to_match%');

Related

Autoincrement id of an object in array

I need to store array of an objects in postgresql. Structure of object should looks like
{
"id": 1
"title": "XYZ",
"content": "abcabc"
}
My question is how to automatically increment an id ?
Structure of the table looks like
user_id | user_name | notes
The notes column should store the array of objects (notes).
What is the best way to do that, what is the best data type ( json, jsonb,
json[], jsonb[] )?
I'm using flask and postgresql (11).
Use a separate table:
create table user_notes (
user_notes_id serial primary key,
user_id int references users(user_id),
title text,
content text,
created_at timestamp default now()
);
If you want to enumerate the notes for a particular user, use row_number():
select un.*, row_number() over (partition by un.user_id order by un.user_notes_id) as seqnum
from user_notes un
where un.user_id = ?;

PostgreSQL - key-value pair normalization

I'm trying to design a schema (on Postgres but any other SQL's fine) that supports the following requirements:
Each document (a row in table documents) has a unique string ID (id field) and several other data fields.
Each document can have 0 or more tags (which is string key-value pairs) attached to it, and, the goal is to build a system that lets users to sort or filter documents using those string key-value pairs. E.g. "Show me all documents that have a tag of "key1" with "value1" value AND sort the output using the tag value of "key3".
So DDL should look like this: (simplified)
create table documents
(
id char(32) not null
constraint documents_pkey
primary key,
data varchar(2000),
created_at timestamp,
updated_at timestamp
)
create table document_tags
(
id serial not null
constraint document_tags_pkey
primary key,
document_id char(32) not null
constraint document_tags_documents_id_fk
references documents
on update cascade on delete cascade,
tag_key varchar(200) not null,
tag_value varchar(2000) not null
)
Now my question is how can I build a query that does filtering/sorting using the tag key values? E.g. Returns all documents (possibly with LIMIT/OFFSET) that does have "key1" = "value1" tag and "key2" = "value2" tags, sorted by the value of "key3" tag.
You can use group by and having:
select dt.document_id
from document_tags dt
where dt.tag_key = 'key1' and dt.tag_value = 'value1'
group by dt.document_id
order by max(case when dt.tag_key = 'key2' then dt.tag_value end);

Query optimization: connecting meta data to a value list table

I have a database containing a table with data and a meta data table. I want to create a View that selects certain meta data belonging to an item and list it as a column.
The basic query for the view is: SELECT * FROM item. The item table is defined as:
CREATE TABLE item (
id INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE
NOT NULL,
traceid INTEGER REFERENCES trace (id)
NOT NULL,
freq BIGINT NOT NULL,
value REAL NOT NULL
);
The meta data to be added follow the schema "metadata.parameter='name'"
The meta table is defined as:
CREATE TABLE metadata (
id INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE
NOT NULL,
parameter STRING NOT NULL
COLLATE NOCASE,
value STRING NOT NULL
COLLATE NOCASE,
datasetid INTEGER NOT NULL
REFERENCES dataset (id),
traceid INTEGER REFERENCES trace (id),
itemid INTEGER REFERENCES item (id)
);
The "name" parameter should be selected this way:
if a record exists where parameter is "name" and itemid matches item.id, then its value should be included in the record.
otherwise, if a record exists where parameter is "name", "itemid" is NULL, and traceid matches item.traceid, its value should be used
otherwise, the result should be NULL, but the record from the item table should be included anyway
Currently, I use the following query to achieve this goal:
SELECT i.*,
COALESCE (
MAX(CASE WHEN m.parameter='name' THEN m.value END),
MAX(CASE WHEN m2.parameter='name' THEN m2.value END)
) AS itemname
FROM item i
JOIN metadata m
ON (m.itemid = i.id AND m.parameter='name')
JOIN metadata m2
ON (m2.itemid IS NULL AND m2.traceid = i.traceid AND m2.parameter='name')
GROUP BY i.id
This query however is somewhat inefficient, as the metadata table is used twice and contains many more records than just the "name" ones. So I am looking for a way to improve speed, especially regarding the case that some extensions are about to be implemented:
there is a third level "dataset" that should be included: a "parameter=name" should be used if it has the same datasetid as the item (will be looked up for the items by searching another which connects traceid and datasetid), if no "parameter=name" exists with either "itemid" matching or "traceid" matching
more meta data should be queried by the view following the same schema
Any help is appreciated.
First of all, you can use one join instead of 2, like this:
JOIN metadata m ON (m.parameter='name' AND (m.itemId = i.id OR (m.itemId IS NULL AND m.traceid = i.traceid)))
Then you can remove COALESCE, using simple select:
SELECT i.*, m.value as itemname
Result should look like this:
SELECT i.*, m.value as itemname
FROM item i
JOIN metadata m ON (m.parameter='name' AND (m.itemId = i.id OR (m.itemId IS NULL AND m.traceid = i.traceid)))
GROUP BY i.id

Creating rule for update view sql error

I am trying to create UpdaeTable view.
I have table product, category, product_has_category.
CREATE TABLE "category" (
"category_id" SERIAL NOT NULL,
"name" varchar(15) NOT NULL,
"description" varchar(150) NOT NULL,
PRIMARY KEY("category_id")
);
CREATE TABLE "product" (
"product_id" SERIAL NOT NULL,
"name" varchar(20) NOT NULL,
"price" int4 NOT NULL,
"description" varchar(200),
"country_of_origin" varchar(20) NOT NULL,
PRIMARY KEY("product_id")
);
CREATE TABLE "product_has_category" (
"NMID" SERIAL NOT NULL,
"product_id" int4 NOT NULL,
"category_id" int4 NOT NULL
);
ALTER TABLE "product_has_category"
ADD CONSTRAINT "Ref_Product_has_Category_to_Product" FOREIGN KEY ("Product_product_id")
REFERENCES "Product"("product_id")
MATCH SIMPLE
ON DELETE CASCADE
ON UPDATE CASCADE
NOT DEFERRABLE;
And here is view that selects products, its price and categories:
CREATE VIEW products_with_categories AS
SELECT product.name AS product_name, product.price, category.name AS category
FROM product, category, product_has_category
WHERE product_has_category.product_id = product.product_id AND
product_has_category.category_id = category.category_id
ORDER BY product_name;
I want view to be updatable and created the rule:
CREATE RULE prod_cat_upd AS ON UPDATE TO products_with_categories
DO INSTEAD
UPDATE product
SET product.name=NEW.product.name
WHERE product.name=OLD.product.name
And I got the following error:
invalid reference to FROM-clause entry for table "product"
Hint: There is an entry for table "product", but it cannot be referenced from this part of the query.
I can not understand what does this error means and how to solve this problem.
Try This syntax to CREATE RULE, Refer for more documentation
CREATE RULE prod_cat_upd AS ON UPDATE TO products_with_categories
DO INSTEAD
UPDATE product
SET name=NEW.name
WHERE name=OLD.name
I created Two SQL Fiddle with the Sample Table
1) Sample SQL FIDDLE with solution, I provide :- Working fine
CREATE TABLE shoelace_data (
sl_name text, -- primary key
sl_avail integer, -- available number of pairs
sl_color text, -- shoelace color
sl_len real, -- shoelace length
sl_unit text -- length unit
);
CREATE TABLE unit (
un_name text, -- primary key
un_fact real -- factor to transform to cm
);
CREATE VIEW shoelace AS
SELECT s.sl_name,
s.sl_avail,
s.sl_color,
s.sl_len,
s.sl_unit,
s.sl_len * u.un_fact AS sl_len_cm
FROM shoelace_data s, unit u
WHERE s.sl_unit = u.un_name;
CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
DO INSTEAD
UPDATE shoelace_data
SET sl_name = NEW.sl_name,
sl_avail = NEW.sl_avail,
sl_color = NEW.sl_color,
sl_len = NEW.sl_len,
sl_unit = NEW.sl_unit
WHERE sl_name = OLD.sl_name;
2) The Code you have for CREATE RULE :- Failed and have the same error what you get
NOT Working
CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
DO INSTEAD
UPDATE shoelace_data
SET shoelace_data.sl_name = NEW.shoelace_data.sl_name,
shoelace_data.sl_avail = NEW.shoelace_data.sl_avail,
shoelace_data.sl_color = NEW.shoelace_data.sl_color,
shoelace_data.sl_len = NEW.shoelace_data.sl_len,
shoelace_data.sl_unit = NEW.shoelace_data.sl_unit
WHERE shoelace_data.sl_name = OLD.shoelace_data.sl_name;
EDIT:-
You are getting this error:-
ERROR: column new.name does not exist
Because your view does not have 'name' as field, your view only have these three fields :- product_name, price and category so use these fields only
CREATE RULE prod_cat_upd AS ON UPDATE TO products_with_categories
DO INSTEAD
UPDATE product
SET name=NEW.product_name
WHERE name=OLD.product_name

SQL Concat Query

I have two tables like this:
TABLE user(
id CHAR(100)
text TEXT
)
TABLE post(
postid CHAR(100)
postedby CHAR(100)
text TEXT
FOREIGN KEY (postedby) references user
);
I need a query that for each user concatenates the TEXT column of all posts of that user and put them in the text column of the user. the order is not important.
What should I do?
To select the values use GROUP_CONCAT:
SELECT postedby, GROUP_CONCAT(text)
FROM post
GROUP BY postedby
To update your original table you will need to join this result with your original table using a multi-table update.
UPDATE user
LEFT JOIN
(
SELECT postedby, GROUP_CONCAT(text) AS text
FROM post
GROUP BY postedby
) T1
ON user.id = T1.postedby
SET user.text = IFNULL(T1.text, '');