Store random data in Postgres database from Python - postgresql

I have the data in this form:
data={'[{"info": "No", "uid": null, "links": ["";, ""], "task_id": 1, "created": "2017-02-15T09:07:09.068145", "finish_time": "2017-02-15T09:07:14.620174", "calibration": null, "user_ip": null, "timeout": null, "project_id": 1, "id": 1}]', 'uuid': u'abc:def:ghi'}
I want to store this data in the Postgres DB. I have this query:
quer1='UPDATE table_1 SET data = "%s" WHERE id = "%s" '%(data1,id)
db_session.execute(quer1)
db_session.commit()
This query does execute but doesn't store anything in the db. Datatype of data is 'text'. I am not able to make where I am wrong. Please help.
Edited::
I updated my query to this:
quer1='UPDATE table_1 SET data = "%s" WHERE hitid = %s '%(data1,id)

First, never use % or str.format to insert values into your queries!!!
Assuming you are using psycopg2, your query should use the following format:
db_session.execute('UPDATE table_1 SET data = %s WHERE id = %s', (data1, id))
As #groteworld mentions, data = {1,2,'3',[4],(5),{6}} is not valid Python.
I will assume you are using a proper value for data in your actual code.

Related

Postgresql: Can the minus operator not be used with a parameter? Only hardcoded values?

The following query deletes an entry using index:
const deleteGameQuery = `
update users
set games = games - 1
where username = $1
`
If I pass the index as a parameter, nothing is deleted:
const gameIndex = rowsCopy[0].games.findIndex(obj => obj.game == gameID).toString();
const deleteGameQuery = `
update users
set games = games - $1
where username = $2
`
const { rows } = await query(deleteGameQuery, [gameIndex, username]);
ctx.body = rows;
The gameIndex parameter is just a string, the same as if I typed it. So why doesn't it seem to read the value? Is this not allowed?
The column games is a jsonb data type with the following data:
[
{
"game": "cyberpunk-2077",
"status": "Backlog",
"platform": "Any"
},
{
"game": "new-pokemon-snap",
"status": "Backlog",
"platform": "Any"
}
]
The problem is you're passing text instead of an integer. You need to pass an integer. I'm not sure exactly how your database interface works to pass integers, try removing toString() and ensure gameIndex is a Number.
const gameIndex = rowsCopy[0].games.findIndex(obj => obj.game == gameID).
array - integer and array - text mean two different things.
array - 1 removes the second element from the array.
select '[1,2,3]'::jsonb - 1;
[1, 3]
array - '1' searches for the entry '1' and removes it.
select '["1","2","3"]'::jsonb - '1';
["2", "3"]
-- Here, nothing is removed because 1 != '1'.
select '[1,2,3]'::jsonb - '1';
[1, 2, 3]
When you pass in a parameter, it is translated by query according to its type. If you pass a Number it will be translated as 1. If you pass a String it will be translated as '1'. (Or at least that's how it should work, I'm not totally familiar with Javascript database libraries.)
As a side note, this sort of data is better handled as a join table.
create table games (
id bigserial primary key,
name text not null,
status text not null,
platform text not null
);
create table users (
id bigserial primary key,
username text not null
);
create table game_users (
game_id bigint not null references games,
user_id bigint not null references users,
-- If a user can only have each game once.
unique(game_id, user_id)
);
-- User 1 has games 1 and 2. User 2 has game 2.
insert into game_users (game_id, user_id) values (1, 1), (2, 1), (2,2);
-- User 1 no longer has game 1.
delete from game_users where game_id = 1 and user_id = 1;
You would also have a platforms table and a game_platforms join table.
Join tables are a little mind bending, but they're how SQL stores relationships. JSONB is very useful, but it is not a substitute for relationships.
You can try to avoid decomposing objects outside of postgress and manipulate jsonb structure inside the query like this:
create table gameplayers as (select 1 as id, '[
{
"game": "cyberpunk-2077",
"status": "Backlog",
"platform": "Any"
},
{
"game": "new-pokemon-snap",
"status": "Backlog",
"platform": "Any"
},
{
"game": "gameone",
"status": "Backlog",
"platform": "Any"
}
]'::jsonb games);
with
ungroupped as (select * from gameplayers g, jsonb_to_recordset(g.games)
as (game text, status text, platform text)),
filtered as (select id,
jsonb_agg(
json_build_object('game', game,
'status', status,
'platfrom', platform
)
) games
from ungroupped where game not like 'cyberpunk-2077' group by id)
UPDATE gameplayers as g set games=f.games
from filtered f where f.id=g.id;

How to delete a node from a JSONB Array across all table rows in Postges?

