PostgreSQL: Sub-select inside insert - sql

I have a table called map_tags:
map_id | map_license | map_desc
And another table (widgets) whose records contains a foreign key reference (1 to 1) to a map_tags record:
widget_id | map_id | widget_name
Given the constraint that all map_licenses are unique (however are not set up as keys on map_tags), then if I have a map_license and a widget_name, I'd like to perform an insert on widgets all inside of the same SQL statement:
INSERT INTO
widgets w
(
map_id,
widget_name
)
VALUES (
(
SELECT
mt.map_id
FROM
map_tags mt
WHERE
// This should work and return a single record because map_license is unique
mt.map_license = '12345'
),
'Bupo'
)
I believe I'm on the right track but know right off the bat that this is incorrect SQL for Postgres. Does anybody know the proper way to achieve such a single query?

Use the INSERT INTO SELECT variant, including whatever constants right into the SELECT statement.
The PostgreSQL INSERT syntax is:
INSERT INTO table [ ( column [, ...] ) ]
{ DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query }
[ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]
Take note of the query option at the end of the second line above.
Here is an example for you.
INSERT INTO
widgets
(
map_id,
widget_name
)
SELECT
mt.map_id,
'Bupo'
FROM
map_tags mt
WHERE
mt.map_license = '12345'

INSERT INTO widgets
(
map_id,
widget_name
)
SELECT
mt.map_id, 'Bupo'
FROM
map_tags mt
WHERE
mt.map_license = '12345'

Quick Answer:
You don't have "a single record" you have a "set with 1 record"
If this were javascript: You have an "array with 1 value" not "1 value".
In your example, one record may be returned in the sub-query,
but you are still trying to unpack an "array" of records into separate
actual parameters into a place that takes only 1 parameter.
It took me a few hours to wrap my head around the "why not".
As I was trying to do something very similiar:
Here are my notes:
tb_table01: (no records)
+---+---+---+
| a | b | c | << column names
+---+---+---+
tb_table02:
+---+---+---+
| a | b | c | << column names
+---+---+---+
|'d'|'d'|'d'| << record #1
+---+---+---+
|'e'|'e'|'e'| << record #2
+---+---+---+
|'f'|'f'|'f'| << record #3
+---+---+---+
--This statement will fail:
INSERT into tb_table01
( a, b, c )
VALUES
( 'record_1.a', 'record_1.b', 'record_1.c' ),
( 'record_2.a', 'record_2.b', 'record_2.c' ),
-- This sub query has multiple
-- rows returned. And they are NOT
-- automatically unpacked like in
-- javascript were you can send an
-- array to a variadic function.
(
SELECT a,b,c from tb_table02
)
;
Basically, don't think of "VALUES" as a variadic
function that can unpack an array of records. There is
no argument unpacking here like you would have in a javascript
function. Such as:
function takeValues( ...values ){
values.forEach((v)=>{ console.log( v ) });
};
var records = [ [1,2,3],[4,5,6],[7,8,9] ];
takeValues( records );
//:RESULT:
//: console.log #1 : [1,2,3]
//: console.log #2 : [4,5,7]
//: console.log #3 : [7,8,9]
Back to your SQL question:
The reality of this functionality not existing does not change
just because your sub-selection contains only one result. It is
a "set with one record" not "a single record".

Related

SQL get the value of a nested key in a jsonb field

