PostgreSQL insert into and select from an array of user defined objects - sql

I've been having some issues while trying to learn PostgreSQL. I've created a relational object called person and then a table consisting of a primary integer key and an array of person objects. I have a feeling it's the way I am inserting rows into this array however, I am unsure of how to access specific columns of the object as well (Ex. person.name).
Currently the only way I was able to insert a row is as follows however, I think it may just be making a string object instead of the proper person object.
INSERT INTO towns VALUES (0, '{"(bob,blue,springfield,33)"}');
For reference the schema I created is:
CREATE TYPE person AS (
name text,
favorite_color text,
hometown text,
age integer
);
CREATE TABLE towns (
town_id integer PRIMARY KEY,
people person[]
);

That's one of the reasons I prefer the array[...] syntax over '{...}'. You don't need to think about nesting quotes:
INSERT INTO towns (town_id, people)
VALUES
(0, array[('bob','blue','springfield',33)::person]);
('bob','blue','springfield',33)::person creates a record of type person and array[...] that makes that a single element array. You have to cast the anonymous record created with (...) to person to make this work. If you want to insert multiple person records into the array it's a bit less typing to case the whole array at the end:
array[('bob','blue','springfield',33), ('arthur','red','sleepytown',42)]::person[]
To select a specific element of a record in an array, you can use e.g:
select town_id, people[1].name
from towns

You are combining 2 things here, a field type as array of a composite type person.
To insert a composite type you should do something like this ROW("bob","blue","springfield",33), note that ROW keyword is optional
For array types you should use brackets inside single quotes, like '{val1,val2}', in your case, you are adding only one element of the array, which result is a person type.
Your example should look like:
INSERT INTO towns VALUES (0, '{ROW("bob","blue","springfield",33)}');
Note that the double quotes are for values inside the person type, not for the whole object.
References: Composite Types, Arrays
To access the person value, you have to wrap the object in (), so (person).name

The only way I could get this to work was by inserting in the following way:
INSERT INTO towns VALUES (0, (array[ROW('bob','blue','springfield',33)::person]));
And to select you would have to do this:
select (people[1]).age from towns;

Related

Insert into Nested records in Bigquery FROM another nested table

I am trying to insert data from one Bigquery table (nested) to another bigquery table (nested). However, I am getting issues during insert.
Source schema: T1
FieldName Type Mode
User STRING NULLABLE
order RECORD REPEATED
order.Name STRING NULLABLE
order.location STRING NULLABLE
order.subscription RECORD NULLABLE
order.subscription.date TIMESTAMP NULLABLE
order.Details RECORD REPEATED
order.Details.id STRING NULLABLE
order.Details.nextDate STRING NULLABLE
Target schema: T2
FieldName Type Mode
User STRING NULLABLE
order RECORD REPEATED
order.Name STRING NULLABLE
order.location STRING NULLABLE
order.subscription RECORD NULLABLE
order.subscription.date TIMESTAMP NULLABLE
order.Details RECORD REPEATED
order.Details.id STRING NULLABLE
order.Details.nextDate STRING NULLABLE
I am trying to use insert into functionality of bigquery. I am looking to insert only few field from source table. My query is like below:
INSERT INTO T2 (user,order.name,order.subscription.date,details.id)
SELECT user,order.name,order.subscription.date,details.id
from
T1 o
join unnest (o.order) order,
unnest ( order.details) details
After a bit of googling I am aware that I would need to use STRUCT when defining field names while inserting, but not sure how to do it. Any help is appreciated. Thanks in advance!
You will have to insert the records as per is needed in your destination table, Struct types need to be inserted fully ( with all the records that it contains ).
I provide a small sample below, I build the following table with a single record to explain this:
create or replace table `project-id.dataset-id.table-source` (
user STRING,
order_detail STRUCT<name STRING, location STRING,subscription STRUCT<datesub TIMESTAMP>,details STRUCT<id STRING,nextDate STRING>>
)
insert into `project-id.dataset-id.table-source` (user,order_detail)
values ('Karen',STRUCT('ShopAPurchase','Germany',STRUCT('2022-03-01'),STRUCT('1','2022-03-05')))
With that information we can now star inserting into our destination tables. In our sample, I'm reusing the source table and just adding an additional record into it like this:
insert into `project-id.dataset-id.table-source` (user,order_detail)
select 'Anna',struct(ox.name,'Japan',ox.subscription,struct('2',dx.nextDate))
from `project-id.dataset-id.table-source` o
join unnest ([o.order_detail]) ox, unnest ([o.order_detail.details]) dx
You will see that in order to perform an unnesting structs I will have to add the value inside an array []. As unnest flatens the struct as a single row. Also, when inserting struct types you will also have to create the struct or use the flattening records to create that struct column.
If you want to add additional records inside a STRUCT you will have to declare your destination table with an ARRAY inside of it. Lets look at this new table source_array:
create or replace table `project-id.dataset-id.table-source_array` (
user STRING,
order_detail STRUCT<name STRING, location STRING,subscription STRUCT<datesub TIMESTAMP>,details ARRAY<STRUCT<id STRING ,nextDate STRING>>>
)
insert into `project-id.dataset-id.table-source_array` (user,order_detail)
values ('Karen',STRUCT('ShopAPurchase','Germany',STRUCT(['2022-03-01']),STRUCT('1','2022-03-05')))
insert into `project-id.dataset-id.table-source_array` (user,order_detail)
select 'Anna',struct(ox.name,'Japan',ox.subscription,[struct('2',dx.nextDate),struct('3',dx.nextDate)])
from `project-id.dataset-id.table-source` o
join unnest ([o.order_detail]) ox, unnest ([o.order_detail.details]) dx
Keep in mind that you should be careful as when dealing with this as you might encounter subarrays error which may cause issues.
I make use of the following documentation for this sample:
STRUCT
UNNEST