I have a table called "Bookmarks" that contains several standard rows and also a JSONB column called "columnsettings"
The content of this JSONB column looks like this.
[
{
"data": "id",
"width": 25
},
{
"data": "field_1",
"width": 125
},
{
"data": "field_12",
"width": 125
},
{
"data": "field_11",
"width": 125
},
{
"data": "field_2",
"width": 125
},
{
"data": "field_7",
"width": 125
},
{
"data": "field_8",
"width": 125
},
{
"data": "field_9",
"width": 125
},
{
"data": "field_10",
"width": 125
}
]
I am trying to write an update statement which would update this columnsettings by removing a specific node I specify. For example, I might want to update the columnsettings and remove just the node where data='field_2' as an example.
I have tried a number of things...I believe it will look something like this, but this is wrong.
update public."Bookmarks"
set columnsettings =
jsonb_set(columnsettings, (columnsettings->'data') - 'field_2');
What is the correct syntax to remove a node within a JSONB Array like this?
I did get a version working when there is a single row. This correctly updates the JSONB column and removes the node
UPDATE public."Bookmarks" SET columnsettings = columnsettings - (select position-1 from public."Bookmarks", jsonb_array_elements(columnsettings) with ordinality arr(elem, position) WHERE elem->>'data' = 'field_2')::int
However, I want it to apply to every row in the table. When there is more than 1 row, I get the error " more than one row returned by a subquery used as an expression"
How do I get this query to update all rows in the table?
UPDATED, the answer provided solved my issue.
I now have another JSONB column where I need to do the same filtering. The structure is a bit different, it looks likke this
{
"filters": [
{
"field": "field_8",
"value": [
1
],
"header": "Colors",
"uitype": 7,
"operator": "searchvalues",
"textvalues": [
"Red"
],
"displayfield": "field_8_options"
}
],
"rowHeight": 1,
"detailViewWidth": 1059
}
I tried using the syntax the same way as follows:
UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
SELECT bookmarkid, JSONB_AGG(el) as tabsettings
FROM public."Bookmarks",
JSONB_ARRAY_ELEMENTS(tabsettings) AS el
WHERE el->'filters'->>'field' != 'field_8'
GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid = public."Bookmarks".bookmarkid;
This gives an error: "cannot extract elements from an object"
I thought I had the syntax correct, but how should this line be formatted?
WHERE el->'filters'->>'field' != 'field_8'
I tried this format as well to get to the array. This doesn't given an error, but it doesn't find any matches...even though there are records.
UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
SELECT bookmarkid, JSONB_AGG(el) as tabsettings
FROM public."Bookmarks",
JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
WHERE el->>'field' != 'field_8'
GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid = public."Bookmarks".bookmarkid;
UPDATED .
This query now seems to work if there is more than one "filter" in the array.
However, if there is only 1 element in array which should be excluded, it doesn't remove the item.
UPDATE public."Bookmarks"
SET tabsettings = filtered_elements.tabsettings
FROM (
SELECT bookmarkid,
tabsettings || JSONB_BUILD_OBJECT('filters', JSONB_AGG(el)) as tabsettings
FROM public."Bookmarks",
-- this must be an array
JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
WHERE el->>'field' != 'field_8'
GROUP BY bookmarkid
) AS filtered_elements
WHERE filtered_elements.bookmarkid = public."Bookmarks".bookmarkid;
You can deconstruct, filter, and re-construct the JSONB array. Something like this should work:
UPDATE bookmarks
SET columnsettings = filtered_elements.columnsettings
FROM (
SELECT id, JSONB_AGG(el) as columnsettings
FROM bookmarks,
JSONB_ARRAY_ELEMENTS(columnsettings) AS el
WHERE el->>'data' != 'field_2'
GROUP BY id
) AS filtered_elements
WHERE filtered_elements.id = bookmarks.id;
Using JSONB_ARRAY_ELEMENTS, you transform the JSONB array into rows, one per object, which you call el. Then you can access the data attribute to filter out the "field_2" entry. Finally, you group by id to put the remainign values back together, and update the corresponding row.
EDIT If your data is a nested array in an object, override the object on the specific key:
UPDATE bookmarks
SET tabsettings = filtered_elements.tabsettings
FROM (
SELECT id,
tabsettings || JSONB_BUILD_OBJECT('filters', JSONB_AGG(el)) as tabsettings
FROM bookmarks,
-- this must be an array
JSONB_ARRAY_ELEMENTS(tabsettings->'filters') AS el
WHERE el->>'field' != 'field_2'
GROUP BY id
) AS filtered_elements
WHERE filtered_elements.id = bookmarks.id;

How to get keys and values of jsonb array for a record (row) from inside PL/lpgSQL function