Let's suppose I have a table my_table with a field named data, of type jsonb, which thus contains a json data structure.
let's suppose that if I run
select id, data from my_table where id=10;
I get
id | data
------------------------------------------------------------------------------------------
10 | {
|"key_1": "value_1" ,
|"key_2": ["value_list_element_1", "value_list_element_2", "value_list_element_3" ],
|"key_3": {
| "key_3_1": "value_3_1",
| "key_3_2": {"key_3_2_1": "value_3_2_1", "key_3_2_2": "value_3_2_2"},
| "key_3_3": "value_3_3"
| }
| }
so in pretty formatting, the content of column data is
{
"key_1": "value_1",
"key_2": [
"value_list_element_1",
"value_list_element_2",
"value_list_element_3"
],
"key_3": {
"key_3_1": "value_3_1",
"key_3_2": {
"key_3_2_1": "value_3_2_1",
"key_3_2_2": "value_3_2_2"
},
"key_3_3": "value_3_3"
}
}
I know that If I want to get directly in a column the value of a key (of "level 1") of the json, I can do it with the ->> operator.
For example, if I want to get the value of key_2, what I do is
select id, data->>'key_2' alias_for_key_2 from my_table where id=10;
which returns
id | alias_for_key_2
------------------------------------------------------------------------------------------
10 |["value_list_element_1", "value_list_element_2", "value_list_element_3" ]
Now let's suppose I want to get the value of key_3_2_1, that is value_3_2_1.
How can I do it?
I have tryed with
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
but I get
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
^
HINT: No operators found with name and argument types provided. Types may need to be converted explicitly.
what am I doing wrong?
The problem in the query
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --this is wrong!
from my_table
where id=10;
was that by using the ->> operand I was turning a json to a string, so that with the next ->> operand I was trying to get a json key object key_3_2 out of a string object, which makes no sense.
Thus one has to use the -> operand, which does not convert json into string, until one gets to the "final" key.
so the query I was looking for was
select id, data->'key_3'->'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --final ->> : this gets the value of 'key_3_2_1' as string
from my_table
where id=10;
or either
select id, data->'key_3'->'key_3_2'->'key_3_2_1' alias_for_key_3_2_1 --final -> : this gets the value of 'key_3_2_1' as json / jsonb
from my_table
where id=10;
More info on JSON Functions and Operators can be find here

Querying based on JSON array sub element

Tried multiple answers from here and elsewhere and couldn't find the right answer yet.
create table mstore (
muuid uuid PRIMARY KEY,
msid text,
m_json JSONb[] not NULL
);
inserted first row:
insert into mstore (muuid, msid, m_json) values (
'3b691440-ee54-4d9d-a5b3-5f1863b78755'::uuid,
'<163178891004.4772968682254423915#XYZ-73SM>',
(array['{"m": 123, "mts": "2021-09-16T10:53:43.599012", "dstatus": "Dropped", "rcpt": "abc1#xyz.com"}']::jsonb[])
);
inserted second row:
insert into mstore (muuid, msid, m_json) values (
'3b691440-ee54-4d9d-a5b3-5f1863b78757'::uuid,
'<163178891004.4772968682254423915#XYZ-75SM>',
(array['{"m": 125, "mts": "2021-09-16T10:53:43.599022", "dstatus": "Dropped", "rcpt": "abc3#xyz.com"}']::jsonb[])
);
updated the first row:
update mstore
set m_json = m_json || '{"m": 124, "mts": "2021-09-16T10:53:43.599021", "dstatus": "Delivered", "rcpt": "abc2#xyz.com"}'::jsonb
where muuid = '3b691440-ee54-4d9d-a5b3-5f1863b78755';
Now table looks like:
muuid | msid | m_json
--------------------------------------+----------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3b691440-ee54-4d9d-a5b3-5f1863b78757 | <163178891004.4772968682254423915#XYZ-75SM> | {"{\"mid\": 125, \"rcpt\": \"abc3#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599022\", \"dstatus\": \"Dropped\"}"}
3b691440-ee54-4d9d-a5b3-5f1863b78755 | <163178891004.4772968682254423915#XYZ-73SM> | {"{\"mid\": 123, \"rcpt\": \"abc1#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599012\", \"dstatus\": \"Dropped\"}","{\"mid\": 124, \"rcpt\": \"abc2#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599021\", \"dstatus\": \"Delivered\"}"}
Now, I need to query based on the status. I tried few but most relevant one was
select * from mstore,jsonb_array_elements(m_json) with ordinality arr(item_object, position) where item_object->>'{"dstatus": "Delivered"}';
and
select * from mstore where m_json #> '[{"dstatus": "Delivered"}]';
Neither work, as they have syntax errors. How to run this query with dstatus values?
Please note that mstore.m_json is a Postgres array of JSONB elements and not a JSONB array and therefore unnest must be used rather than jsonb_array_elements. Also have a look at ->> operator in the documentation.
The same applies to your second example. It would work if mstore.m_json is a JSONB array and not a Postgres array of JSONB elements.
select m.muuid, m.msid, l.item_object, l.pos
from mstore m
cross join lateral unnest(m.m_json) with ordinality l(item_object, pos)
where l.item_object ->> 'dstatus' = 'Delivered';
It would be better to use JSONB data type for column mstore.m_json rather than JSONB[] or - much better - normalize the data design.

