postgresql using json sub-element in where clause - sql

This might be a very basic question but I am not able to find anything on this online.
If I create a sample table :
create table dummy ( id int not null, data json );
Then, if I query the table using the following query:
select * from dummy where data->'x' = 10;
Now since there are no records in the table yet and there is no such property as 'x' in any record, it should return zero results.
But I get the following error:
postgres=# select * from dummy where data->'x' = 10;
ERROR: operator does not exist: json = integer
LINE 1: select * from dummy where data->'x' = 10;
However following query works:
select * from dummy where cast(data->>'x' as integer) = 10;
Am I missing something here or typecasting is the only way I can get an integer value from a json field ? If that's the case, does it not affect the performance when data becomes extremely large ?

Am I missing something here or typecasting is the only way I can get
an integer value from a json field ?
You're correct, typecasting is the only way to read an integer value from a json field.
If that's the case, does it not affect the performance when data
becomes extremely large ?
Postgres allows you to index functions including casts, so the index below will allow you to quickly retrieve all rows where data->>x has some integer value
CREATE INDEX dummy_x_idx ON dummy(cast("data"->>'x' AS int))

JSON operator ->> means Get JSON array element (or object field) as text, so type cast is necessary.
You could define your own JSON operator, but it would only simplify the code, without consequences for performance.

Related

Need a query that works in both cases when column type is either text or text[]

Following are my 2 tables
CREATE TABLE temptext (
id text
);
CREATE TABLE temptext2 (
id _text
);
My insert queries are
INSERT INTO temptext
(id)VALUES
('a');
INSERT INTO temptext2
(id)VALUES
('{c,d,e,a}');
I have 2 queries that run perfectly
SELECT *
FROM temptext2
WHERE ( ('edfdas' = ANY ("id")))
and
SELECT *
FROM temptext
WHERE ( ("id" = ANY ('{"edfdas"}')))
If I replace temptext2 with temptext in either of 1 then it fails giving error -
ERROR: could not find array type for data type text[]
or
ANY/ALL (array) requires array on right side
I need a query that runs in both cases since I don't know the data type in the database whether it is text or _text.
PS: There can be multiple values along with 'edfdas'
One way to get around this is, is to make sure you always compare the value with an array by turning the text column into a text[]. This can be achieved by appending the column to an empty array. This works for text and text[] alike
SELECT *
FROM temptext
WHERE 'one' = any(array[]::text[]||id)
The query works for both tables. Concatenating a single value to an empty array yields an array with a single value. Concatenating and array to an empty array yields the original array.
However this will prevent the usage of any index. If you want an efficient solution (that can make use of possible indexes), you will have to use two different queries.
I find the fact that your application doesn't know how the tables are defined quite strange to be honest. Fixing that seems to be more efficient and less error prone in the long run.
ANY operator can only be used with an array on the right side, but you are trying to use it with a text value.
Try one of the following -
Use the IN operator instead of ANY, and enclose the value in parentheses to create a set. For example:
SELECT *
FROM temptext
WHERE 'edfdas' IN ("id")
SELECT *
FROM temptext2
WHERE 'edfdas' IN ("id")
This will work for both tables, because the IN operator can be used with a set on the right side, and the set will be implicitly converted to an array if the column type is an array type.
You could also use the = operator and wrap the value in quotes to create a text value, like this:
SELECT *
FROM temptext
WHERE "id" = 'edfdas'
SELECT *
FROM temptext2
WHERE "id" = 'edfdas'
This will also work for both tables, because the = operator can be used with a text value on the right side, and the text value will be implicitly converted to an array if the column type is an array type.

How to avoid performance degradation when run query with cast in where clause?

I have a table with 2 varchar columns (name and value)
and I have such a query:
select * from attribute
where name = 'width' and cast( value as integer) > 12
This query works but I suppose there are could be an issue with execution plan because of index build over value column because it is technically varchar but we convert it to integer.
Are there ways to fix it ?
P.S. I can't change type to int because the database design implies that value could be any type.
Performance should not be your first worry here.
Your statement is prone to failures. You read this as:
read all rows with name = 'width'
of these rows cast all values to integer and only keep those with a value graeter than 12
But the DBMS is free to check conditions in the WHERE clause in any order. If the DBMS does that:
cast all values to integer and only keep the rows with a value graeter than 12
of these rows keep all with name = 'width'
the first step will already cause a runtime error, if there is a non-integer value in that table, which is likely.
So first get your query safe. The following should work:
select *
from
(
select *
from attribute
where name = 'width'
) widths
where cast(value as integer) > 12;
This will still fail, when your width contains non-integers. So, to get this even safe in case of invalid data in the table, you may want to add a check that the value only contains digits in the subquery.
And yes, this won't become super-fast. You sacrifice speed (and data consistency checks) for flexibility with this data model.
What you can do, however, is create an index on both columns, so the DBMS can quickly find all width rows and then have the value directly at hand, before it accesses the table:
create index idx on attribute (name, value);
As far as I know, there is no fail-safe cast function in PostgreSQL. Otherwise you could use this and have a function index instead. I may be wrong, so maybe someone can come up with a better solution here.

