Copy data from integer field to jsonb field - sql

I have the following schema
CREATE TABLE survey_results (
id integer NOT NULL,
raw jsonb DEFAULT '{}'::jsonb,
survey_id integer NOT NULL,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
INSERT INTO survey_results (id, survey_id, raw, created_at, updated_at)
VALUES (1, 10, '{"survey": {}}', '2018-01-10', '2018-01-11');
INSERT INTO survey_results (id, survey_id, raw, created_at, updated_at)
VALUES (2, 20, '{"survey": {}}', '2018-01-12', '2018-01-12');
I want to copy survey_id value to raw->survey->survey_id key. I've tried to do that in the following way but that didn't work:
UPDATE survey_results SET raw#>>'{survey, survey_id}' = survey_id;
Is there way of doing that in PostgreSQL?
http://sqlfiddle.com/#!17/ed50f/1

You need to use jsonb_set()
update survey_results
set raw = jsonb_set(raw, '{survey, survey_id}', to_jsonb(survey_id), true);
Online example: http://rextester.com/GTBTY13915

Related

Select date in oracle db

I set or insert time in a postgres table value with now()::timestamp
But it does not work in oracle db.
This is the table:
create table types
(
id number not null,
description varchar(255) not null,
created_at timestamp null,
updated_at timestamp null,
deleted_at timestamp null,
CONSTRAINT autenticacion_id PRIMARY KEY (id)
);
So to insert the data I make:
insert into types (id, description) values (1, 'hello world', now()::timestamp)
But I get:
ORA-00917: missing comma
insert into types (id, description) values (1, 'hello world', (select sysdate from dual))
I get:
ORA-00942: table or view does not exist
You are providing 3 values and have only given 2 columns that you want to insert into.
SYSDATE is a DATE data type and SYSTIMESTAMP is a TIMESTAMP data type; they both have a date and time component but SYSTIMESTAMP also has fractional seconds (and a time zone, but that will be ignored as the column you are inserting into is a TIMESTAMP and not a TIMESTAMP WITH TIME ZONE data type).
What you probably want is:
INSERT INTO types (id, description, created_at)
VALUES (1, 'hello world', SYSTIMESTAMP)
However, you may want to consider that the timestamp should be in a specific time zone (typically UTC):
INSERT INTO types (id, description, created_at)
VALUES (1, 'hello world', SYSTIMESTAMP AT TIME ZONE 'UTC')
Note: You can wrap a value in (SELECT value FROM DUAL) but it is not necessary.
fiddle

Update multiple tables in trigger where one of the tables is used for trigger activation

Let's say I have two tables called widgetCustomer and widgetSale. On an insert in widgetSale I want to add a timestamp to the widgetSale row and add the sale id as last_order_id to the widgetCustomer table.
I understand using AFTER INSERT ON will result in an error on trying to update the NEW row, hence we need to use BEFORE INSERT ON clause. Which brings forward a new issue that AUTO_INCREMENT has not yet generated a id for sale hence last_order_id would all be zero. There is a method to do this at MySQL/MariaDB TRIGGER but it seems to fail on my system (i.e., the last order ids are still zero).
As a work around I'm using two different triggers one before insert and one after insert. Although it does work I'm keen to learn if there is a possible flaws with the method above and is there a better way of doing this (both in terms of performance and data integrity).
My code is given below:
DROP TABLE IF EXISTS widgetSale;
DROP TABLE IF EXISTS widgetCustomer;
DROP TABLE IF EXISTS widgetLog;
CREATE TABLE widgetCustomer ( id integer primary key AUTO_INCREMENT, name TEXT, last_order_id INT, stamp TEXT );
CREATE TABLE widgetSale ( id integer primary key AUTO_INCREMENT, item_id INT, customer_id INTEGER, quan INT, price INT, stamp TEXT );
CREATE TABLE widgetLog ( id integer primary key AUTO_INCREMENT, stamp TEXT, event TEXT, username TEXT, tablename TEXT, table_id INT);
INSERT INTO widgetCustomer (name) VALUES ('Bob');
INSERT INTO widgetCustomer (name) VALUES ('Sally');
INSERT INTO widgetCustomer (name) VALUES ('Fred');
SELECT * FROM widgetCustomer;
CREATE TRIGGER stampSale BEFORE INSERT ON widgetSale
FOR EACH ROW BEGIN
SET NEW.stamp = CURRENT_TIMESTAMP();
END
CREATE TRIGGER stampOnRest AFTER INSERT ON widgetSale
FOR EACH ROW BEGIN
UPDATE widgetCustomer SET last_order_id = NEW.id, stamp = CURRENT_TIMESTAMP()
WHERE widgetCustomer.id = NEW.customer_id;
INSERT INTO widgetLog (stamp, event, username, tablename, table_id)
VALUES (CURRENT_TIMESTAMP(), 'INSERT', 'TRIGGER', 'widgetSale', NEW.id);
END
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (1, 3, 5, 1995);
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (2, 2, 3, 1495);
INSERT INTO widgetSale (item_id, customer_id, quan, price) VALUES (3, 1, 1, 2995);
SELECT * FROM widgetSale;
SELECT * FROM widgetCustomer;
SELECT * FROM widgetLog;
I'm using mariadb 10.6.* on Archlinux.

How to create an "on-the-fly" mapping table within a SELECT statement in Postgresql

I'm creating a select statement that combines two tables, zone and output,
based on a referenced device table and on a mapping of zone_number to output_type_id.
The mapping of zone_number to output_type_id doesn't appear
anywhere in the database, and I would like to create it "on-the-fly" within the select
statement. Below is my schema:
CREATE TABLE output_type (
id INTEGER NOT NULL,
name TEXT,
PRIMARY KEY (id)
);
CREATE TABLE device (
id INTEGER NOT NULL,
name TEXT,
PRIMARY KEY (id)
);
CREATE TABLE zone (
id SERIAL NOT NULL,
device_id INTEGER NOT NULL REFERENCES device(id),
zone_number INTEGER NOT NULL,
PRIMARY KEY (id),
UNIQUE (zone_number)
);
CREATE TABLE output (
id SERIAL NOT NULL,
device_id INTEGER NOT NULL REFERENCES device(id),
output_type_id INTEGER NOT NULL REFERENCES output_type(id),
enabled BOOLEAN NOT NULL,
PRIMARY KEY (id)
);
And here is some example data:
INSERT INTO output_type (id, name) VALUES
(101, 'Output 1'),
(202, 'Output 2'),
(303, 'Output 3'),
(404, 'Output 4');
INSERT INTO device (id, name) VALUES
(1, 'Test Device');
INSERT INTO zone (device_id, zone_number) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4);
INSERT INTO output (device_id, output_type_id, enabled) VALUES
(1, 101, TRUE),
(1, 202, FALSE),
(1, 303, FALSE),
(1, 404, TRUE);
I need to get the associated enabled field from the output table for each zone for a given device.
Each zone_number maps to an output_type_id. For this example:
zone_number | output_type_id
----------------------------
1 | 101
2 | 202
3 | 303
4 | 404
One way to handle the mapping would be to create a new table
CREATE TABLE zone_output_type_map (
zone_number INTEGER,
output_type_id INTEGER NOT NULL REFERENCES output_type(id)
);
INSERT INTO zone_output_type_map (zone_number, output_type_id) VALUES
(1, 101),
(2, 202),
(3, 303),
(4, 404);
And use the following SQL to get all zones, plus the enabled flag, for device 1:
SELECT zone.*, output.enabled
FROM zone
JOIN output
ON output.device_id = zone.device_id
JOIN zone_output_type_map map
ON map.zone_number = zone.zone_number
AND map.output_type_id = output.output_type_id
AND zone.device_id = 1
However, I'm looking for a way to create the mapping of zone nunbers to output
types without creating a new table and without piecing together a bunch of AND/OR
statements. Is there an elegant way to create a mapping between the two fields
within the select statement? Something like:
SELECT zone.*, output.enabled
FROM zone
JOIN output
ON output.device_id = zone.device_id
JOIN (
SELECT (
1 => 101,
2 => 202,
3 => 303,
4 => 404
) (zone_number, output_type_id)
) as map
ON map.zone_number = zone.zone_number
AND map.output_type_id = output.output_type_id
AND zone.device_id = 1
Disclaimer: I know that ideally the enabled field would exist in the zone
table. However, I don't have control over that piece. I'm just looking for the
most elegant solution from the application side. Thanks!
You can use VALUES as an inline table and JOIN to it, you just need to give it an alias and column names:
join (values (1, 101), (2, 202), (3, 303), (4, 304)) as map(zone_number, output_type_id)
on ...
From the fine manual:
VALUES can also be used where a sub-SELECT might be written, for
example in a FROM clause:
SELECT f.*
FROM films f, (VALUES('MGM', 'Horror'), ('UA', 'Sci-Fi')) AS t (studio, kind)
WHERE f.studio = t.studio AND f.kind = t.kind;
UPDATE employees SET salary = salary * v.increase
FROM (VALUES(1, 200000, 1.2), (2, 400000, 1.4)) AS v (depno, target, increase)
WHERE employees.depno = v.depno AND employees.sales >= v.target;
So just to complement the accepted answer, the following code is a valid, self-contained Postgresql expression which will evaluate to an 'inline' relation with columns (zone_number, output_type_id):
SELECT * FROM
(VALUES
(1, 101),
(2, 202),
(3, 303),
(4, 304)
) as i(zone_number, output_type_id)
(The (VALUES ... AS ...) part alone will not make a valid expression, which is why I added the SELECT * FROM.)
JOIN
(SELECT 1 zone_number, 101 as output_type_id
UNION ALL
SELECT 2 zone_number, 202 as output_type_id
UNION ALL
SELECT 3 zone_number, 303 as output_type_id
) mappings on mappings.zone_number = zone.zone_number