How to store an array of date ranges in Postgres?

I am trying to build a schedule, I generate an array of objects on the client containing date ranges
[
{start: "2020-07-06 0:0", end: "2020-07-10 23:59"},
{start: "2020-07-13 0:0", end: "2020-07-17 23:59"}
]
I have a column of type daterange[] what is the proper way to format this data to insert it into my table?
This is what I have so far:
INSERT INTO schedules(owner, name, dates) VALUES (
1,
'work',
'{
{[2020-07-06 0:0,2020-07-10 23:59]},
{[2020-07-13 0:0,2020-07-17 23:59]}
}'
)
I think you want:
insert into schedules(owner, name, dates) values (
1,
'work',
array[
'[2020-07-06, 2020-07-11)'::daterange,
'[2020-07-13, 2020-07-18)'::daterange
]
);
Rationale:
you are using dateranges, so you cannot have time portions (for this, you would need tsrange instead); as your code stands, it seems like you want an inclusive lower bound and an exclusive upper bound (hence [ at the left side, and ) at the right side)
explicit casting is needed so Postgres can recognize the that array elements have the proper datatype (otherwise, they look like text)
then, you can surround the list of ranges with the array[] constructor
Demo on DB Fiddle:
owner | name | dates
----: | :--- | :----------------------------------------------------
1 | work | {"[2020-07-06,2020-07-11)","[2020-07-13,2020-07-18)"}

how to check a value of a key is true in postgres jsonb query

For example my table is :
CREATE TABLE mytable (
id bigint NOT NULL,
foo jsonb
);
and it has some values :
id | foo
-----+-------
1 | "{'a':false,'b':true}"
2 | "{'a':true,'b':false}"
3 | NULL
I want to know how to check if value of a key is true , and which operator should I use?
I want something like this that can check the value :
SELECT 1
FROM mytable
WHERE
id=2
AND
foo['a'] is true
;
The syntax foo['a'] is invalid in Postgres.
If you want to access the value of a key, you need to use the ->> operator as documented in the manual
select *
from mytable
where id = 2
and foo ->> 'a' = 'true';
SELECT 1
FROM mytable
Where
id=2
AND
(foo ->> 'a')::boolean is true;
;
More correct might be
SELECT 1
FROM mytable
WHERE id=2
AND (foo -> 'a') = 'true'::JSONB;
This has the benefit of allowing postgres to make better use of any indexes you may have on your jsonB data as well as avoiding some of the ambiguity with the ->> operator that others have mentioned.
Using ->>
=> SELECT (('{"a": true}'::JSONB)->>'a') = 'true' as result;
result
--------
t
(1 row)
=> SELECT (('{"a": "true"}'::JSONB)->>'a') = 'true' as result;
result
--------
t
(1 row)
Using ->
=> SELECT (('{"a": "true"}'::JSONB)->'a') = 'true'::JSONB as result;
result
--------
f
(1 row)
=> SELECT (('{"a": true}'::JSONB)->'a') = 'true'::JSONB as result;
result
--------
t
(1 row)
Note: This is the same as Tamlyn's answer, but with an included example of how to compare against a JSONB true.
To get the text value of a key use ->> (double head) and to get the json or jsonb value use -> (single head).
Be careful because the text representations of JSON boolean value true and string value "true" are both true.
tamlyn=# select '{"a":true}'::json->>'a' bool, '{"a":"true"}'::json->>'a' str;
bool | str
------+------
true | true
(1 row)
In your case you probably want ->.
tamlyn=# select '{"a":true}'::json->'a' bool, '{"a":"true"}'::json->'a' str;
bool | str
------+--------
true | "true"
(1 row)
Get the JSON object field, cast to boolean and do a regular SQL where clause:
select *
from mytable
where (foo -> 'a')::boolean is true;