Parameterization of an array of enums?

I have a table where one of the fields is an array of enums. For example let say this is what it looks like:
CREATE TYPE foobar AS ENUM (
'FOO',
'BAR'
);
CREATE TABLE my_table (
id SERIAL PRIMARY KEY,
foobarray foobar[] DEFAULT ARRAY['FOO']::foobar[]
);
When I try to use node-postgres to insert/update a row it is not clear how to parameterize the array and get it type cast to an array of enums.
When I try:
const foobarray = ["BAR"];
await pool.query("UPDATE my_table SET foobarray=$2::foobar[] WHERE id=$1", [id, foobarray]);
I get:
error: invalid input value for enum foobarray: "{"
Any ideas how to get this to work?
I figured out my issue...
I was actually pulling the value, before updating it, as follows:
SELECT foobarray FROM my_table WHERE id=$1;
This resulted in the following array being in the result:
["{", "}", "FOO"]
I didn't realize this and was trying to modify the array from the result before updating which resulted in "{" and "}" which are obviously not valid ENUM values being passed through.
I was able to solve this issue by keeping my original UPDATE query the same but modifying the SELECT query to:
SELECT foobarray::text[] FROM my_table WHERE id=$1;
This results in the following array being in the result:
["FOO"]
Tweaking it and updating now causes no problems.

PostgreSQL - How to cast dynamically?

I have a column that has the type of the dataset in text.
So I want to do something like this:
SELECT CAST ('100' AS %INTEGER%);
SELECT CAST (100 AS %TEXT%);
SELECT CAST ('100' AS (SELECT type FROM dataset_types WHERE id = 2));
Is that possible with PostgreSQL?
SQL is strongly typed and static. Postgres demands to know the number of columns and their data type a the time of the call. So you need dynamic SQL in one of the procedural language extensions for this. And then you still face the obstacle that functions (necessarily) have a fixed return type. Related:
Dynamically define returning row types based on a passed given table in plpgsql?
Function to return dynamic set of columns for given table
Or you go with a two-step flow. First concatenate the query string (with another SELECT query). Then execute the generated query string. Two round trips to the server.
SELECT '100::' || type FROM dataset_types WHERE id = 2; -- record resulting string
Execute the result. (And make sure you didn't open any vectors for SQL injection!)
About the short cast syntax:
Postgres data type cast

passing an array into oracle sql and using the array

I am running into the following problem, I am passing an array of string into Oracle SQL, and I would like to retrieve all the data where its id is in the list ...
here's what i've tried ...
OPEN O_default_values FOR
SELECT ID AS "Header",
VALUE AS "DisplayValue",
VALUE_DESC AS "DisplayText"
FROM TBL_VALUES
WHERE ID IN I_id;
I_id is an array described as follows - TYPE gl_id IS TABLE OF VARCHAR2(15) INDEX BY PLS_INTEGER;
I've been getting the "expression is of wrong type" error.
The I_id array can sometimes be as large as 600 records.
My question is, is there a way to do what i just describe, or do i need to create some sort of cursor and loop through the array?
What has been tried - creating the SQL string dynamically and then con-cat the values to the end of the SQL string and then execute it. This will work for small amount of data and the size of the string is static, which will caused some other errors (like index out of range).
have a look at this link: http://asktom.oracle.com/pls/asktom/f?p=100:11:620533477655526::::P11_QUESTION_ID:139812348065
effectively what you want is a variable in-list with bind variables.
do note this:
"the" is deprecated. no need for it
today.
TABLE is it's replacement
select * from TABLE( function );
since you already have the type, all you need to do is something similar to below:
OPEN O_default_values FOR
SELECT ID AS "Header",
VALUE AS "DisplayValue",
VALUE_DESC AS "DisplayText"
FROM TBL_VALUES
WHERE ID IN (select column_value form table(I_id));