PostgreSQL array in form type: value - sql

Is there any way to create an array in PostgreSQL which contains multiple data types in form type:value?
For example, one of the table records should be an array with values height:190, color:black etc.
If it isn't possible with arrays, how could I mannage this other way?

https://www.postgresql.org/docs/current/static/hstore.html
This module implements the hstore data type for storing sets of
key/value pairs within a single PostgreSQL value
t=# select ('height=>190, color=>black')::hstore;
hstore
-----------------------------------
"color"=>"black", "height"=>"190"
(1 row)
https://www.postgresql.org/docs/current/static/datatype-json.html
JSON data types are for storing JSON (JavaScript Object Notation)
data, as specified in RFC 7159. Such data can also be stored as text,
but the JSON data types have the advantage of enforcing that each
stored value is valid according to the JSON rules.
t=# select '{"height":190, "color":"black"}'::json;
json
---------------------------------
{"height":190, "color":"black"}
(1 row)

Related

Extract key value pair from json column in redshift

I have a table mytable that stores columns in the form of JSON strings, which contain multiple key-value pairs. Now, I want to extract only a particular value corresponding to one key.
The column that stores these strings is of varchar datatype, and is created as:
insert into mytable(empid, json_column) values (1,'{"FIRST_NAME":"TOM","LAST_NAME" :"JENKINS", "DATE_OF_JOINING" :"2021-06-10", "SALARY" :"1000" }').
As you can see, json_column is created by inserting only a string. Now, I want to do something like:
select json_column.FIRST_NAME from mytable
I just want to extract the value corresponding to key FIRST_NAME.
Though my actual table is far more complex than this example, and I cannot convert these JSON keys into different columns themselves. But, this example clearly illustrates my issue.
This needs to be done over Redshift, please help me out with any valuable suggestions.
using function json_extract_path_text of Redshift can solve this problem easily, as follows:
select json_extract_path_text(json_column, 'FIRST_NAME') from mytable;

check if a jsonb field contains an array

I have a jsonb field in a PostgreSQL table which was supposed to contain a dictionary like data aka {} but few of its entries got an array due to source data issues.
I want to weed out those entries. One of the ways is to perform following query -
select json_field from data_table where cast(json_field as text) like '[%]'
But this requires converting each jsonb field into text. With data_table having order of 200 million entries, this looks like bit of an overkill.
I investigated pg_typeof but it returns jsonb which doesn't help differentiate between a dictionary and an array.
Is there a more efficient way to achieve the above?
How about using the json_typeof function?
select json_field from data_table where json_typeof(json_field) = 'array'

Store SELECT output to 'json' type field

I would like to store some of rows of data in database to as a JSON (in json type field) for backup reasons, before transaction is launched.
Something like:
INSERT INTO public.backup (user_id, data) VALUES (1, (SELECT * FROM ...))
Is it possible to do it simple, without parsing select and converting it to JSON in my application?
You can convert whole rows to json with row_to_json():
INSERT INTO public.backup (user_id, data)
SELECT 1, row_to_json(t)
FROM tbl t
WHERE ...; -- select some rows
It's not as simple to preserve column names if the source is a query rather than a plain table. See:
Return multiple columns of the same row as JSON array of objects
In Postgres 9.4 or later, consider the data type jsonb for your data column. Same query, the result is cast to jsonb with the assignment automatically.
How do I query using fields inside the new PostgreSQL JSON datatype?

PostgreSQL - best way to return an array of key-value pairs

