In our previous blog, we described some of the features and benefits of Oracle Data Redaction and described some of the use cases for redaction, also known as dynamic masking. For this installment, we will cover some of the improvements introduced with Oracle Database 23ai and provide some examples of how you can take advantage of these new capabilities today.

Improved SQL support

With Oracle Database 23ai, Oracle Data Redaction offers robust new options for protecting sensitive data by masking it as it is returned from a database query. Use of data redaction can help organizations protect data that is displayed on a screen or used in a report and can help them meet data privacy compliance requirements to limit exposure. Some of the enhancements we introduced include support for redacting the following:

  • Data in views: Redact columns in the target list of views, including both CREATE VIEW and inline views.
  • Base columns of virtual columns: Redact a base column in a virtual column.
  • Inline views with SET Operators: Use common table expressions (WITH clause), OUTER JOINs, and aggregate functions such as COUNT and MAX, on columns with Data Redaction policies.
  • Data from queries including DISTINCT, GROUP BY and ORDER BY: Queries that include expressions on redacted columns in both the GROUP BY and SELECT lists are now fully supported.

Examples of redaction in action

Here’s a detailed look at the new features, as well as examples using the Oracle sample schema. If you want to follow along with your own Oracle Database you may install the sample schema, sales history (SH). You can find the latest copy here. Note these examples require Oracle Database 23.6.

First, create a read only user and grant it the appropriate privileges on SH schema objects.

BEGIN
    DBMS_REDACT.ADD_POLICY(
      object_schema => 'SH',
      object_name   => 'CUSTOMERS',
      policy_name   => 'REDACT_SENSITIVE_DATA',
         expression     => 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') != ''SH'''
    );
END;
/

Redacting data in views

Database views are frequently used to simplify data access as a form of data security, data abstraction, data aggregation, or data transformation. Data Redaction now lets you include expressions on redacted columns in the target list of views, including both CREATE VIEW and inline views. For example, let’s say you want to create a simplified view of customer data called CUSTOMER_VIEW. This view will transform first and last names, as well as the material status, into a format your reporting tools will understand.

CREATE OR REPLACE VIEW sh.customer_view AS
 SELECT
  cust_id,
  UPPER(cust_first_name) AS FIRST_NAME,
  UPPER(cust_last_name) AS LAST_NAME,
   CASE
    WHEN cust_marital_status IS NULL OR TRIM(cust_marital_status) = '' THEN 'UNKNOWN'
    ELSE UPPER(cust_marital_status)
   END AS marital_status,
  cust_gender AS GENDER,
  cust_email AS EMAIL,
  cust_postal_code AS POSTAL_CODE,
  cust_credit_limit AS CREDIT_LIMIT
 FROM sh.customers;

Apply the redaction policy to the CUST_MARITAL_STATUS column of the base table:

BEGIN
 DBMS_REDACT.ALTER_POLICY (
   object_schema          => 'SH',
   object_name            => 'CUSTOMERS',
   policy_name            => 'REDACT_SENSITIVE_DATA',
   column_name            => 'CUST_MARITAL_STATUS',
   action                 => DBMS_REDACT.ADD_COLUMN,
   function_type          => DBMS_REDACT.FULL);
END;
/

Next, connect as SH_VIEWER and query the CUSTOMER_VIEW:

SELECT CUST_ID, FIRST_NAME, LAST_NAME, EMAIL, MARITAL_STATUS
 FROM sh.customer_view
 WHERE cust_id IN (101, 103, 176, 201);

When you run the query, you will see the data returned with the MARITAL_STATUS column redacted.

SELECT CUST_ID, FIRST_NAME, LAST_NAME, EMAIL, MARITAL_STATUS
FROM sh.customer_view
WHERE cust_id IN (101, 103, 176, 201);
 
   CUST_ID FIRST_NAME      LAST_NAME       EMAIL                                  MARITAL_STATUS
---------- --------------- --------------- -------------------------------------- --------------
       101 LOU             FITZ            lou.fitz@company2.example.com
       103 AILEEN          NEWKIRK         aileen.newkirk@company2.example.com
       176 CARIN           LIGHTFOOT       carin.lightfoot@company2.example.com
       201 ALYSSA          CLIPP           alyssa.clipp@company2.example.com

The view we you created applies the required transformations to the FIRST_NAME and LAST_NAME columns and modifies the marital status column format while maintaining the redaction policy on the underlying data. The ability to redact data in view allows your users to see the data masked the same way whether they query from the view or from the base table.

Redacting the base columns of virtual columns

Rather than requiring your users to query individual columns, you may want to combine multiple columns into a single virtual column. For example, let’s say you created a virtual column to combine CUST_STREET_ADDRESS, CUST_CITY, CUST_STATE_PROVINCE, and CUST_POSTAL_CODE into FULL_ADDRESS. However, you may not want to display the customer’s postal code in the query results when your users query the virtual column, FULL_ADDRESS. Previously, you would not be able to apply a redaction policy to the CUST_POSTAL_CODE column, however you can with Oracle Database 23ai. Here is an example:

