INSERT INTO with subquery and ON CONFLICT - sqlite

I want to insert all elements from a JSON array into a table:
INSERT INTO local_config(parameter, value)
SELECT json_extract(j.value, '$.parameter'), json_extract(j.value, '$.value')
FROM json_each(json('[{"parameter": 1, "value": "value1"}, {"parameter": 2, "value": "value2"}]')) AS j
WHERE value LIKE '%'
ON CONFLICT (parameter) DO UPDATE SET value = excluded.value;
This works so far, but do I really need the WHERE value LIKE '%' clause?
When I remove it:
INSERT INTO local_config(parameter, value)
SELECT json_extract(j.value, '$.parameter'), json_extract(j.value, '$.value')
FROM json_each(json('[{"parameter": 1, "value": "value1"}, {"parameter": 2, "value": "value2"}]')) AS j
ON CONFLICT (parameter) DO UPDATE SET value = excluded.value;
I get this error:
[SQLITE_ERROR] SQL error or missing database (near "DO": syntax error)

From SQL As Understood By SQLite/Parsing Ambiguity:
When the INSERT statement to which the UPSERT is attached takes its
values from a SELECT statement, there is a potential parsing
ambiguity. The parser might not be able to tell if the "ON" keyword is
introducing the UPSERT or if it is the ON clause of a join. To work
around this, the SELECT statement should always include a WHERE
clause, even if that WHERE clause is just "WHERE true".
So you need the WHERE clause, but it can be a simple WHERE true or just WHERE 1.

Related

Oracle Apex 22.21 - REST data source - nested JSON array - sync two tables by trigger - PLSQL error question

This question is a follow up to another SO question.
I've actually recreated the tables from the previous question. The updated JSON response can be found at the bottom of this question.
ORDERS_LOCAL table
ORDERS_LOCAL table data. ORDER_ITEMS column is the JSON array that I need to extract into ORDER_ITEMS_LOCAL table.
ORDER_ITEMS_LOCAL table. LINE_ID column should be created automatically. ORDER_ID column is a foreign key to ORDERS_LOCAL table. PRODUCT_ID column is a foreign key to PRODUCTS table. LINE_NUMBER is just the order line number (line 1 = product 1, price, qty | line 2 = product 2, price, qty etc..) I believe it's called a sequence type?
PRODUCTS table
PRODUCTS table data
Per Carsten's answer, I've created a new trigger for the ORDERS table from the Object Browser.
I've then entered Carsten's PLSQL code from the previous question. He did mention that it was pseudo-code. So I tried to update it..
create or replace trigger "TR_MAINTAIN_LINES"
AFTER
insert or update or delete on "ORDERS_LOCAL"
for each row
begin
if inserting then
insert into ORDER_ITEMS_LOCAL ( line_id, order_id, line_number, product_id, quantity, price)
( select :new.id,
seq_lines.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_number for ordinality,
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) );
elsif deleting then
delete ORDER_ITEMS_LOCAL
where order_id = :old.id;
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.id;
--
-- handle the update case here.
-- I would simply delete and re-insert LINES rows.
end if;
end;
I am receiving the following errors
Compilation failed, line 4 (08:38:57) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00049: bad bind variable 'NEW.ID'Compilation failed, line 19 (08:38:57) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00049: bad bind variable 'OLD.ID'Compilation failed, line 22 (08:38:57) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00049: bad bind variable 'OLD.ID'
I believe this is due to missing columns in the trigger code but I'm not sure.
I am new to PLSQL and parsing the JSON is kind of confusing.. especially below. Please see my comments.
if inserting then
insert into ORDER_ITEMS_LOCAL ( line_id, order_id, line_number, product_id, quantity, price)
( select :new.id, -- is this new id for `line_id`
order_id -- how to insert order_id foreign key
seq_lines.nextval, -- not sure what this is for?
j.line_number, -- I changed 'lines' to 'order_items' so should this be seq_order_items.nextval, ?
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items, -- I changed 'lines' to 'order_items' so I changed this from :new.lines,
'$[*]' columns ( -- Would I include 'line_id' and 'order_id' in here as well?
line_number for ordinality,
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) );
Updated JSON response
[
{
"order_id": "HO9b6-ahMY-B2i9",
"order_number": 34795,
"order_date": "2022-11-02",
"store_id": 2,
"full_name": "Ronda Perfitt",
"email": "rperfitt1#microsoft.com",
"city": "Fresno",
"state": "California",
"zip_code": "93762",
"credit_card": "5108758574719798",
"order_items": [
{
"line_number": 1,
"product_id": 2,
"quantity": 1,
"price": 3418.85
},
{
"line_number": 2,
"product_id": 7,
"quantity": 1,
"price": 4070.12
}
]
},
{
"order_id": "RFvUC-sN8Y-icJP",
"order_number": 62835,
"order_date": "2022-10-09",
"store_id": 1,
"full_name": "Wash Rosenfelt",
"email": "wrosenfelt3#wisc.edu",
"city": "Chicago",
"state": "Illinois",
"zip_code": "60646",
"credit_card": "5048372443777103",
"order_items": [
{
"line_number": 1,
"product_id": 1,
"quantity": 1,
"price": 3349.05
},
{
"line_number": 2,
"product_id": 3,
"quantity": 1,
"price": 4241.29
},
{
"line_number": 3,
"product_id": 1,
"quantity": 1,
"price": 3560.03
}
]
},
]
I apologize for making this confusing. I really want to learn how to do this right. Your support is much appreciated. Thank you.
In the trigger code, the :old and :new prefixes reference the row of your table, before and after the trigger operation. So ...
In the UPDATING case, :old.{column-name} references the value of a table column column before the update, :new.{column-name} references the value after the update.
In the INSERTING case, there is no :old.{column-name} (thus that would be NULL); :new.{column-name} references the inserted value.
And in the DELETING case, there is no :new.{column-name} value; only :old.{column-name} is available.
You see the compiler error, as my trigger pseudo-code contained :new.id, but your table does not have a column named ID; it's ORDER_ID in your case. So you need to adjust that code accordingly.
https://docs.oracle.com/en/database/oracle/oracle-database/21/lnpls/plsql-triggers.html#GUID-E76C8044-6942-4573-B7DB-3502FB96CF6F

