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

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

Related

Oracle Apex - REST data source - extract a nested JSON array - trigger two tables - PLSQL error question

This question is a follow up to another SO question.
I've followed Carsten's instructions on the previous question. I am now receiving a new error.
Compilation failed, line 9 (08:51:36) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PL/SQL: ORA-00904: "J"."PRICE": invalid identifierCompilation failed, line 3 (08:51:36) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PL/SQL: SQL Statement ignored
Trigger
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 ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
j.line_number,
j.product_id,
j.quantity,
j.price
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
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.order_id;
elsif updating then
delete ORDER_ITEMS_LOCAL
where order_id = :old.order_id;
--
-- handle the update case here.
-- I would simply delete and re-insert LINES rows.
end if;
end;
What I need help figuring out
The order_id column in ORDER_ITEMS table should be a foreign key to the ORDERS table referring to the order_id. That way each 'order line item' can be traced back to the order_id. Is :new.order_id correct in this case?
insert into ORDER_ITEMS_LOCAL ( order_id, line_id, line_number, product_id, quantity, price)
( select :new.order_id,
seq_line_id.nextval,
The line_id column in ORDER_ITEMS table should be automatically assigned based on the next value (ex: line_id:98, line_id:99, line_id:100) line_id is not in the JSON response. Is seq_line_id.nextval, correct in this case?
I am not sure what the 'j' is referring to. (j.quantity, j.price)
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) );
Does :new.order_items grab the order_items from the JSON array?
'$[*]' columns = $– Start with the current object. [] – Look inside an array
Would I include order_id in here as well?
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
}
]
},
I've found a few resources for the json_table function online but I'm having difficulty finding one that's within a trigger function similar to Carsten's code. Your help explaining this would be much appreciated.
-----------UPDATE---------
ORDERS_LOCAL Table
ORDERS_LOCAL Table data
ORDER_ITEMS_LOCAL Table
LINE_ID column to be automatically created
ORDER_ID column foreign key to ORDERS_LOCAL table
PRODUCT_ID column foreign key to PRODUCTS table
The JSON_TABLE expression in the FROM clause is missing the alias. The select list uses the "j" prefix for the columns from the JSON_TABLE expression, but the JSON_TABLE is not aliased ...
You might change as follows (note the additional "j" in the last line)
from json_table(
:new.order_items,
'$[*]' columns (
line_id for ordinality,
line_number number path '$.line_number',
product_id number path '$.product_id',
quantity number path '$.quantity',
price number path '$.price' ) ) j );

INSERT INTO with subquery and ON CONFLICT

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.

Top N per Classification in CosmosDB

I'm kinda stuck on this issue. I have several hundreds of a certain model stored in ComsosDb and I can't seem to get the top 5 of each category.
This is the model:
"id": "06224840-6b88-4394-9324-4d1628383702",
"name": "Reservation",
"description": null,
"client": null,
"reference": null,
"isMonitoring": false,
"monitoringSince": null,
"hasRiskProfile": false,
"riskProfile": -1,
"monitorFrequency": 0,
"mainBindable": null,
"organizationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"userId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"createDate": "2020-08-18T11:00:02.5266403Z",
"updateDate": "2020-08-18T11:00:02.5266419Z",
"lastMonitorDate": "2020-08-18T11:00:02.5266427Z"
So what i'm trying to do is use C# to get the top 5 from each risk profile where the organizationId matches. GroupBy through LINQ throws an error, same with a row_number() query combined with a PARTITION BY, doesn't seem to work either.
Any way I can get this to work in a single query compatible with cosmos?
EDIT:
What i am trying to achieve in CosmosDb is this roughly:
WITH TopEntries AS (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY [riskProfile]
ORDER BY [updateDate] DESC
) AS [ROW NUMBER]
WHERE [organizationId] = "xyz"
FROM [reservations]
)
SELECT * FROM TopEntries
WHERE TopEntries.[ROW NUMBER] <= 5
It sounds like combining TOP and ORDER BY would do the job. For example:
SELECT TOP 5 *
FROM c
WHERE c.organizationId = "xyz"
ORDER BY c.riskProfile
You can build such queries with parameters in the .NET SDK as in this sample.
The functionality you are trying to achieve is not directly possible through single query in Cosmos DB. There are 2 steps to do this(You can change as per you document sets)
Firstly you will have to group by like below:
SELECT c.city FROM c where c.org = 'xyz' group by c.city
Then loop through the result one by one from the first query like below:
SELECT TOP 5 * FROM C WHERE C.city = 'delhi' order by C.date desc
You can refer to similar issue here:
https://learn.microsoft.com/en-us/answers/questions/38454/index.html

How would I return a count of all rows in the table and then the count of each time a specific status is found?

Please forgive my ignorance on sqlalchemy, up until this point I've been able to navigate the seas just fine. What I'm looking to do is this:
Return a count of how many items are in the table.
Return a count of many times different statuses appear in the table.
I'm currently using sqlalchemy, but even a pure sqlite solution would be beneficial in figuring out what I'm missing.
Here is how my table is configured:
class KbStatus(db.Model):
id = db.Column(db.Integer, primary_key=True)
status = db.Column(db.String, nullable=False)
It's a very basic table but I'm having a hard time getting back the data I'm looking for. I have this working with 2 separate queries, but I have to believe there is a way to do this all in one query.
Here are the separate queries I'm running:
total = len(cls.query.all())
status_count = cls.query.with_entities(KbStatus.status, func.count(KbStatus.id).label("total")).group_by(KbStatus.status).all()
From here I'm converting it to a dict and combining it to make the output look like so:
{
"data": {
"status_count": {
"Assigned": 1,
"In Progress": 1,
"Peer Review": 1,
"Ready to Publish": 1,
"Unassigned": 4
},
"total_requests": 8
}
}
Any help is greatly appreciated.
I don't know about sqlalchemy, but it's possible to generate the results you want in a single query with pure sqlite using the JSON1 extension:
Given the following table and data:
CREATE TABLE data(id INTEGER PRIMARY KEY, status TEXT);
INSERT INTO data(status) VALUES ('Assigned'),('In Progress'),('Peer Review'),('Ready to Publish')
,('Unassigned'),('Unassigned'),('Unassigned'),('Unassigned');
CREATE INDEX data_idx_status ON data(status);
this query
WITH individuals AS (SELECT status, count(status) AS total FROM data GROUP BY status)
SELECT json_object('data'
, json_object('status_count'
, json_group_object(status, total)
, 'total_requests'
, (SELECT sum(total) FROM individuals)))
FROM individuals;
will return one row holding (After running through a JSON pretty printer; the actual string is more compact):
{
"data": {
"status_count": {
"Assigned": 1,
"In Progress": 1,
"Peer Review": 1,
"Ready to Publish": 1,
"Unassigned": 4
},
"total_requests": 8
}
}
If the sqlite instance you're using wasn't built with support for JSON1:
SELECT status, count(status) AS total FROM data GROUP BY status;
will give
status total
-------------------- ----------
Assigned 1
In Progress 1
Peer Review 1
Ready to Publish 1
Unassigned 4
which you can iterate through in python, inserting each row into your dict and adding up all total values in another variable as you go to get the total_requests value at the end. No need for another query just to calculate that number; do it manually. I bet it's really easy to do the same thing with your existing second sqlachemy query.

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, ...)

Resources