As you stated, you either have to create FKs with an ON DELETE CASCADE
clause or to pre-delete the other rows with a subselect-delete.
So, if you don't have an ON DELETE CASCADE
clause you have to do
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled'
);
DELETE FROM ORDERS WHERE STATUS = 'Canceled';
It is simple, but is somewhat redundant so you may use the WITH statement.
If the request to select the required rows is quite big, and if you don't have at least a RR isolation level, you may have to use a TEMPORARY table :
DECLARE GLOBAL TEMPORARY TABLE TMP_IDS_TO_DELETE (ID BIGINT) NOT LOGGED;
INSERT INTO SESSION.TMP_IDS_TO_DELETE (ID)
SELECT ORDER_ID FROM ORDERS WHERE STATUS = 'Canceled';
DELETE FROM ORDERS_ITEMS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS_ADDRESS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
DELETE FROM ORDERS WHERE ORDER_ID in (
SELECT ID FROM SESSION.TMP_IDS_TO_DELETE
);
This way you are sure that you will delete the same rows in each table, and a FK error will still fire up if you miss something. Per default, the temporary table will empty itself on commit.