Using an equality check between columns in a SELECT clause

I am using Microsoft SQL Server 2012 and I would like to run this seemingly simple query:
SELECT
FirstEvent.id AS firstEventID,
SecondEvent.id AS secondEventID,
DATEDIFF(second, FirstEvent.WndFGEnd, SecondEvent.WndFGStart) AS gap,
FirstEvent.TitleID = SecondEvent.TitleID AS titlesSameCheck
FROM VibeFGEvents AS FirstEvent
RIGHT OUTER JOIN VibeFGEvents AS SecondEvent
ON
FirstEvent.intervalMode = SecondEvent.intervalMode
AND FirstEvent.id = SecondEvent.id - 1
AND FirstEvent.logID = SecondEvent.logID
However FirstEvent.TitleID = SecondEvent.TitleID AS titlesSameCheck in the SELECT clause is incorrect syntax. But the SELECT Clause (Transact-SQL) documentation includes this syntax:
SELECT [ ALL | DISTINCT ]
[ TOP ( expression ) [ PERCENT ] [ WITH TIES ] ]
<select_list>
<select_list> ::=
{
*
| { table_name | view_name | table_alias }.*
| {
[ { table_name | view_name | table_alias }. ]
{ column_name | $IDENTITY | $ROWGUID }
| udt_column_name [ { . | :: } { { property_name | field_name }
| method_name ( argument [ ,...n] ) } ]
| expression
[ [ AS ] column_alias ]
}
| column_alias = expression
} [ ,...n ]
I think that means an expression is valid in the select clause and indeed the examples given include things like 1 + 2. Looking at the documentation for expressions:
{ constant | scalar_function | [ table_name. ] column | variable
| ( expression ) | ( scalar_subquery )
| { unary_operator } expression
| expression { binary_operator } expression
| ranking_windowed_function | aggregate_windowed_function
}
boolean equality checks are valid expressions and indeed the example expression given in the = (Equals) (Transact-SQL) documentation includes one:
SELECT DepartmentID, Name
FROM HumanResources.Department
WHERE GroupName = 'Manufacturing'
albeit in the WHERE clause not the SELECT clause. It looks like I cannot use = the equality operator to compare expressions in my SELECT clause as they are being wrongly interpreted as assignment.
How do I include a Boolean equality column comparison equivalent to FirstEvent.TitleID = SecondEvent.TitleID AS titlesSameCheck in my SELECT clause?
Like this:
case when FirstEvent.TitleID = SecondEvent.TitleID then 1 else 0 end as titlesSameCheck
You cannot use the Boolean type directly except in conditional statements (case, where, having, etc.)
Best way to solve your problem is to do something like
select case when x = y then 'true' else 'false' end
The bit type is probably the closest to boolean.
select CAST(case when x = y then 1 else 0 end as bit)
Of course, use whichever two values best represent what you are after.
As the two existing answers state, boolean values can't be returned as a column value. This is documented in the Comparison Operators section:
Unlike other SQL Server data types, a Boolean data type cannot be
specified as the data type of a table column or variable, and cannot
be returned in a result set.
Given that restriction, using CASE to transform the value to something that can be displayed is your best alternative.