RECORD to JSONB, no automatic way? - postgresql

jsonb_build_object('a',a, 'b',b) is redundant (not "automatic"), and to_jsonb(row(a,b)) is ugly because ignores column names... Is there no way to do the right thing?
NOTES
A typical query is something as
SELECT a,b, record_to_jsonb(c,d,e) as info FROM t
I need a dynamic solution, but, to illustrate, a statical solution is to define a datatype,
CREATE TYPE mytest AS (c text, d int, e boolean);
SELECT a,b, to_jsonb(row(c,d,e)::mytest) as info FROM t; -- work fine!
PS: why PostgreSQL not offers the inverse of jsonb_to_record()? Either I'm not realizing to the see an elegant and eficient way to use to_jsonb (most probable), or, maybe PostgreSQL's architect-developers forgot the concept of library function orthogonality... There is no technical problem to implement a nice record_to_jsonb() function, as demonstrated before by functions like xmlattributes(a,b), that captures colunm name and value.

select a, b, to_jsonb(subq) as info
from t
cross join lateral (values (c, d, e)) as subq(c, d, e);
or shorter, abbreviating CROSS JOIN syntax and using SELECT clause directly
select a, b, to_jsonb(subq) as info
from t, lateral (select c, d, e) subq;
Could something like this be more in line with your use case? My thought would be to keep a and b in the json and let the requesting code just ignore it.
select a, b, to_jsonb(t) - 'a' - 'b' as info
from t

Related

RedShift: some troubles with nested json

I have next JSON:
{"promptnum":4,"corpuscode":"B0014","prompttype":"video","skipped":false,"transcription":"1","deviceinfo":{"DEVICE_ID":"exynos980","DEVICE_MANUFACTURER":"samsung","DEVICE_SERIAL":"unknown","DEVICE_DESIGN":"a51x","DEVICE_MODEL":"SM-A5160","DEVICE_OS":"android","DEVICE_OS_VERSION":"10","DEVICE_CARRIER":"","DEVICE_BATTERY_LEVEL":"70.00%","DEVICE_BATTERY_STATE":"unplugged","Current App Version":"1.1.0","Current App Build":"6"}}
I want to get values from 1-st level and 2-nd level.
1-st level: "promptnum":4,"corpuscode":"B0014","prompttype":"video","skipped":false,"transcription":"1","deviceinfo":...
2-nd level:
"deviceinfo":{"DEVICE_ID":"exynos980","DEVICE_MANUFACTURER":"samsung","DEVICE_SERIAL":"unknown","DEVICE_DESIGN":"a51x","DEVICE_MODEL":"SM-A5160","DEVICE_OS":"android","DEVICE_OS_VERSION":"10","DEVICE_CARRIER":"","DEVICE_BATTERY_LEVEL":"70.00%","DEVICE_BATTERY_STATE":"unplugged","Current App Version":"1.1.0","Current App Build":"6"}
When I parse 1-st level with
SELECT d.*
FROM (
SELECT c.json_parse, c.json_parse.deviceinfo AS device_info
FROM (
SELECT JSON_PARSE(file_attr)
FROM public.dc_ac_files
) AS c) AS d
it's work well.
But when I try to get values from 2-nd level with
SELECT d.*, l.DEVICE_ID
FROM (
SELECT c.json_parse, c.json_parse.deviceinfo AS device_info
FROM (
SELECT JSON_PARSE(file_attr)
FROM public.dc_ac_files
) AS c) AS d, d.device_info AS l
it doesn't work - no errors and no data.
If I know, it's right way to parse nested json, but it doesn't work for me.
Can you help me?
Viktor you have a couple of issues. First the notation "AS d, d.device_info AS l" is used to unnest arrays in your super data. You don't have any arrays to unnest so this is returning zero rows.
Second Redshift defaults to lower case for all column names so DEVICE_ID is being seen as device_id. You can enable case sensitive column names by setting the enable_case_sensitive_identifier connection variable to true and quoting all column names that require upper characters. "SET enable_case_sensitive_identifier TO true;" and changing l.DEVICE_ID to l."DEVICE_ID".
You also have unneeded layers in your query.
Putting all these together you can run:
SELECT l, l.deviceinfo, l.deviceinfo."DEVICE_ID"
FROM (
SELECT JSON_PARSE(file_attr) AS l
FROM public.dc_ac_files
) AS c
You also don't need SUPER data type to perform this. This can be done with json string parsing functions.
SELECT file_attr, json_extract_path_text(file_attr, 'deviceinfo') as deviceinfo, json_extract_path_text(file_attr, 'deviceinfo','DEVICE_ID') as device_id
FROM public.dc_ac_files

