Query JSONB column using joins to filter by subquery referencing outer query - postgresql

I need to analyze survey data (stored in records) where a question can have a choice of options. My goal is to identify the answers given that were NOT within the range of allowed options for this question. However, my query returns everything (I suspect a subquery) and I don't know how to fix it.
Schema
The records stores its data in the data JSONB column. There, the keys are question UIDs, e.g. uid00000006 has the answer option1. option1 is a choice to select.
(Not all questions need to have a dropdown, so some other value is fine such as 42.)
{"uid00000006": {"value": "option1"}, "uid00000008": {"value": 42}}
A question optionally has a reference to a optionset (the dropdown) which has a range of optionvalues (the values of the dropdown) , e.g. option1, option2, option3 etc.
create table record
(
recordid bigint not null primary key,
uid varchar(11) unique,
data jsonb default '{}'::jsonb not null
);
create table question
(
questionid bigint not null primary key,
uid varchar(11) not null unique,
optionsetid bigint
);
create table optionset
(
optionsetid bigint not null primary key,
uid varchar(11) not null unique
);
create table optionvalue
(
optionvalueid bigint not null primary key,
uid varchar(11) not null unique,
code varchar(230) not null,
optionsetid bigint
);
-- create optionset
INSERT INTO optionset (optionsetid, uid) VALUES (1, 'uid00000001');
-- insert optionvalues into optionset
INSERT INTO optionvalue (optionvalueid, uid, code, optionsetid) VALUES (100, 'uid00000002', 'option1', 1);
INSERT INTO optionvalue (optionvalueid, uid, code, optionsetid) VALUES (101, 'uid00000003', 'option2', 1);
INSERT INTO optionvalue (optionvalueid, uid, code, optionsetid) VALUES (102, 'uid00000004', 'option3', 1);
INSERT INTO optionvalue (optionvalueid, uid, code, optionsetid) VALUES (103, 'uid00000005', 'option4', 1);
-- insert questions
INSERT INTO question (questionid, uid, optionsetid) VALUES (1001, 'uid00000006', 1);
INSERT INTO question (questionid, uid, optionsetid) VALUES (1002, 'uid00000007', 1);
INSERT INTO question (questionid, uid, optionsetid) VALUES (1003, 'uid00000008', NULL);
-- insert records
INSERT INTO record (recordid, uid, data) VALUES (10001, 'uid00000009', '{"uid00000006": {"value": "option1"}, "uid00000008": {"value": 42}}'::jsonb);
INSERT INTO record (recordid, uid, data) VALUES (10002, 'uid00000010', '{"uid00000006": {"value": "option2"}}'::jsonb);
INSERT INTO record (recordid, uid, data) VALUES (10003, 'uid00000011', '{"uid00000006": {"value": "UNMAPPED"}}'::jsonb);
My query
My drafted query is:
SELECT r.uid AS record_uid,
key AS question_uid,
os.uid AS optionset_uid,
value ->> 'value' AS value
FROM record r, JSONB_EACH(r.data)
JOIN question q ON q.uid = key
JOIN optionset os ON q.optionsetid = os.optionsetid
WHERE q.optionsetid IS NOT NULL
AND value::varchar NOT IN (SELECT DISTINCT code FROM optionvalue WHERE optionsetid = q.optionsetid)
;
DBFiddle
Problem
The query above returns all records instead only one. In reference to the sample data, the expected result would be to return only the record where the value is UNMAPPED (meaning it is the record where an answer was given that is not "valid").

You should change value::varchar NOT IN to value ->> 'value' NOT IN
SELECT
r.uid AS record_uid,
key AS question_uid,
os.uid AS optionset_uid,
value ->> 'value' AS value
FROM
record r, jsonb_each(r.data)
JOIN question q ON q.uid = key
JOIN optionset os ON q.optionsetid = os.optionsetid
WHERE
q.optionsetid IS NOT NULL
AND value ->> 'value' NOT IN (SELECT DISTINCT code FROM optionvalue WHERE optionsetid = q.optionsetid);

Related

Passing UUID of one table to another table as a Foreign key value in PostgreSQL

I have table Employee in Postgres:
drop table if exists employee;
create table employee (
id uuid default uuid_generate_v4 () primary key,
first_name varchar not null,
last_name varchar not null
);
And another table salary :
drop table if exists salary;
create table salary (
check_id uuid default uuid_generate_v4 () primary key,
salary int not null,
employee_id uuid references employee (id)
);
employee_id is the foreign key to id in the Employee table, but I don't understand how to insert a value inside employee_id since UUID is unique.
I am inserting values into Employee table:
insert into employee (first_name, last_name, email, code) values ( 'jonh', 'smith', 'jonh#example.com', '1');
And then if I try insert values into salary table:
insert into salary (salary ) values ('1000');
Then select command will return employee_id value empty.
But if I make it default uuid_generate_v4 (), then result is: Key (employee_id)=(c4ccd745-02ba-4a0e-8586-32e3c6a2b84a) is not present in table "employee".
I understand that because employee_id is a foreign key it should match with uuid in employee, but since uuid is mostly unique, how can I make it work?
You have to use the uuid that was inserted into the employee table. You can do this with a CTE in a single statement:
WITH new_employee AS (
INSERT INTO employee (first_name, last_name, email, code)
VALUES ('jonh', 'smith', 'jonh#example.com', '1')
RETURNING id
)
INSERT INTO salary (salary, employee_id)
SELECT 1000, id
FROM new_employee;

PostgreSQL not returning records just inserted