INSERT OR REPLACE WHERE ROWID Causes Error

The following code:
$stm = $sql->prepare('INSERT OR REPLACE INTO "vehicle_service_invoice" (
invoice, "date", unit, odometer, sublet, sub, po, categories
) VALUES (
:invoice, :date, :unit, :odometer, :sublet, :sub, :po, :categories
) WHERE rowid = :rowid;'
);
$stm->bindParam(':invoice', $_POST['invoice']);
$stm->bindParam(':date', $_POST['date']);
$stm->bindParam(':unit', $_POST['unit']);
$stm->bindParam(':odometer', $_POST['odometer']);
$stm->bindParam(':sublet', $_POST['sublet']);
$stm->bindParam(':sub', $_POST['sub']);
$stm->bindParam(':po', $_POST['po']);
$stm->bindParam(':categories', $categories);
$stm->bindParam(':rowid', $_POST['rowid']);
$stm->execute();
Produces the following query:
INSERT OR REPLACE INTO "vehicle_service_invoice" (
invoice,
"date",
unit,
odometer,
sublet,
sub,
po,
categories
) VALUES (
7230,
'2013-02-07',
558,
34863,
0,
0,
1486347,
5
) WHERE rowid = 1
That produces the following error:
ERROR: near "WHERE": syntax error.
What I am trying to do is make a single path for both my INSERT and UPDATE logic to follow, so after I found out that I could do INSERT OR REPLACE, I figured I could just update the information based on the ROWID of each item. To me the syntax looks correct, what am I doing wrong?
It should be noted that I don't care about changing ROWID values as I understand that is a tripping point on doing INSERT OR REPLACE statements. Everything is joined together in other queries based off of the INVOICE column. I only want to use the ROWID to refer to that row.
In an INSERT statement, a WHERE clause does not make sense.
The INSERT OR REPLACE works as follows: a record with the specified values is inserted into the table.
If this would result in a UNIQUE constraint violation, the old record is deleted.
To replace a record that might already exist, the colum(s) that identify that record must be part of the values to be inserted.
In other words, you must insert the rowid value:
INSERT OR REPLACE INTO vehicle_service_invoice(rowid, ...) VALUES (1, ...)