Most elegant way to create 'subrecord' type keeping names of columns

So I'm playing with Postgres' composite types, and I cannot figure out one thing. Suppose I want to use subset of columns of certain table, or mix of different columns of several different tables used in the query, and create a record type out of them.
Logically, simple (c.id, c.name) should work, but it seems that column names are actually lost - it's not possible to address fields of the records by name and id, and, for example, to_json function cannot use field names when creating json out of this record. Using subquery (select c.id, c.name) is predictably failing with subquery must return only one column error.
I can, of course, use lateral join or common table expression to create this sub-type, but I'm thinking - if there's more elegant way?
see db<>fiddle demo with table example and test query
create table test(id integer, name text, price int);
insert into test(id,name,price)
values
(1,'name1',1),
(2,'name2',12),
(3,'name3',23),
(5,'name5',4),
(9,'name9',3);
create type sub_test as (id integer, name text);
select
c.price,
-- using predefined type - works
to_json((c.id, c.name)::sub_test),
-- creating row type on the fly - doesn't work, names are lost
to_json((c.id, c.name)),
-- using derived table created with lateral join - works
to_json(d)
from test as c, lateral(select c.id, c.name) as d

How can I insert a key-value pair into a hive map?

Based on the following tutorial, Hive has a map type. However, there does not seem to be a documented way to insert a new key-value pair into a Hive map, via a SELECT with some UDF or built-in function. Is this possible?
As a clarification, suppose I have a table called foo with a single column, typed map, named column_containing_map.
Now I want to create a new table that also has one column, typed map, but I want each map (which is contained within a single column) to have an additional key-value pair.
A query might look like this:
CREATE TABLE IF NOT EXISTS bar AS
SELECT ADD_TO_MAP(column_containing_map, "NewKey", "NewValue")
FROM foo;
Then the table bar would contain the same maps as table foo except each map in bar would have an additional key-value pair.
Consider you have a student table which contains student marks in various subjects.
hive> desc student;
id string
name string
class string
marks map<string,string>
You can insert values directly to table as below.
INSERT INTO TABLE student
SELECT STACK(1,
'100','Sekar','Mathematics',map("Mathematics","78")
)
FROM empinfo
LIMIT 1;
Here 'empinfo' table can be any table in your database.
And Results are:
100 Sekar Mathematics {"Mathematics":"78"}
for key-value pairs, you can insert like following sql:
INSERT INTO TABLE student values( "id","name",'class',
map("key1","value1","key2","value2","key3","value3","key4","value4") )
please pay attention to sequence of the values in map.
I think the combine function from brickhouse will do what you need. Slightly modifying the query in your original question, it would look something like this
SELECT
combine(column_containing_map, str_to_map("NewKey:NewValue"))
FROM
foo;
The limitation with this example is that str_to_map creates a MAP< STRING,STRING >. If your hive map contains other primitive types for the keys or values, this won't work.
I'm sorry, I didn't quite get this. What do you mean by with some UDF or built-in function?If you wish to insert into a table which has a Map field it's similar to any other datatype. For example :
I have a table called complex1, created like this :
CREATE TABLE complex1(c1 array<string>, c2 map<int,string> ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY '-' MAP KEYS TERMINATED BY ':' LINES TERMINATED BY '\n';
I also have a file, called com.txt, which contains this :
Mohammad-Tariq,007:Bond
Now, i'll load this data into the above created table :
load data inpath '/inputs/com.txt' into table complex1;
So this table contains :
select * from complex1;
OK
["Mohammad","Tariq"] {7:"Bond"}
Time taken: 0.062 seconds
I have one more table, called complex2 :
CREATE TABLE complex2(c1 map<int,string>);
Now, to select data from complex1 and insert into complex2 i'll do this :
insert into table complex2 select c2 from complex1;
Scan the table to cross check :
select * from complex2;
OK
{7:"Bond"}
Time taken: 0.062 seconds
HTH

How to test a CONSTRAINT in oracle

I have the following constraint:
ALTER TABLE Movie
ADD CONSTRAINT NomsRANGE
CHECK (totalNoms BETWEEN 0 AND 20);
...and used the following to try and test it:
INSERT INTO Movie
(totalNoms)
VALUES
('23');
I get the following error:
cannot insert NULL into ("ALI"."MOVIE"."MOVIEID")
My scheme is:
Actor (actorID, lastName, firstName, middleName, suffix, gender, birthDate, deathDate)
Movie (movieID, title, year, company, totalNoms, awardsWon, DVDPrice, discountPrice)
Quote (quoteID, quote)
Role (roleID ,roleName ,gender ,actorID* ,movieID*)
RoleQuote (roleID*, quoteID*)
And my relationships are:
CONSTRAINT_NAME C
------------------------------ -
QUOTE_FK R
ROLE_FK R
MOVIE_ROLE_FK R
ACTOR_ROLE_FK R
ACTORID P
MOVIEID P
QUOTEID P
ROLEID P
ROLEQUOTEID P
9 rows selected.
Answer:
The answer was simple none of you spotted this it took me all day but i solved it so am posting solution so it helps others.
INSERT INTO Movie(movieID, totalNoms)
VALUES('049', '22');
I missed the first value which was the primary key as the error was saying:
cannot insert NULL into ("ALI"."MOVIE"."MOVIEID")
When i entered a new primary key it showed the constraint was violated
Thank you guys for all the help
If you omit columns in an INSERT statement, the database still needs to fill the column for that row. Which would be NULL, unless you setup a DEFAULT constraint/etc for each column.
The error is telling you that MOVIE.movieid does not accept NULL, which is good. It sounds like you need to setup the sequence to populate the value, or provide logic for however you want MOVIE.movieid populated (far less ideal). The sequence can either be referenced in the INSERT statement to fill the value, or you can look at triggers/default constraints to handle things in the background.
You'll have to figure out how you want/need to handle any other errors relating to column NULLability. Only after this is done, will you be able to see if your CHECK constraint works -- there's a little concern about if the value is provided as a string, if Oracle will implicitly convert it to an INT/NUMERIC data type.
1) The specific error is telling you that your INSERT statement needs to specify the MovieID. If you have created sequences to generate your synthetic primary key values, you'd want something like this (assuming there are no other NOT NULL columns in the Movie table)
INSERT INTO Movie
(movieID, totalNoms)
VALUES
(movieId_seq.nextval, 23);
2) Assuming that totalNoms is a NUMBER since your check constraint is treating it like a number, you would want to insert the number 23 rather than the string 23. Forcing Oracle to do implicit conversions is never a good idea-- it probably won't matter on a simple INSERT like this but if you get in the habit of using numbers when you're dealing with numeric fields and strings when you're dealing with character fields, life will be a lot easier.

SQLite- elegantly setting IDs

I want to use a database like:
table wrongdoer (primarykey integer
ID, date)
table crime (numeric
foreign_key_to_wrongdoer, text crime)
I want my application (in python) to register a wrongdoer (which should give the entry a unique integer) and register the first crime against him. My idea is rather clumsy:
insert into wrongdoer(...)
id=cur.execute("select max(ID)")
//this is to select the most recent ID
cur.execute("insert into
crime('"+id+"'"crime+"'")")
That is, insert an entry, select its unique key from DB (assuming its the highest number), and then use it for the subsequent queries. Is there a better way to do it?
Check this function out:
http://www.sqlite.org/c3ref/last_insert_rowid.html