I'm trying to select a number of fields, one of which needs to be an array with each element of the array containing two values. Each array item needs to contain a name (character varying) and an ID (numeric). I know how to return an array of single values (using the ARRAY keyword) but I'm unsure of how to return an array of an object which in itself contains two values.
The query is something like
SELECT
t.field1,
t.field2,
ARRAY(--with each element containing two values i.e. {'TheName', 1 })
FROM MyTable t
I read that one way to do this is by selecting the values into a type and then creating an array of that type. Problem is, the rest of the function is already returning a type (which means I would then have nested types - is that OK? If so, how would you read this data back in application code - i.e. with a .Net data provider like NPGSQL?)
Any help is much appreciated.
ARRAYs can only hold elements of the same type
Your example displays a text and an integer value (no single quotes around 1). It is generally impossible to mix types in an array. To get those values into an array you have to create a composite type and then form an ARRAY of that composite type like you already mentioned yourself.
Alternatively you can use the data types json in Postgres 9.2+, jsonb in Postgres 9.4+ or hstore for key-value pairs.
Of course, you can cast the integer to text, and work with a two-dimensional text array. Consider the two syntax variants for a array input in the demo below and consult the manual on array input.
There is a limitation to overcome. If you try to aggregate an ARRAY (build from key and value) into a two-dimensional array, the aggregate function array_agg() or the ARRAY constructor error out:
ERROR: could not find array type for data type text[]
There are ways around it, though.
Aggregate key-value pairs into a 2-dimensional array
PostgreSQL 9.1 with standard_conforming_strings= on:
CREATE TEMP TABLE tbl(
id int
,txt text
,txtarr text[]
);
The column txtarr is just there to demonstrate syntax variants in the INSERT command. The third row is spiked with meta-characters:
INSERT INTO tbl VALUES
(1, 'foo', '{{1,foo1},{2,bar1},{3,baz1}}')
,(2, 'bar', ARRAY[['1','foo2'],['2','bar2'],['3','baz2']])
,(3, '}b",a{r''', '{{1,foo3},{2,bar3},{3,baz3}}'); -- txt has meta-characters
SELECT * FROM tbl;
Simple case: aggregate two integer (I use the same twice) into a two-dimensional int array:
Update: Better with custom aggregate function
With the polymorphic type anyarray it works for all base types:
CREATE AGGREGATE array_agg_mult (anyarray) (
SFUNC = array_cat
,STYPE = anyarray
,INITCOND = '{}'
);
Call:
SELECT array_agg_mult(ARRAY[ARRAY[id,id]]) AS x -- for int
,array_agg_mult(ARRAY[ARRAY[id::text,txt]]) AS y -- or text
FROM tbl;
Note the additional ARRAY[] layer to make it a multidimensional array.
Update for Postgres 9.5+
Postgres now ships a variant of array_agg() accepting array input and you can replace my custom function from above with this:
The manual:
array_agg(expression)
...
input arrays concatenated into array of one
higher dimension (inputs must all have same dimensionality, and cannot
be empty or NULL)
I suspect that without having more knowledge of your application I'm not going to be able to get you all the way to the result you need. But we can get pretty far. For starters, there is the ROW function:
# SELECT 'foo', ROW(3, 'Bob');
?column? | row
----------+---------
foo | (3,Bob)
(1 row)
So that right there lets you bundle a whole row into a cell. You could also make things more explicit by making a type for it:
# CREATE TYPE person(id INTEGER, name VARCHAR);
CREATE TYPE
# SELECT now(), row(3, 'Bob')::person;
now | row
-------------------------------+---------
2012-02-03 10:46:13.279512-07 | (3,Bob)
(1 row)
Incidentally, whenever you make a table, PostgreSQL makes a type of the same name, so if you already have a table like this you also have a type. For example:
# DROP TYPE person;
DROP TYPE
# CREATE TABLE people (id SERIAL, name VARCHAR);
NOTICE: CREATE TABLE will create implicit sequence "people_id_seq" for serial column "people.id"
CREATE TABLE
# SELECT 'foo', row(3, 'Bob')::people;
?column? | row
----------+---------
foo | (3,Bob)
(1 row)
See in the third query there I used people just like a type.
Now this is not likely to be as much help as you'd think for two reasons:
I can't find any convenient syntax for pulling data out of the nested row.
I may be missing something, but I just don't see many people using this syntax. The only example I see in the documentation is a function taking a row value as an argument and doing something with it. I don't see an example of pulling the row out of the cell and querying against parts of it. It seems like you can package the data up this way, but it's hard to deconstruct after that. You'll wind up having to make a lot of stored procedures.
Your language's PostgreSQL driver may not be able to handle row-valued data nested in a row.
I can't speak for NPGSQL, but since this is a very PostgreSQL-specific feature you're not going to find support for it in libraries that support other databases. For example, Hibernate isn't going to be able to handle fetching an object stored as a cell value in a row. I'm not even sure the JDBC would be able to give Hibernate the information usefully, so the problem could go quite deep.
So, what you're doing here is feasible provided you can live without a lot of the niceties. I would recommend against pursuing it though, because it's going to be an uphill battle the whole way, unless I'm really misinformed.
A simple way without hstore
SELECT
jsonb_agg(to_jsonb (t))
FROM (
SELECT
unnest(ARRAY ['foo', 'bar', 'baz']) AS table_name
) t
>>> [{"table_name": "foo"}, {"table_name": "bar"}, {"table_name": "baz"}]

When to store data in a designated table or serialized? How to make the call?

I'm using a postgresql install to house my data.
My Post model has an attribute called "selection" which currently stores data within a TEXT column in the form of: "x1,x2,x3,x4,x5..."
When I need to access this data i split it on the the comma and do my thing with it.
I'm prototyping an app so i quickly just did the easiest thing when i was writing it but now i can see an alternative option would be to create a table for "selections" and associate it back to the post, then have individual rows for each bit.
My question is, how or when do i make the choice to store or not data like this?
Thank you
PostgreSQL has array types - so you can use a "text[]" type
postgres=# create table xxx(a text[]);
CREATE TABLE
postgres=# insert into xxx values(array['x1','x2']);
INSERT 0 1
postgres=# insert into xxx values(array['x1','x2','x3']);
INSERT 0 1
postgres=# select * from xxx where 'x1' = ANY(a);
a
------------
{x1,x2}
{x1,x2,x3}
(2 rows)
postgres=# select * from xxx where 'x3' = ANY(a);
a
------------
{x1,x2,x3}
(1 row)
You can use index for large data too
If those represent other data elements in other tables in your database, then I would never store it as a comma separated string.
SQL in general is optimized for set-based arithmetic and functions, not for string parsing.
The only scenario that I can think of where the string version may be easier/faster is if you want to find a specific set of values and ONLY those values, i.e. Col = 'A1, B2, C3, d4'.
Otherwise, if you want to check individual fields or do other comparisons, storing that data in a normalized table is the best course of action. It's more extensible, easier and more efficient to check for specific values, and will make other operations on that table quicker (since you store less data in-row for that main table).