SQLite Syntax Error

I made a sql query, below. I am getting an exception which I have shown as well. Any help is appreciated.
QUERY:
INSERT OR REPLACE INTO CLAIMS (DATE ,TIME , ADDRESS , CITY,
STATE , POSTAL , PHFNAME ,PHLNAME ,PHEMAIL ,PHPHONE ,AGENCY ,POLICY ,VEHICLENAME,
YEAR ,MAKE ,MODEL ,PLATELICENSE ,LSTATE ,VIN ,DRIVERNAME,DRFNAME ,DRLNAME ,
DRPHONE ,DREMAIL ,DRLICENSE) VALUES("Wednesday, May 4, 2011",
"10:39:10 PM EDT", "400 Chatham","Pune", "Penn", "45223", "John",
"Richard","jsmith#newyahoo.com","+1-11111111111",
"Three Rivers Insurance","(null)", "(null)", "(null)",
"(null)", "(null)","(null)", "(null)", "(null)","(null)",
"(null)", "(null)","(null)", "(null)","(null)") WHERE DATE LIKE
'Wednesday, May 4,%' AND TIME = '10:39:10 PM EDT'
Error:
SQLiteManager: Likely SQL syntax error:
[ near "WHERE": syntax error ]
Exception Name: NS_ERROR_FAILURE
Exception Message: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [mozIStorageConnection.createStatement]
I faced the same issue for sync table for insert and update at a time and finally i use.
To use the insert or replace statement you need to use the coalesce function that required primary key column to put the where clause.
COALESCE((SELECT PRIMARY_KEY_COLUMN FROM TABLE_NAME WHERE UNIQUE_COLUMN = 'VALUE'),
(SELECT MAX(PRIMARY_KEY_COLUMN)+1 FROM TABLE_NAME))
it worked for me e.g
INSERT OR REPLACE INTO TBL_ADDRESSES
(ADDRESS_ID,ADDRESS1,ADDRESS2,ADDRESS3,ADDRESS_NAME,CITY,DB_ID)
VALUES (
COALESCE((SELECT ADDRESS_ID FROM TBL_ADDRESSES WHERE DB_ID = '111'),
(SELECT MAX(ADDRESS_ID)+1 FROM TBL_ADDRESSES)),
'IT 27','Pratap DSFSDSDDSDSF','test add ','IT 27','Jaipur','111') ;
INSERT OR REPLACE doesn't support WHERE clause, see theSQLite Insert doc. You can use a WHERE clause in UPDATE statements, see the SQLite Insert doc
Can help you if you can let us know what you wanted here ?

INSERT IF NOT EXISTS ELSE UPDATE?