ALTER TABLE sh.customers ADD (full_address AS
(cust_street_address || ', ' || cust_city ||
', ' || cust_state_province || ' ' || cust_postal_code));
BEGIN
    DBMS_REDACT.ALTER_POLICY(
      object_schema => 'SH',
      object_name   => 'CUSTOMERS',
      column_name   => 'CUST_POSTAL_CODE',
      policy_name   => 'REDACT_SENSITIVE_DATA',
      function_type => DBMS_REDACT.FULL
    );
END;
/

In previous releases, you could not apply a redaction policy to a column that is part of a virtual column. You would receive this error message:

ORA-28073: The column "CUST_POSTAL_CODE" has an unsupported data type or attribute.

If you tried to add the virtual column after having a redaction policy, you would see an error like this:
ORA-28083: A redacted column was referenced in a virtual column expression. With Oracle Database 23ai, SH_READER will see a result like this:

SELECT cust_id, cust_last_name, full_address
  FROM sh.customers
 WHERE cust_id IN (101, 103, 176, 201);
   CUST_ID CUST_LAST_NAME FULL_ADDRESS
---------- -------------- ------------
       101 FITZ           
       103 NEWKIRK        
       176 LIGHTFOOT      
       201 CLIPP                             

Note you cannot apply redaction policies to the virtual column itself, but only the base columns.

Now, when you query the REDACTION_COLUMNS view, you will see two columns for 19c and two columns for 23.6.

COLUMN column_name FORMAT a25
COLUMN function_type FORMAT a25
 
SELECT column_name, function_type
  FROM REDACTION_COLUMNS
 WHERE OBJECT_OWNER = 'SH'
   AND OBJECT_NAME = 'CUSTOMERS';
 
COLUMN_NAME          FUNCTION_TYPE
-------------------- -----------------
CUST_MARTIAL_STATUS  FULL REDACTION
CUST_POSTAL_CODE     FULL REDACTION      

Redacting data in inline views with SET operators

Let’s say a line of business wants to use production data to create a report identifying the number of customers above and below the average credit limit. Furthermore, they want to be careful not to expose actual credit limit values in their report. To accomplish this, a data analyst uses a complex query that includes common table expressions (WITH clause), OUTER JOINs, and aggregate functions such as COUNT and MAX. With Oracle 23ai, the DBA can define a Data Redaction policy that will selectively mask these values in the report.

In this example, the data analyst user (SH_READER) should be able to see the CUST_POSTAL_CODE column. So we will drop that column from the Data Redaction policy and add the CUST_CREDIT_LIMIT column as a fully redacted column.

BEGIN
DBMS_REDACT.ALTER_POLICY (
   object_schema          => ‘SH’,
   object_name            => ‘CUSTOMERS’,
   policy_name            => ‘REDACT_SENSITIVE_DATA’,
   column_name            => ‘CUST_POSTAL_CODE’,
   action                 => DBMS_REDACT.DROP_COLUMN);
END;
/

BEGIN
DBMS_REDACT.ALTER_POLICY (
   object_schema          => ‘SH’,
   object_name            => ‘CUSTOMERS’,
   policy_name            => ‘REDACT_SENSITIVE_DATA’,
   column_name            => ‘CUST_CREDIT_LIMIT’,
   action                 => DBMS_REDACT.ADD_COLUMN,
   function_type          => DBMS_REDACT.FULL);
END;
/

The following query will create a table that groups customers by postal code and displays the number of customers above and below the credit limit, while redacting the highest, lowest, or average credit limit data in the output.

COLUMN cust_postal_code HEADING 'Postal|Code'
COLUMN total_customer_count HEADING 'Total|Customer|Count'
COLUMN num_customers_below_avg HEADING 'Num Customers|Below Avg'
COLUMN num_customers_above_avg HEADING 'Num Customers|Above Avg'
COLUMN highest_credit_limit HEADING 'Highest|Credit Limit'
COLUMN lowest_credit_limit HEADING 'Lowest|Credit Limit'
COLUMN average_credit_limit HEADING 'Average|Credit Limit'

WITH avg_credit_limits AS (
    SELECT cust_postal_code, AVG(cust_credit_limit) AS avg_credit_limit
    FROM sh.customers
    WHERE cust_postal_code IN ('55787', '63736', '38082')
    AND cust_credit_limit IS NOT NULL
    GROUP BY cust_postal_code
)
SELECT
    c.cust_postal_code,
    COUNT(*) AS total_customer_count,
    COUNT(CASE WHEN c.cust_credit_limit < acl.avg_credit_limit THEN 1 END) AS num_customers_below_avg,
    COUNT(CASE WHEN c.cust_credit_limit > acl.avg_credit_limit THEN 1 END) AS num_customers_above_avg,
    MAX(c.cust_credit_limit) AS highest_credit_limit,
    ROUND(MIN(c.cust_credit_limit), 0) AS lowest_credit_limit,
    ROUND(AVG(c.cust_credit_limit), 0) AS average_credit_limit