Need Help Writing SQL Query in Postgresql

I've been trying to write this query but can't seem to get it right for some reason or another..
What I need to do is:
Change the status of a question to 'closed' if there has not been an update associated with this question inserted into the qUpdateTable in the last 24 hours.
I only want it to be closed if a staff member has replied to it at least once.
You can determine if a staff member or a user has replied to the question by checking the qUpdateTable and seeing if a the StaffID field is empty or has a value for that particular tickets updates. If there is a staffID then it has been updated by a staff member, however if it does not then the qUpdate was done by a user.
Essentialy the way this works is a user posts a question by inserting into the Question table, and replies are made by inserting into the qUpdate table and linked to the original question using the foreign key - "QuestionID".
The tables:
CREATE TABLE Staff
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Customer
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL,
Email VARCHAR(40) NOT NULL
);
CREATE TABLE Product
(
ID INTEGER NOT NULL PRIMARY KEY,
Name TEXT NOT NULL
);
CREATE TABLE Question
(
ID INTEGER NOT NULL PRIMARY KEY,
Problem VARCHAR(1000),
Status VARCHAR(20) NOT NULL DEFAULT 'open',
Priority INTEGER NOT NULL,
LoggedTime TIMESTAMP NOT NULL,
CustomerID INTEGER NOT NULL,
ProductID INTEGER NOT NULL,
FOREIGN KEY (ProductID) REFERENCES Product(ID),
FOREIGN KEY (CustomerID) REFERENCES Customer(ID),
CHECK (Status IN ('open','closed') AND Priority IN (1,2,3))
);
CREATE TABLE qUpdate
(
ID INTEGER NOT NULL PRIMARY KEY,
Message VARCHAR(1000) NOT NULL,
UpdateTime TIMESTAMP NOT NULL,
QuestionID INTEGER NOT NULL,
StaffID INTEGER,
FOREIGN KEY (StaffID) REFERENCES Staff(ID),
FOREIGN KEY (QuestionID) REFERENCES Question(ID)
);
Some sample inserts:
INSERT INTO Customer (ID, Name, Email) VALUES (1, 'testname1', 'testemail1');
INSERT INTO Customer (ID, Name, Email) VALUES (2, 'testname2', 'testemail2');
INSERT INTO Staff (ID, Name) VALUES (1, 'Don Keigh');
INSERT INTO Product (ID, Name) VALUES (1, 'Xbox');
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (1, 'testproblem1', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (2, 'testproblem2', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, StaffID, QuestionID) VALUES (2, 'testmessage1','2012-07-12 14:27', 1, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, QuestionID) VALUES (3, 'testmessage1','2012-06-18 19:42', 2);
What I've done so far (which obviously doesn't work)
UPDATE Question
SET Status = 'closed'
WHERE EXISTS
(SELECT qUpdate.QuestionID
MAX(qUpdate.UpdateTime - Now() = INTERVAL '1 day') FROM qUpdate
LEFT JOIN Question ON qUpdate.QuestionID = Question.ID
WHERE qUpdate.StaffID IS NOT NULL);
I realise my explanation may be a bit confusing so if you need more info post and I'll reply ASAP
UPDATE Question
SET Status = 'closed'
where
-- this clause asserts there's at least one staff answer
exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and StaffID is not null
)
-- this clause asserts there's been no update in the last 24 hours
and not exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and qUpdate.UpdateTime > (now() - interval '24 hours')
)
and Status = 'open';
You'll almost certainly want an index on qUpdate(QuestionID) or possibly qUpdate(QuestionID,UpdateTime) or qUpdate(QuestionID,StaffID) to get good performance on the subselects in the exists() tests.