I have a text column in a table that contains city/country type of data, convertible to jsonb, such as:
SELECT
'{"Toronto":"Canada","Moscow":"Russia","New York":"USA"}'::text
AS city_data
and I have to convert it to a JSONB array of this format:
[
{
"city": "Moscow",
"match": false,
"country": "Russia"
},
{
"city": "Toronto",
"match": false,
"country": "Canada"
},
{
"city": "New York",
"match": false,
"country": "USA"
}
]
I am able to do it in a regular PostgreSQL query, by first getting each key/value, and then aggregating it back to JSONB array.
WITH data AS (
WITH city_table AS (
SELECT
'{"Toronto":"Canada","Moscow":"Russia","New York":"USA"}'::text AS city_data
)
SELECT cities.key AS city, cities.value AS country
FROM city_table
LEFT JOIN jsonb_each_text(city_data::jsonb) cities ON true
)
SELECT jsonb_agg(jsonb_build_object(
'city', city::text,
'country', country::text,
'match', false::boolean)) AS combined
FROM data
But I can't figure out how to do this LEFT JOIN inside a PL/pgSQL function.
The function loops over a cursor, and where a certain condition is met, I need to do this conversion and store it into a variable.
i.e. Inside cursor cur, for record rec, I have to get corresponding rec.city_data, convert it to the new format, and save it to variable var_city_data
How do I do the LEFT JOIN on a rec? Usually inside a record I just call rec.field_name and don't need to do any FROM...
I tried:
FOR rec IN cur LOOP
-- conditions........
WITH data AS (
SELECT cities.key AS city, cities.value AS country
FROM rec -- I THINK THE ERROR IS SOMEWHERE HERE AND NEXT LINE
LEFT JOIN jsonb_each_text(rec.city_data::jsonb) cities ON true
)
SELECT INTO var_city_data
jsonb_agg(jsonb_build_object('city', city::text,
'country', country::text,
'match', false::boolean))
FROM data;
RAISE NOTICE 'city_data: % ', var_city_data;
-- ... other conditions...
END LOOP;
I also tried removing FROM rec , doing directly FROM jsonb_each_text(rec.city_data::jsonb) etc and I keep getting errors of type PL/pgSQL function inline_code_block.
Any help would be greatly appreciated.
NOTE: this function has to work on PostgreSQL versions 9.6 and 11+
There is no need of LEFT JOIN or WITH Clause.
Simply try below query:
FOR rec IN cur LOOP
-- conditions........
SELECT jsonb_agg(jsonb_build_object(
'city', cities.key::text,
'country', cities.value::text,
'match', false::boolean))
INTO var_city_data
FROM jsonb_each_text(rec.city_data::jsonb) cities(key,value);
RAISE NOTICE 'city_data: % ', var_city_data;
-- ... other conditions...
END LOOP;
DEMO

How can I update a jsonb column while leaving existing fields intact in postgresql

How can I refactor this update statement so that it updates a jsonb column in my postgresql table?
update contacts as c set latitude = v.latitude,longitude = v.longitude,
home_house_num = v.home_house_num,home_predirection = v.home_predirection,
home_street_name = v.home_street_name, home_street_type = v.home_street_type
from (values (16247746,40.814140,-74.259250,'25',null,'Moran','Rd'),
(16247747,20.900840,-156.373700,'581','South','Pili Loko','St'))
as v(contact_id,latitude,longitude,home_house_num,home_predirection,home_street_name,home_street_type) where c.contact_id = v.contact_id
My table looks like this ...
|contact_id (bigInt) |data (jsonb)|
-----------------------------------
|111231 |{"email": "albsmith#gmail.com", "home_zip": "07052", "lastname": "Smith",
"firstname": "Al", "home_phone": "1111111111", "middlename": "B",
"home_address1": "25 Moran Rd", "home_street_name": "Moran Rd"}
Note that I do not want to overwrite any other fields that may already exist in the jsonb object that are not specified in the update statement. For example, in this example, I would not want to overwrite the email or name fields
You could restructure your update into something like:
update
contacts c
set
data = c.data || v.data
FROM
(values
(
16247746,
'{
"latitude": 40.814140,
"longitude": -74.259250,
"home_house_num": "25",
"home_predirection": null,
"home_street_name": "Moran",
"home_street_type": "Rd"
}'::JSONB
),
(
16247747,
'{
"latitude": 20.900840,
"longitude": -156.373700,
"home_house_num": "581",
"home_predirection": "Sourth",
"home_street_name": "Pili Loko",
"home_street_type": "St"
}'::JSONB
)
) as v(
contact_id,
data
)
where
c.contact_id = v.contact_id
;

set returning functions are not allowed in CASE, in Postgres 10.6

sql is not my strong point
upgrading from postgres 9.6 to 10.6 and I get an error from this query:
SELECT id, (CASE WHEN jsonb_typeof(content->'user')='array' THEN jsonb_array_elements(content -> 'user')
ELSE content::jsonb->'user' END) As att FROM event;
error:
set returning functions are not allowed in CASE
result i get back (from v 9.6):
id, att
1, {"name": "Andrew"},
2, {"name": "Stacey"},
3, null
the 'user' element can also be null.
This is what a 'content' column could look like (either one of the two)
{"user": [{"name": "Andrew", "country_code": "GBR"}]]
{"user": null}
any help pleas been stuck on this for a while
I guess you could use
SELECT id, att
FROM event,
jsonb_array_elements(CASE WHEN jsonb_typeof(content->'user')='array'
THEN content -> 'user'
ELSE jsonb_build_array(content->'user')
END) AS user(att);