In Part 1, I demonstrated how we can configure Claude Code (Claude) to talk to the database through SQLcl with a password stored in the wallet and a .mcp.json scoped to the project. For Part 2, let's dive deeper into MCP capabilities by building out a consultant utilization report built entirely inside Claude, with the SQLcl MCP server as the only thing communicating with the database. We will have Claude perform the ETL/ELT functions by filtering out data from a spreadsheet, changing consultant names and changing company names to ensure confidentiality. Then Claude will leverage the MCP server to create the table, load the data into the table, perform queries to calculate utilization and provide a comprehensive dashboard of the consultant's utilization.
If you want to follow along, grab the sample CSV here: fictious-time.csv. It is the same 1,084-row dataset used throughout this post.
Background Information
The dataset is a real timesheet export from our consulting practice: consultant, client, project, task, hours, billable flag. Claude's first task for this post wasn't running a query. It was preparing and cleansing the data.
↳Swapping Real Names for Fictitious Ones with Claude Code
I started with the time.csv timesheet export and a lookup file called cons.txt with real name on the left, fictitious name on the right which was pipe delimited:
Real Name A|Berry Kline
Real Name B|Mark Pencil
Real Name C|Admin QuincyI picked plain text because it is easy to audit, diff, and regenerate. With both files in the working directory, the prompt to Claude was:
❯ The cons.txt file has 2 columns. The first column is the actual name.
It is delimited by a "|". The second column is the fictious name.
Create a new file called fictious-name.csv. Take the time.csv and
filter out only the names from the first column. Then write out each
row but write it out with the fictious name from the second column.That single instruction did two jobs in one pass. Consultants not listed in cons.txt were dropped entirely, while listed ones had their names swapped to the fictitious alias. Every other field passed through untouched, and the result was written to fictious-name.csv.
↳Swapping Real Clients for Tech Giants with Claude
Client names are even more sensitive than consultant names, and utilization analysis needs consistent labels across the whole period. This meant that editing the file manually was not an option.
❯ From the fictious.csv, the 3rd column is the Client name. Find the
top 25 tech companies and replace each of the company names with
the tech company names. You must do a global search and replace
for each company as I will be doing a utilization report and need
to be consistent.Claude counted thirty distinct client names in the file and pulled thirty tech company names for the substitution. The result is a CSV that looks like the original but carries none of its sensitive data.
Date,Employee,Client Name,Project,Task,Hours,Supplier,Billable / Not Billable
1-Oct-25,Berry Kline,Samsung,Samsung Monitoring Support Services,Managed Services,0.5,Snowflake,Billable
1-Oct-25,Mark Pencil,Broadcom,Data Engineering-50171304,DOT SUPPORT,8,Snowflake,Billable
1-Oct-25,Admin Quincy,Broadcom,Data Engineering-50171304,APEX SUPPORT,12,Snowflake,Billable
1-Oct-25,Sam Carson,Qualcomm,Managed Services,Month End,0.5,Snowflake,BillableThe whole redaction pipeline ran inside Claude Code without a single line of code that I wrote myself. I described the work in plain English and Claude Code produced the files.
Loading the CSV Into Oracle AI Database 26ai
Next, let's prepare our data set to be loaded into an Oracle table. Look at the prompt I gave Claude Code.
❯ Use the oracle-selectai-wallet. Create a table that maps columns to
the fictious-time.csv file. Load the data from the csv to the table
that you will create.My single prompt told MCP to do three jobs: infer a schema, create the table, and load 1,084 rows. Take note that the prompt didn't provide a table name or column names. I could have been extra explicit with the context but chose to let the AI engine make the choices.
↳Creating the Table with Claude Code
Claude Code read the CSV header for names and sampled the first rows for types: Hours numeric with decimals, Date in the DD-MON-RR format Oracle parses natively, free text columns well under 120 chars. It returned the following DDL.
CREATE TABLE CONSULTANT_TIME (
ENTRY_DATE DATE,
EMPLOYEE VARCHAR2(60),
CLIENT_NAME VARCHAR2(60),
PROJECT VARCHAR2(120),
TASK VARCHAR2(80),
HOURS NUMBER(5,2),
SUPPLIER VARCHAR2(40),
BILLABLE VARCHAR2(12)
);BILLABLE kept the literal source text (Billable / Not Billable) so the table mirrors the CSV. Claude sent the statement through run-sql and Oracle answered in one line.
Table CONSULTANT_TIME created.A quick describe command below confirmed the table structure:
❯ describe consultant_time tableName Null? Type
------------ ----- -------------
ENTRY_DATE DATE
EMPLOYEE VARCHAR2(60)
CLIENT_NAME VARCHAR2(60)
PROJECT VARCHAR2(120)
TASK VARCHAR2(80)
HOURS NUMBER(5,2)
SUPPLIER VARCHAR2(40)
BILLABLE VARCHAR2(12)A single prompt produced one round trip to the database and created a new table which completed the first third of the original ask.
First Attempt: SQLcl load
The instinctive next command was SQLcl's single line bulk loader:
❯ load CONSULTANT_TIME /Users/vendome/Projects/dbaexpert.com/fictious-time.csvMCP answered:
SP2-0738: Restricted command:
"load CONSULTANT_TIME /Users/vendome/Projects/dbaexpert.com/fictious-time.csv"
not availableThe SQLcl MCP server blocks any command that touches the local file system. That is the right default from a security standpoint, and it meant the bulk loader was off the table for this run. There are commands you are not allowed to execute through run-sqlcl. The server rejects them with SP2-0738: Restricted command:
load <table> <file>— bulk loader@<script.sql>or@@<script.sql>— script execution from a filespool— writing output to the local filesystemhost/!— shell escapes- Any command that reads from or writes to the local filesystem
↳Fallback Option #1: A Python Script Written by Claude Code
The next prompt asked Claude to shape the data for a load that worked with the MCP server.
❯ The SQLcl load command is restricted by the MCP server. Read
fictious-time.csv, parse it as CSV (there are quoted fields with
commas inside), and write a Python script that emits INSERT ALL
statements in batches of 100 rows, targeting CONSULTANT_TIME. Use
TO_DATE('...','DD-MON-RR') for the date column and escape single
quotes.Claude wrote the script, ran it, and produced eleven batch files under /tmp/ct_load/: ten of 100 rows and one of 84.
↳Fallback Option #2: SQL*Loader
The textbook fallback would have been SQL*Loader. Claude could have generated a sqlldr control file and pushed 1,084 rows with direct path speed, but that needs the Oracle client on the agent's machine. I did not have it installed and did not want the extra effort for a one-off load. Option #1 stayed with what was already on the box.
↳MCP Behaves Like a Heterogeneous Gateway
Oracle DBAs who have worked with DG4ODBC will recognize the behavior. MCP is not a bulk transport. Every tool call is one request paired with one response, so eleven INSERT ALL batches means eleven round trips. For 1,084 rows the overhead is invisible, but for a million rows the right answer is still SQL*Loader or an external table.
↳Confirming the Load
SELECT COUNT(*) FROM CONSULTANT_TIME; ROW_COUNT
-----------
1084The load produced 1,084 timesheet entries with:
- 9 distinct consultants
- 30 distinct client names.
Q4 Utilization: One Prompt, One Chart
With the data loaded and MCP proved out as a two-way channel between Claude and the database, I was finally able to ask the core question. What does utilization look like this quarter? This was answered with a single prompt.
❯ Show me a graphical representation of consultant utilization based
on 500 billable hours per quarter. Sum the billable hours for
October, November, and December. Show me the utilization as a
percentage for each consultant. Render it as a bar chart with the
utilization percentage labeled, ordered from highest to lowest.Claude translated the ask into an aggregate SQL query, sent it through the run-sql MCP tool, and produced the result as an ASCII bar chart in the chat window. Here is the SQL it generated.
SELECT employee,
SUM(hours) AS billable_hours,
ROUND(SUM(hours) * 100 / 500, 2) AS util_pct
FROM consultant_time
WHERE billable = 'Billable'
AND entry_date >= DATE '2025-10-01'
AND entry_date < DATE '2026-01-01'
GROUP BY employee
ORDER BY SUM(hours) DESC;It is a straightforward aggregate that filters to billable rows inside Q4, sums hours per consultant, computes the percentage against the 500-hour target, and sorts from high to low, with no hand-holding required. Here is the chart it came back with.
Consultant Hours Util% Utilization (1 block = 2%) 100% target ┤
─────────────────── ─────── ─────── ─────────────────────────────────────────────────
Admin Quincy 669.00 133.80% ███████████████████████████████████████████████████████████████████ ← OVER
Berry Kline 502.50 100.50% ██████████████████████████████████████████████████▏
Randy Rogers 498.00 99.60% █████████████████████████████████████████████████▊
Aaron Thomas 473.50 94.70% ███████████████████████████████████████████████▍
Charlie Handsome 472.50 94.50% ███████████████████████████████████████████████▎
Mark Pencil 400.00 80.00% ████████████████████████████████████████
Sam Carson 216.50 43.30% █████████████████████▊
Joe Cool 157.55 31.51% ███████████████▊
Matthew Shark 127.00 25.40% ████████████▊
─────────────────── ─────── ─────── ─────────────────────────────────────────────────
0% 100%
Quarter: Oct 1 to Dec 31, 2025 Target: 500 billable hours per consultant
Total Q4 billable hours (9 consultants): 3,516.55 Average utilization: 78.12%Now, I will ask for a graphical representation with the following prompt.
❯ I want a white background. Show me a graphical representation of the
consultant's utilization in a dashboard with each of the consultant
in a ring chart with percentage of utilization and total number of
hours. Also show me a bar chart with the same data points side by side.Claude Code built the dashboard and returned a link to the rendered HTML page. It is a live page you can open right now.
Rendered by Claude Code in a single session. No ETL, no BI tool, no manual SQL.
With MCP in place, we built an analytical dashboard without an ETL job in the pipeline and without a BI tool to host the chart. The data came out of Oracle AI Database 26ai through SQLcl, the dashboard rendered as a browser page, and the entire loop stayed inside one Claude Code session.
Claude Code did not stop at the chart. It read back the numbers it had just produced and offered its own assessment of what the quarter said about each consultant. You can see that read in the Observations section below.
↳Observations (from Claude)
- Admin Quincy is 33.8% over the 500-hour target (669 hours). This is likely a heavy APEX support engagement carrying significant overtime and is worth a conversation about sustainability.
- Berry Kline, Randy Rogers, Aaron Thomas, Charlie Handsome are all at or just under the target. This is the on plan tier where the business is healthy.
- Mark Pencil sits at 80%. He is close to the target but has real room to take on more billable work.
- Sam Carson, Joe Cool, Matthew Shark are all under 50%. That is the underutilized tier and is the first place a practice manager looks to redeploy, pair on an existing engagement, or backfill with new work.
All the setup from Part 1 paid off here in a single prompt. There was no ETL job and no BI tool involved. A single chat message produced an aggregate against 1,084 rows and a chart that a practice lead could act on.
Conclusion
With MCP in place, you can simply describe what you want in plain natural English and get real output from the database, whether the question is about a schema, a table, or almost anything the data dictionary knows about. You can ask Claude to write stored procedures, packages, and functions, and you can use the same chat window to investigate database size, tablespace usage, security posture, or to copy a schema from one environment to another. English, paired with the right prompt and the right context engineering, is becoming the new programming language for database work. Come back for Part 3, where we shift the same MCP plumbing toward the data architect's view of the database: extending the schema, auditing the design, indexing the foreign keys, and producing a complete ER diagram in a single prompt.