PostgreSQL OUTER join logic

I have a pretty old informix procedure which I would like to use in my new PostgreSQL db.
I'm new to Postgres and I feel like the joins are pretty different.
This is a part of my old Informix code:
CREATE PROCEDURE mw_getsvid(bid INT)
RETURNING INT;
DEFINE aid INT;
SELECT a.id INTO aid
FROM cerberus c, delphi d,
OUTER (emilia e, fragile f)
WHERE c.id = [...]
RETURN aid;
END PROCEDURE;
so what I am trying to do is that c outer joins with e or f. or d outer joins with e or f.
I would be pretty happy if anyone could send me his ideas or a simillar example.
You'll have to use the SQL standard join syntax with PostgreSQL:
FROM cerberus c
JOIN delphi d ON d.col1 = c.col2
LEFT JOIN emilia e ON e.col3 = c.col4
LEFT JOIN fragile f ON f.col5 = c.col6 AND f.col7 = d.col8
Joins are left associative, but you can use parentheses to override that.
Of course the order in which you write doen joins is not necessarily the order in which they are executed.
See the documentation for details.
This answer also has interesting information.

Is JPA subquery in FROM clause possible?

I'm having a little problem with JPA. Consider this scenario:
Table A (id_a) | Table B (id_b, id_a)
What I need is a query like this:
Select a.*, c.quantity from A as a, (Select Count(*) as quantity
from B as b where b.id_a = a.id_a) as c;
The thing is that I want to use a jpa query and not a native query, something like this:
Select a, c FROM A a, (Select Count(b) FROM B b where a.idA = b.a.idA) c;
So then I could iterate from the result (a list of Object[] with a and c in each node) and then assign a.quantity = c;
I repeat, I don't want to use native query, but I found no other way than use redundant data, and add another column to A called Quantity and every time I insert and delete from B, update this column in A.
Please help, I read somewhere that JPA doesn't accept subqueries in Form clause, so, what can I do ?
Thanks a lot !
JPA does not support sub-selects in the FROM clause but EclipseLink 2.4 current milestones builds does have this support.
See,
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Sub-selects_in_FROM_clause
You can probably rewrite the query with just normal joins though.
Maybe,
Select a, size(a.bs) from A a
or
Select a, count(b) from A a join a.bs b group by a

Fetch a column with a string variable

Is there a way to fetch a specific column based on the text from a variable.
For instance you have a table with a, b, c
You want to query table d, e, f
Column c stores the field name of e or f
SELECT a, b, CAST(c as ?) AS e_or_f FROM table1 JOIN table2
--
Obviously you could use an if/else or case operator but not having to write that out would be nice.
Thanks
In this case, what you really want is to use CASE, possibly mixed with a CTE or an inline view. For example:
SELECT CASE WHEN 'foo' = myvar THEN foo
WHEN 'bar' = myvar THEN bar
WHEN 'baz' = myvar THEN baz
FROM foobarbaz
CROSS JOIN (SELECT ?::text AS myvar) v;
In order for a query to be efficient, the engine needs to know precisely what columns the query is about to return, because the query plan may greatly depend upon it.
With structures like CASE, there's a certain level of dynamicity in the query, but the query planner can still have an idea about what columns may need to be retrieved in one case or another before the query is executed. With a truly dynamic approach, when a column name is supposed to be retrieved from another column, variable or query parameter, it would be extremely difficult, if ever possible, to come up with a really efficient query plan.
So, you really need to let the query planner know about the required data at the time of compiling the query. Therefore, either use CASE/inline IF/what-have-you, or return both columns and choose between the two in the calling application as necessary.

PostgreSQL: Using subquery abbreviation ('AS') in the WHERE clause

Consider the following query in PostgreSQL:
SELECT
a, b,
(A VERY LONG AND COMPLICATED SUBQUERY) AS c,
(ANOTHER VERY LONG AND COMPLICATED SUBQUERY) AS d
FROM table
I want to have c and d in the WHERE clause, like:
WHERE c AND d;
But, as far as I know, I can only do:
WHERE A VERY LONG AND COMPLICATED SUBQUERY) AND
(ANOTHER VERY LONG AND COMPLICATED SUBQUERY)
Which is clumsy, code-replicating, breaking the single-choice principle and utterly ugly.
By the way, the same problem applies to the SELECT clause: I can not use abbreviations for previously-defined subqueries.
You could use a subquery:
SELECT a,b,c,d FROM
(SELECT
a, b,
(A VERY LONG AND COMPLICATED SUBQUERY) AS c,
(ANOTHER VERY LONG AND COMPLICATED SUBQUERY) AS d
FROM table
) AS T1
WHERE c AND d
You could also do this with a CTE.