I've found a few "would be" solutions for the classic "How do I insert a new record or update one if it already exists" but I cannot get any of them to work in SQLite.
I have a table defined as follows:
CREATE TABLE Book
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level INTEGER,
Seen INTEGER
What I want to do is add a record with a unique Name. If the Name already exists, I want to modify the fields.
Can somebody tell me how to do this please?
Have a look at http://sqlite.org/lang_conflict.html.
You want something like:
insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);
Note that any field not in the insert list will be set to NULL if the row already exists in the table. This is why there's a subselect for the ID column: In the replacement case the statement would set it to NULL and then a fresh ID would be allocated.
This approach can also be used if you want to leave particular field values alone if the row in the replacement case but set the field to NULL in the insert case.
For example, assuming you want to leave Seen alone:
insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
(select ID from Book where Name = "SearchName"),
"SearchName",
5,
6,
(select Seen from Book where Name = "SearchName"));
You should use the INSERT OR IGNORE command followed by an UPDATE command:
In the following example name is a primary key:
INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'
The first command will insert the record. If the record exists, it will ignore the error caused by the conflict with an existing primary key.
The second command will update the record (which now definitely exists)
You need to set a constraint on the table to trigger a "conflict" which you then resolve by doing a replace:
CREATE TABLE data (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);
Then you can issue:
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);
The "SELECT * FROM data" will give you:
2|2|2|3.0
3|1|2|5.0
Note that the data.id is "3" and not "1" because REPLACE does a DELETE and INSERT, not an UPDATE. This also means that you must ensure that you define all necessary columns or you will get unexpected NULL values.
INSERT OR REPLACE will replace the other fields to default value.
sqlite> CREATE TABLE Book (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT,
TypeID INTEGER,
Level INTEGER,
Seen INTEGER
);
sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0
sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');
sqlite> SELECT * FROM Book;
1001|SQLite|||
If you want to preserve the other field
Method 1
sqlite> SELECT * FROM Book;
1001|C++|10|10|0
sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;
sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0
Method 2
Using UPSERT (syntax was added to SQLite with version 3.24.0 (2018-06-04))
INSERT INTO Book (ID, Name)
VALUES (1001, 'SQLite')
ON CONFLICT (ID) DO
UPDATE SET Name=excluded.Name;
The excluded. prefix equal to the value in VALUES ('SQLite').
Firstly update it. If affected row count = 0 then insert it. Its the easiest and suitable for all RDBMS.
Upsert is what you want. UPSERT syntax was added to SQLite with version 3.24.0 (2018-06-04).
CREATE TABLE phonebook2(
name TEXT PRIMARY KEY,
phonenumber TEXT,
validDate DATE
);
INSERT INTO phonebook2(name,phonenumber,validDate)
VALUES('Alice','704-555-1212','2018-05-08')
ON CONFLICT(name) DO UPDATE SET
phonenumber=excluded.phonenumber,
validDate=excluded.validDate
WHERE excluded.validDate>phonebook2.validDate;
Be warned that at this point the actual word "UPSERT" is not part of the upsert syntax.
The correct syntax is
INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...
and if you are doing INSERT INTO SELECT ... your select needs at least WHERE true to solve parser ambiguity about the token ON with the join syntax.
Be warned that INSERT OR REPLACE... will delete the record before inserting a new one if it has to replace, which could be bad if you have foreign key cascades or other delete triggers.
If you have no primary key, You can insert if not exist, then do an update. The table must contain at least one entry before using this.
INSERT INTO Test
(id, name)
SELECT
101 as id,
'Bob' as name
FROM Test
WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;
Update Test SET id='101' WHERE name='Bob';
I believe you want UPSERT.
"INSERT OR REPLACE" without the additional trickery in that answer will reset any fields you don't specify to NULL or other default value. (This behavior of INSERT OR REPLACE is unlike UPDATE; it's exactly like INSERT, because it actually is INSERT; however if what you wanted is UPDATE-if-exists you probably want the UPDATE semantics and will be unpleasantly surprised by the actual result.)
The trickery from the suggested UPSERT implementation is basically to use INSERT OR REPLACE, but specify all fields, using embedded SELECT clauses to retrieve the current value for fields you don't want to change.
I think it's worth pointing out that there can be some unexpected behaviour here if you don't thoroughly understand how PRIMARY KEY and UNIQUE interact.
As an example, if you want to insert a record only if the NAME field isn't currently taken, and if it is, you want a constraint exception to fire to tell you, then INSERT OR REPLACE will not throw and exception and instead will resolve the UNIQUE constraint itself by replacing the conflicting record (the existing record with the same NAME). Gaspard's demonstrates this really well in his answer above.
If you want a constraint exception to fire, you have to use an INSERT statement, and rely on a separate UPDATE command to update the record once you know the name isn't taken.

insert/select with a condition

I'm not sure if this is possible but is there a way to select A unless its null then select B instead?
I am writing a trigger, my code is
insert into tbl_a(userid, obj)
select p.author, new.id
FROM user_comment AS p
WHERE p.id=new.parent
however new.parent is a nullable long (i'll switch to foreign key once supported in system.data.sqlite)
if its null i get 0 results and no insert. I would like to use join media as m on m.id=new.media_id and return m.user_id if new.parent is null. So how do i write the select to return m.author if parent isnt null (which i'll return p.author like the above)
You can use IFNULL(col1, col2). If col1 is not null, it is returned. If col1 is null, col2 is returned.

Resources