I am trying to insert (clone) some records in a table and need to get source ids and ids that got generated. This simplified example demonstrates my issue. After new records are created, referencing their ids in a SELECT produces no results even though records do get created and subsequent SELECT on the table shows them. It feels like insert and select are happening in different transaction scopes.
CREATE TABLE tbl_value(
id int4 NOT NULL GENERATED ALWAYS AS identity PRIMARY KEY,
some_id INTEGER NOT NULL,
value VARCHAR NOT NULL
);
INSERT INTO tbl_value(some_id, value) VALUES(1000, 'value 1'), (1000, 'value 2'), (1000, 'value 3');
with
outer_input as
(
select id, some_id, value from tbl_value where id in (1,2)
),
inner_insert as
(
INSERT INTO tbl_value(some_id, value)
select 2000, value from outer_input
returning id
)
select * from tbl_value v inner join inner_insert i on v.id = i.id;

Using an id returned from an insert in a with statement in postgresql

Say that you have the following table structure, that you like wikipedia have the identity and state of a page stored in different tables:
create table endUsers (
uuid UUID primary key,
created timestamptz default now()
);
create table endUserRevisions (
id bigserial primary key,
endUser UUID not null references endUsers,
modified timestamptz default now(),
modifiedBy UUID not null references portalUsers,
name text not null,
company text not null,
email text not null
);
alter table endUsers add column
latestRevision bigint not null references endUserRevisions;
And that you then want to insert a completely new user into this database like:
with lastID as (
insert into endUserRevisions (endUser, name, company, email)
values ('08e7882c-7596-43d1-b4cc-69f855210d72', 'a', 'b', 'c') returning id)
insert into endUsers (uuid, latestRevision)
values ('08e7882c-7596-43d1-b4cc-69f855210d72', lastID);
-- or
with revision as (
insert into endUserRevisions (endUser, name, company, email)
values ('08e7882c-7596-43d1-b4cc-69f855210d72', 'a', 'b', 'c') returning *)
insert into endUsers (uuid, latestRevision)
values ('08e7882c-7596-43d1-b4cc-69f855210d72', revision.id);
Both these variants fail with either
column "lastid" does not exist
or
missing FROM-clause entry for table "last"
The reason why the fail is because each subquery is accessable to the surrounding context as a table, not as a plain value. In other words it must be accessed using a select statement like:
with revision as (
insert into endUserRevisions (endUser, name, company, email)
values ('08e7882c-7596-43d1-b4cc-79f855210d76', 'a', 'b', 'c') returning id)
insert into endUsers (uuid, latestRevision)
values ('08e7882c-7596-43d1-b4cc-79f855210d76', (select id from revision));
-- or
with revision as (
insert into endUserRevisions (endUser, name, company, email)
values ('08e7882c-7596-43d1-b4cc-79f855210d74', 'a', 'b', 'c') returning id)
insert into endUsers (uuid, latestRevision)
select '08e7882c-7596-43d1-b4cc-79f855210d74', revision.id from revision;

Default ID with Korma and Postgresql?

I have the following schema:
CREATE TABLE IF NOT EXISTS art_pieces
(
-- Art Data
ID SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
price INT NULL,
-- Relations
artists_id INT NULL
);
--;;
CREATE TABLE IF NOT EXISTS artists
(
-- Art Data
ID SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
This is the corresponding art-piece entity:
(defentity art-pieces
(table :art_pieces)
(entity-fields
:id
:title
:description
:price
:artists_id)
(belongs-to artists))
I'm wondering why the following returns PSQLException ERROR: null value in column "id" violates not-null constraint:
(create-piece {:title "The Silence of the Lambda"
:description "Something something java beans and a nice chianti"
:price 5000})
Shouldn't the ID SERIAL PRIMARY KEY field populate automatically? Is this something to do with Korma's interaction with PSQL?
INSERT INTO "art_pieces" ("description", "id", "price", "title") VALUES (?, NULL, ?, ?)
The problem here is that you try to insert NULL value into id column. Default value is inserted only if you omit the column or use DEFAULT keyword (instead of NULL).
To insert the next value of the sequence into the serial column, specify that the serial column should be assigned its default value. This can be done either by excluding the column from the list of columns in the INSERT statement, or through the use of the DEFAULT key word
PostgreSQL Serial Types
So you have to change the query to:
INSERT INTO "art_pieces" ("description", "id", "price", "title") VALUES (?, DEFAULT, ?, ?)
-- or
INSERT INTO "art_pieces" ("description", "price", "title") VALUES (?, ?, ?)
Another workaround (in case you don't have permissions to change the query) would be to add a trigger function that will replace NULL value in id column automatically:
CREATE OR REPLACE FUNCTION tf_art_pieces_bi() RETURNS trigger AS
$BODY$
BEGIN
-- if insert NULL value into "id" column
IF TG_OP = 'INSERT' AND new.id IS NULL THEN
-- set "id" to the next sequence value
new.id = nextval('art_pieces_id_seq');
END IF;
RETURN new;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER art_pieces_bi
BEFORE INSERT
ON art_pieces
FOR EACH ROW EXECUTE PROCEDURE tf_art_pieces_bi();

Generate n rows of NULL in PostgreSQL

I have a table that looks like this:
id, integer, Primary Key, not null
name, character varying
created, timestamp without timezone, not null, default: now()
I want to generate n rows with NULL a name field.
I know that I can do:
INSERT INTO
employee (name)
VALUES
(NULL),
(NULL)...
But I'd prefer to do something like this:
INSERT INTO
employee (name)
SELECT
NULL
FROM
dummy_table_with_n_rows
And I would be able to choose the n.
INSERT INTO
employee (name)
SELECT
NULL
FROM
generate_series(1,10000) i;