SQL query: Last but one rank for user

My table structure looks like this:
create table rankings (
id IDENTITY NOT NULL,
user_id INT NOT NULL,
game_poule_id INT NOT NULL,
rank INT NOT NULL,
insertDate DATETIME NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (game_poule_id) REFERENCES game_poules(id) ON DELETE CASCADE
);
All old rankings of users per game are saved in this table. Now I want to have the last but one rank in the table for all users in a gamepoule.
Has someone an idea how to achive this? Thanks
You need to self join the table to get the records you require.
For this answer I created your table without the foreign keys as they are not required to get it to work.
CREATE TABLE Rankings (
id int IDENTITY NOT NULL,
user_id INT NOT NULL,
game_poule_id INT NOT NULL,
rank INT NOT NULL,
insertDate DATETIME NOT NULL
);
Insert some sample data. Without more information I cannot simulate any better than this.
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(1, 100, 3, CURRENT_TIMESTAMP-2)
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(1, 100, 2, CURRENT_TIMESTAMP-1)
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(1, 101, 6, CURRENT_TIMESTAMP)
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(2, 100, 5, CURRENT_TIMESTAMP-2)
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(2, 100, 1, CURRENT_TIMESTAMP-1)
INSERT Rankings(user_id,game_poule_id,rank,insertDate)
VALUES(2, 101, 2, CURRENT_TIMESTAMP)
Query for the last but one rank
SELECT Rankings.game_poule_id, Rankings.user_id, rank, MAX(Rankings.insertDate)
FROM Rankings INNER JOIN
(SELECT game_poule_id, user_id, MAX(insertDate) max_insert_date
FROM rankings
GROUP BY game_poule_id, user_id) Max_Ranking_Date
ON Rankings.user_id = Max_Ranking_Date.user_id
AND Rankings.insertDate < Max_Ranking_Date.max_insert_date
AND Rankings.game_poule_id = Max_Ranking_Date.game_poule_id
GROUP BY Rankings.game_poule_id, Rankings.user_id, rank
PLEASE NOTE!
As you can see from the results you will not get a ranking for a game that only has one row per user. But since you are asking for the "last but one" that only makes sense for games with multiple entries.
EDIT:
I've just realised the query I have provided will not return one row per user per game. Anyone want to fix it? I have to get on with some work :)
Another possible (not very nice) solution
SELECT
*
FROM
rankings r
WHERE
FK_gamePoule = 0 AND
r.insertDate = COALESCE(
(SELECT
r2.insertDate
FROM
rankings r2
WHERE
r.FK_user = r2.FK_user ORDER BY r2.insertDate DESC
LIMIT 1
OFFSET 1), '2048-12-31 23:59:59')