FROM sh.customers c
JOIN avg_credit_limits acl ON c.cust_postal_code = acl.cust_postal_code
WHERE c.cust_postal_code IN ('55787', '63736', '38082')
GROUP BY c.cust_postal_code
ORDER BY c.cust_postal_code;

The schema user (SH) will see the unredacted credit limit data when running the report.

                Total
Postal       Customer Num Customers Num Customers      Highest       Lowest      Average
Code            Count     Below Avg     Above Avg Credit Limit Credit Limit Credit Limit
---------- ---------- ------------- ------------- ------------ ------------ ------------
38082             695           359           336        15000         1500         5947
55787             612           301           311        15000         1500         6223
63736             637           334           303        15000         1500         5830 

 

However, when the data analyst user (SH_READER) runs the query, actual credit limits will be redacted.

                Total
Postal       Customer Num Customers Num Customers      Highest       Lowest      Average
Code            Count     Below Avg     Above Avg Credit Limit Credit Limit Credit Limit
---------- ---------- ------------- ------------- ------------ ------------ ------------
38082             695           359           336            0            0            0
55787             612           301           311            0            0            0
63736             637           334           303            0            0            0

 

This report displays the column averages but not the unredacted credit limit data. 

Note: Data Redaction does not place any restriction on the WHERE clause of ad hoc SQL, so the WHERE clause could be used in an iterative fashion to infer the actual data even when there is a Data Redaction policy on the queried column and only the redacted value is displayed.  But if you have pre-defined analytics reports like above, you can reduce the risk of sensitive data being exposed without rewriting the query or updating the reporting tool.

Redacting queries with DISTINCT, GROUP BY and ORDER BY clauses

Most analytic or advanced queries rely on support for DISTINCT, GROUP BY and ORDER BY clauses. Queries that include expressions on redacted columns in both the GROUP BY and SELECT lists are now fully supported with Oracle Database 23ai. Additionally, queries using SELECT DISTINCT and ORDER BY clauses can now include redacted columns, helping to prevent exposure of sensitive data without the need to re-write the query.

Our final example demonstrates how you can redact sensitive data in these analytic-type queries. This query selects distinct values from a redacted column and orders them, applying redaction policies to the sensitive column data while sorting.

COLUMN cust_postal_code HEADING 'Postal|Code'
COLUMN total_credit_limit HEADING 'Total|Credit'

SELECT cust_postal_code, SUM(cust_credit_limit) AS TOTAL_CREDIT_LIMIT
  FROM sh.customers
GROUP BY cust_postal_code
ORDER BY total_credit_limit DESC
FETCH FIRST 10 ROWS ONLY;   ;
With previous database releases, you would have received an error like the following:
ORA-28094: SQL construct not supported by data redaction
With Oracle 23ai, you can now view the data sorted by the aggregate total of the salaries for each department.
COLUMN cust_postal_code HEADING 'Postal|Code'
COLUMN total_credit_limit HEADING 'Total|Credit'
SELECT cust_postal_code, SUM(cust_credit_limit) AS total_credit_limit
  FROM sh.customers
GROUP BY cust_postal_code
ORDER BY total_credit_limit DESC
FETCH FIRST 10 ROWS ONLY;

The schema user (SH) will see the credit limitdata.

Postal          Total
Code           Credit
---------- ----------
38082         4133000
78558         3904500
55787         3808500
48346         3741000
63736         3713500
45704         3680500
69776         3647500
67843         3606500
72860         3277500
80841         3262500
10 rows selected.

The data analyst user (SH_READER) will not see the credit limit data in the returned query results.

Postal          Total
Code           Credit
---------- ----------
38082               0
78558               0
55787               0
48346               0
63736               0
45704               0
69776               0
67843               0
72860               0
80841               0

10 rows selected.

Restore the SH schema to its original configuration

To clean-up, we will drop the view, redaction policy and virtual column with the following SQL:

DROP VIEW sh.customer_view;
BEGIN
  DBMS_REDACT.DROP_POLICY(
    object_schema => 'SH',
    object_name   => 'CUSTOMERS',
    policy_name   => 'REDACT_SENSITIVE_DATA'
  );
END;
/
ALTER TABLE sh.customers DROP COLUMN full_address;  

Try Oracle Data Redaction Today

These updates to Oracle Data Redaction provide powerful new tools for database administrators and developers, ensuring that sensitive data remains secure and compliant with industry standards. By integrating these features into your data management strategy, you can enhance both the security and functionality of your Oracle databases.

To learn more about Data Redaction, visit the Advanced Security product page on the Oracle web site. Get hands-on experience with Oracle Data Redaction by exploring one of our free Redaction LiveLabs. Visit Oracle LiveLabs and search for “redaction” to access interactive labs that showcase redaction capabilities in action.