※ 本記事は、Martin Bachによる”Working with JSON in Multilingual Engine/JavaScript in Oracle Database 23c Free“を翻訳したものです。
2023年12月20日
JSONは、主要なデータ交換形式ではないとしても、最も重要なデータの格納方法の1つとなっています。この記事では、MLE/JavaScriptを使用すると、JSONの操作がかなり簡単であることを示しています。結局のところ、JSONはJavaScript Object Notationの略ですので、これは必ずしも驚くべきことではありません …
背景
この例では、リレーショナル・コンテキストでJSONを使用する方法を確認できます。つまり、JSONは、Oracle Database 21cで最初に導入されたJSONデータ型を使用してリレーショナル表に格納されます。フォローする場合は、『Oracle Database JSON開発者ガイド』の第4章に進んでください。
この例は、Linux x86-64上のOracle Database 23c Freeで作成およびテストされています。
ここでは、この記事で多くを見ることになる発注の例を示します:
{
"PONumber": 1600,
"Reference": "ABULL-20140421",
"Requestor": "Alexis Bull",
"User": "ABULL",
"CostCenter": "A50",
"ShippingInstructions": {
"name": "Alexis Bull",
"Address": {
"street": "200 Sporting Green",
"city": "South San Francisco",
"state": "CA",
"zipCode": 99236,
"country": "United States of America"
},
"Phone": [
{
"type": "Office",
"number": "909-555-7307"
},
{
"type": "Mobile",
"number": "415-555-1234"
}
]
},
"Special Instructions": null,
"AllowPartialShipment": true,
"LineItems": [
{
"ItemNumber": 1,
"Part": {
"Description": "One Magic Christmas",
"UnitPrice": 19.95,
"UPCCode": 13131092899
},
"Quantity": 9.0
},
{
"ItemNumber": 2,
"Part": {
"Description": "Lethal Weapon",
"UnitPrice": 19.95,
"UPCCode": 85391628927
},
"Quantity": 5.0
}
]
}
データベース内JavaScriptとJSONが一致
この仮説的なビジネスによってプロモーションが実行されると仮定します。クリスマスに近づくにつれて、一定額以上の支出をしている顧客はフットバッグを手に入れることになります。フットバッグは楽しい小さなプレゼントであり、モビリティと調整を奨励します。
次に、無料アイテムをオーダーに追加する方法の例を示します。購買オーダーがドキュメント(前述の例)で示されているように定義されている場合、次のコードがあります:
create or replace mle module purchase_order_module
language javascript as
/**
* A simple function accepting a purchase Order (as per chapter 4 in the Oracle Database
* JSON Developer's Guide) and checking whether its value is high enough to merit the
* addition of a free item
*
* @param {object} po the purchase order to be checked
* @param {object} freeItem which item to add to the order free of charge
* @param {number} threshold the minimum order value before a free item can be added
* @param {boolean} a flag indicating whether the free item was successfully added
* @returns {object} the potentially updated purchaseOrder
* @throws exceptions in case
* - any of the mandatory parameters is null
* - in the absence of line items
* - if the free item has already been added to the order
*/
export function addFreeItem(po, freeItem, threshold, itemAdded) {
// sanity checking
if (po == null || freeItem == null || threshold == null) {
throw new Error(`mandatory parameter either not provided or null`);
}
// ensure there are line items provided by the purchase order
if (po.LineItems === undefined) {
throw new Error(`PO number ${po.PONumber} does not contain any line items`);
}
// bail out if the free Item has already been added to the purchase order
if (po.LineItems.find(({ Part }) => Part.Description === freeItem.Part.Description)) {
throw new Error(`${freeItem.Part.Description} has already been added to order ${po.PONumber}`);
}
// In, Out, and InOut Parameters are implemented using special interfaces, see section
// 5.2 in the MLE JavaScript Developers Guide
itemAdded.value = false;
// get the total order value
const poValue = po.LineItems
.map(x => x.Part.UnitPrice * x.Quantity)
.reduce(
(accumulator, currentValue) => accumulator + currentValue, 0
);
// add a free item to the purchase order if its value
// exceeds the threshold
if ( poValue > threshold ) {
// update the ItemNumber
freeItem.ItemNumber = (po.LineItems.length + 1)
po.LineItems.push(freeItem);
itemAdded.value = true;
}
return po;
}
/
SQLまたはPL/SQL(あるいはその両方)でaddFreeItem()を使用する場合は、コール仕様を作成する必要があります。PL/SQLパッケージを使用したコール仕様の例を次に示します:
create or replace package purchase_order_pkg as
function add_free_item(
p_po IN JSON,
p_free_item IN JSON,
p_threshold IN NUMBER,
p_item_added OUT BOOLEAN
)
return json as
mle module purchase_order_module
signature 'addFreeItem(any, any, number, Out)';
-- additional code
end purchase_order_pkg;
/
あなたの製品に300以上を費やしている各顧客に、素敵で楽しいフットバッグを提供することを決定したと仮定すると、これはそれを行うための1つの方法です:
-- let's list the number of line items per purchase order
select
p.id,
p.po_document.LineItems.size() as numItemsBefore
from
j_purchaseorder p;
次の出力が表示されます:
SQL> select
2 p.id,
3 p.po_document.LineItems.size() as numItemsBefore
4 from
5* j_purchaseorder p;
ID NUMITEMSBEFORE
___________________________________ _________________
09BA7F7EB40F034DE063020011ACD4A2 2
09BA7F7EB410034DE063020011ACD4A2 3
各発注の適格性を確認する前に、次の点に注意してください:
set serveroutput on long 100000 pages 100 lines 120 longchunk 120
declare
l_free_item JSON;
l_updated_po JSON;
l_item_added BOOLEAN;
-- stub to simulate error handling ... this isn't the way to do it
-- but should act as a good-enough approximation for this article
procedure log_error(
p_id j_purchaseorder.id%type,
p_error varchar2)
as
begin
dbms_output.put_line('an error occurred processing ID ' || p_id);
dbms_output.put_line(p_error);
end;
begin
-- this would have typically been selected from the database,
-- not in this example though, to keep it simple
-- note how ItemNumber is initialised to -1, it's updated by
-- add_free_item()
l_free_item := JSON('{
"ItemNumber": -1,
"Part":
{
"Description": "Super Fun Footbag",
"UPCCode": 1234567890,
"UnitPrice": 0.00
},
"Quantity": 1
}');
-- iterate over all purchase orders
for po in (select id, po_document from j_purchaseorder) loop
begin
l_item_added := false;
-- add a free item for purchases > 300
l_updated_po := purchase_order_pkg.add_free_item(
p_po => po.po_document,
p_free_item => l_free_item,
p_threshold => 300,
p_item_added => l_item_added
);
-- persist the updated order in the database, skip the operation
-- if the original PO has not been updated
if ( l_item_added ) then
update j_purchaseorder
set
po_document = l_updated_po
where
id = po.id;
end if;
exception when others then
log_error( po.id, sqlerrm );
end;
end loop;
end;
/
幸運な人がいるかどうか見てみましょう:
select
p.id,
p.po_document.LineItems.size() as numItemsafter
from
j_purchaseorder p;
ID 09BA7F7EB410034DE063020011ACD4A2のライン・アイテムの数が1増えました:
SQL> select
2 p.id,
3 p.po_document.LineItems.size() as numItemsafter
4 from
5* j_purchaseorder p;
ID NUMITEMSAFTER
___________________________________ ________________
09BA7F7EB40F034DE063020011ACD4A2 2
09BA7F7EB410034DE063020011ACD4A2 4
幸せなお客様がいらっしゃるようです:
select
json_serialize(
p.po_document.LineItems[3]
pretty
) as freebee
from
j_purchaseorder p
where
id = '09BA7F7EB410034DE063020011ACD4A2';
FREEBEE
_____________________________________________________
{
"ItemNumber" : 4,
"Part" :
{
"Description" : "Super Fun Footbag",
"UPCCode" : 1234567890,
"UnitPrice" : 0
},
"Quantity" : 1
}
同じPL/SQLブロックをもう一度実行しても、フットバッグは2回追加されません。JavaScriptコードでは、それらのうち1つのみが許可されます。
SQL> l
1 declare
2 l_free_item JSON;
3 l_updated_po JSON;
4 l_item_added BOOLEAN;
5
6 -- stub to simulate error handling ... this isn't the way to do it
7 -- but should act as a good-enough approximation for this article
8 procedure log_error(
9 p_id j_purchaseorder.id%type,
10 p_error varchar2)
11 as
12 begin
13 dbms_output.put_line('an error occurred processing ID ' || p_id);
14 dbms_output.put_line(p_error);
15 end;
16 begin
17 -- this would have typically been selected from the database,
18 -- not in this example though, to keep it simple
19 -- note how ItemNumber is initialised to -1, it's updated by
20 -- add_free_item()
21 l_free_item := JSON('{
22 "ItemNumber": -1,
23 "Part":
24 {
25 "Description": "Super Fun Footbag",
26 "UPCCode": 1234567890,
27 "UnitPrice": 0.00
28 },
29 "Quantity": 1
30 }');
31
32 -- iterate over all purchase orders
33 for po in (select id, po_document from j_purchaseorder) loop
34
35 begin
36 l_item_added := false;
37
38 -- add a free item for purchases > 300
39 l_updated_po := purchase_order_pkg.add_free_item(
40 p_po => po.po_document,
41 p_free_item => l_free_item,
42 p_threshold => 300,
43 p_item_added => l_item_added
44 );
45
46 -- persist the updated order in the database, skip the operation
47 -- if the original PO has not been updated
48 if ( l_item_added ) then
49
50 update j_purchaseorder
51 set
52 po_document = l_updated_po
53 where
54 id = po.id;
55 end if;
56 exception when others then
57 log_error( po.id, sqlerrm );
58 end;
59 end loop;
60* end;
SQL> /
an error occurred processing ID 09BA7F7EB410034DE063020011ACD4A2
ORA-04161: Error: Super Fun Footbag has already been added to order 672
ORA-04171: at addFreeItem (MARTIN.PURCHASE_ORDER_MODULE:30:15)
PL/SQL procedure successfully completed.
まとめ
この記事の目的は、JSONの操作に関して、データベース内JavaScriptの使いやすさを示すことでした。
