# Python

# Read Excel to Oracle by Pandas, xlrd and oracledb(cx_Oracle)

cx_Oracle has been renamed to python-oracledb. The last python-oracledb version to support Oracle Database 11g is 3.4.2. (python-oracledb Release Notes)

python-oracledb’s default Thin mode can connect to Oracle Database 12.1 or later. To connect to Oracle Database 11.2, you need to enable Thick mode by calling oracledb.init_oracle_client() in your code. (Enabling python-oracledb Thick Mode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pathlib import Path

import oracledb
import pandas as pd
from sqlalchemy import create_engine
from sqlalchemy.dialects.oracle import CLOB, VARCHAR2

# Thick mode is required via the installed Instant Client.
# oracledb.init_oracle_client(lib_dir=r"C:\Program Files\instantclient_11_2")
oracledb.init_oracle_client()

dev = "name:pwd@DEV"
conn_string = f"oracle+oracledb://{dev}"


def read_excels_to_db(paths):
for path in paths:
tablename = path.stem
df = pd.read_excel(path, keep_default_na=False, engine="xlrd")
# dtype = {c: VARCHAR2(1000)
# for c in df.columns[df.dtypes == 'object'].tolist()}

# dtype = {c: VARCHAR2(df[c].str.len().max())
# for c in df.columns[df.dtypes == 'object'].tolist()}

# Pandas 3.0
str_cols = df.select_dtypes(include=["object", "string"]).columns.tolist()
dtype = {
c: VARCHAR2(int(df[c].str.len().max())) for c in str_cols
} # convert object/string columns to oracle VARCHAR2
# or
# dtype = {}
# for c in df.columns:
# if pd.api.types.is_object_dtype(df[c]) or pd.api.types.is_string_dtype(
# df[c]
# ):
# max_bytes = df[c].str.encode("utf-8").str.len().max()

# if max_bytes and max_bytes <= 4000:
# dtype[c] = VARCHAR2(max_bytes)
# else:
# dtype[c] = CLOB

engine = create_engine(conn_string, echo=False)
df.to_sql(
tablename,
con=engine,
index=False,
if_exists="append",
dtype=dtype, # pyright: ignore[reportArgumentType]
chunksize=10**4,
)


if __name__ == "__main__":
paths = Path("./").glob("*.xlsx")
read_excels_to_db(paths)

# Read Excel/XML/CSV/HTML to Oracle by Streamlit and Pandas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import os

import oracledb
import pandas as pd
import streamlit as st
from sqlalchemy import create_engine
from sqlalchemy.dialects.oracle import VARCHAR2

# Thick mode is required via the installed Instant Client.
# oracledb.init_oracle_client(lib_dir=r"C:\Program Files\instantclient_11_2")
oracledb.init_oracle_client()

col1, col2 = st.columns(2)

with col1:
env = st.selectbox("Oracle Enviroment", ("DEV", "TEST", "PROD"))

if env in ("DEV", "TEST"):
conn_string = f"oracle+oracledb://name:pwd@{env}"
elif env in ("PROD"):
conn_string = f"oracle+oracledb://name:pwd@{env}"

engine = create_engine(conn_string, echo=False)

with col2:
tablename = st.text_input("Table Name", "")


col1, col2, col3, col4 = st.columns(4)

with col1:
sheet_name_index = st.text_input("Excel Sheet Name/Index", "0")
try:
sheet_name = int(sheet_name_index)
except:
sheet_name = sheet_name_index


with col2:
csv_separator = st.selectbox("CSV Separator", (",", "|", ":"))

with col3:
xpath = st.text_input("XML XPath", "//record")

with col4:
table_index = st.number_input("HTML Table Index", value=0)


uploaded_files = st.file_uploader(
"Choose a Excel/CSV/XML/HTML file", accept_multiple_files=True
)


is_upload = st.button("Upload Data to Oracle")
# button default style
# <div class="row-widget stButton">
# <button kind="primary" class="css-1cpxqw2 edgvbvh1">Upload</button>
# </div>
st.markdown(
"""
<style>
div.stButton button:first-child {
background-color: rgb(204, 49, 49);
width: 100%;
color: #fff;
padding: 8px;
}
</style>""",
unsafe_allow_html=True,
)


if is_upload:
for uploaded_file in uploaded_files:
filename = uploaded_file.name
basename, extension = os.path.splitext(filename)

if not tablename:
tablename = basename.lower()

if extension in (".xls", ".xlsx"):
df = pd.read_excel(
uploaded_file,
sheet_name=sheet_name,
keep_default_na=False,
engine="openpyxl",
)
elif extension in (".csv", ".txt"):
df = pd.read_csv(uploaded_file, keep_default_na=False, sep=csv_separator)
elif extension in (".xml"):
df = pd.read_xml(uploaded_file, xpath=xpath)
elif extension in (".html"):
data_lists = pd.read_html(uploaded_file)
df = pd.DataFrame(data_lists[table_index])
else:
f"Unknown file format `{extension}`"

dtype = {c: VARCHAR2(4000) for c in df.columns[df.dtypes == "object"].tolist()}
st.success(f"💕Read `{filename}` success!")
st.write(df.head())

df.to_sql(
tablename,
con=engine,
index=False,
if_exists="append",
dtype=dtype,
chunksize=10**4,
)

st.success(f"💕Upload `{filename}` success!")
"***" # markdown horizontal rule

# streamlit run

# Compress large file to zip

1
2
3
4
5
6
7
8
9
10
11
import os
import zipfile

def zip_large_file(filepath: str, max_size: int = 10 * 1024 * 1024):
if os.path.getsize(filepath) > max_size:
zip_file = os.path.splitext(filepath)[0] + '.zip'
with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.write(filepath, os.path.basename(filepath))
return zip_file
else:
return filepath

# Rust

# Oracle CRUD by oracle crate

oracle - Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use oracle::{Connection, Error, ErrorKind};

#[derive(Debug)]
struct Employee {
id: i64,
name: String,
salary: f64,
}

fn connect() -> Result<Connection, Error> {
// username, password, connect string ("host:port/service_name" or a TNS alias)
Connection::connect("username", "password", "TEST2")
}

fn create_table(conn: &Connection) -> Result<(), Error> {
// Note: IDENTITY columns require COMPATIBLE >= 12.2, which not all
// Oracle instances (e.g. older XE setups) have. Using a sequence +
// trigger instead works on every Oracle version (9i and up).
let statements = [
"BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE employees (
id NUMBER PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
salary NUMBER(10,2)
)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN -- ORA-00955: name already used
RAISE;
END IF;
END;",
"BEGIN
EXECUTE IMMEDIATE 'CREATE SEQUENCE employees_seq START WITH 1 INCREMENT BY 1';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN -- ORA-00955: name already used
RAISE;
END IF;
END;",
"BEGIN
EXECUTE IMMEDIATE '
CREATE OR REPLACE TRIGGER employees_bir
BEFORE INSERT ON employees
FOR EACH ROW
WHEN (NEW.id IS NULL)
BEGIN
SELECT employees_seq.NEXTVAL INTO :NEW.id FROM dual;
END;';
END;",
];

for sql in statements {
conn.execute(sql, &[])?;
}
Ok(())
}

// ---------- CREATE ----------
fn create_employee(conn: &Connection, name: &str, salary: f64) -> Result<i64, Error> {
let stmt = conn.execute(
"INSERT INTO employees (name, salary) VALUES (:1, :2) RETURNING id INTO :3",
&[&name, &salary, &None::<i64>],
)?;
conn.commit()?;
let ids: Vec<i64> = stmt.returned_values(3)?;
Ok(ids[0])
}

// ---------- READ (one) ----------
fn get_employee(conn: &Connection, id: i64) -> Result<Option<Employee>, Error> {
let row = conn.query_row_as::<(i64, String, f64)>(
"SELECT id, name, salary FROM employees WHERE id = :1",
&[&id],
);

match row {
Ok((id, name, salary)) => Ok(Some(Employee { id, name, salary })),
Err(e) if e.kind() == ErrorKind::NoDataFound => Ok(None),
Err(e) => Err(e),
}
}

// ---------- READ (all) ----------
fn get_all_employees(conn: &Connection) -> Result<Vec<Employee>, Error> {
let mut employees = Vec::new();
let rows = conn.query_as::<(i64, String, f64)>(
"SELECT id, name, salary FROM employees ORDER BY id",
&[],
)?;

for row_result in rows {
let (id, name, salary) = row_result?;
employees.push(Employee { id, name, salary });
}
Ok(employees)
}

// ---------- UPDATE ----------
fn update_employee_salary(conn: &Connection, id: i64, new_salary: f64) -> Result<u64, Error> {
let stmt = conn.execute(
"UPDATE employees SET salary = :1 WHERE id = :2",
&[&new_salary, &id],
)?;
conn.commit()?;
Ok(stmt.row_count()?)
}

// ---------- DELETE ----------
fn delete_employee(conn: &Connection, id: i64) -> Result<u64, Error> {
let stmt = conn.execute("DELETE FROM employees WHERE id = :1", &[&id])?;
conn.commit()?;
Ok(stmt.row_count()?)
}

fn main() -> Result<(), Error> {
let conn = connect()?;
create_table(&conn)?;

// Create
let id = create_employee(&conn, "Alice Johnson", 75000.0)?;
println!("Inserted employee with id = {}", id);

let id2 = create_employee(&conn, "Bob Smith", 62000.0)?;
println!("Inserted employee with id = {}", id2);

// Read one
if let Some(emp) = get_employee(&conn, id)? {
println!("Fetched: {:?}", emp);
}

// Read all
println!("All employees:");
for emp in get_all_employees(&conn)? {
println!(" {:?}", emp);
}

// Update
let updated = update_employee_salary(&conn, id, 80000.0)?;
println!("Rows updated: {}", updated);

// Delete
let deleted = delete_employee(&conn, id2)?;
println!("Rows deleted: {}", deleted);

Ok(())
}

# Read CSV to Oracle by polars and oracle crate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Read a CSV with Polars and load it into Oracle.
//!
//! Mirrors the logic of the earlier Python/pandas version:
//! * every column is read as text (like pandas `dtype='str'`)
//! * identifiers are truncated to 30 chars (pre-12.2 Oracle limit)
//! * each column is sized by its max UTF-8 *byte* length:
//! <= 4000 bytes -> VARCHAR2(n) (> 4000 -> CLOB)
//! * rows are inserted with a single batched (array) INSERT
//!
//! Runtime requirement: Oracle Instant Client must be installed and findable
//! (LD_LIBRARY_PATH / PATH / DYLD_LIBRARY_PATH depending on OS).
//!
//! Usage:
//! csv2oracle <csv_path> <table_name> [separator]
//! Connection is taken from env vars ORA_USER, ORA_PASS, ORA_DSN.

use std::time::Instant;

use anyhow::{Context, Result, anyhow};
use oracle::Connection;
use polars::prelude::*;

const VARCHAR2_MAX_BYTES: usize = 4000;
const BATCH_SIZE: usize = 10_000;

fn main() -> Result<()> {
let start = Instant::now();

let mut args = std::env::args().skip(1);
let csv_path = args.next().ok_or_else(|| anyhow!("missing <csv_path>"))?;
let table = args.next().ok_or_else(|| anyhow!("missing <table_name>"))?;
let sep = match args.next().as_deref() {
Some("tab") | Some("\\t") => b'\t',
Some(s) if s.len() == 1 => s.as_bytes()[0],
Some(other) => {
return Err(anyhow!(
"separator must be a single char or 'tab', got {other:?}"
));
}
None => b',',
};

let user = std::env::var("ORA_USER").context("set ORA_USER")?;
let pass = std::env::var("ORA_PASS").context("set ORA_PASS")?;
let dsn = std::env::var("ORA_DSN").context("set ORA_DSN (e.g. host:1521/service)")?;

// ---- 1. read CSV, force every column to String (== pandas dtype='str')
let df = CsvReadOptions::default()
.with_has_header(true)
.with_parse_options(CsvParseOptions::default().with_separator(sep))
.try_into_reader_with_file_path(Some(csv_path.clone().into()))
.with_context(|| format!("opening {csv_path}"))?
.finish()
.context("parsing CSV")?
.lazy()
.select([col("*").cast(DataType::String)])
.collect()
.context("casting all columns to String")?;

if df.height() == 0 {
println!("CSV has no rows; nothing to load.");
return Ok(());
}

// ---- 2. truncate column names to 30 chars
let names: Vec<String> = df
.get_column_names()
.iter()
.map(|c| c.chars().take(30).collect::<String>())
.collect();

// pull each column as a &StringChunked once (avoid per-row lookups)
let mut cols: Vec<&StringChunked> = Vec::with_capacity(df.width());
for orig in df.get_column_names() {
let sc = df
.column(orig)?
.as_materialized_series()
.str()
.with_context(|| format!("column {orig} is not String"))?;
cols.push(sc);
}

// ---- 3. size each column -> VARCHAR2(n) or CLOB
let col_defs: Vec<String> = names
.iter()
.zip(&cols)
.map(|(name, sc)| {
let max_bytes = sc
.into_iter()
.filter_map(|opt| opt.map(str::len)) // .len() = UTF-8 byte length
.max()
.unwrap_or(0)
.max(1); // never declare VARCHAR2(0)
let ty = if max_bytes <= VARCHAR2_MAX_BYTES {
format!("VARCHAR2({max_bytes})")
} else {
"CLOB".to_string()
};
format!("\"{name}\" {ty}")
})
.collect();

let table = table.chars().take(30).collect::<String>();
let create_sql = format!("CREATE TABLE {table} (\n {}\n)", col_defs.join(",\n "));

// ---- 4. connect
let conn = Connection::connect(&user, &pass, &dsn).context("connecting to Oracle")?;

// create table if it does not already exist (ORA-00955 = name already used)
match conn.execute(&create_sql, &[]) {
Ok(_) => println!("created table {table}"),
Err(e) if e.to_string().contains("ORA-00955") => {
println!("table {table} already exists; appending");
}
Err(e) => return Err(anyhow!("CREATE TABLE failed: {e}\nSQL was:\n{create_sql}")),
}

// ---- 5. batched INSERT with numbered binds :1, :2, ...
let quoted_cols = names
.iter()
.map(|n| format!("\"{n}\""))
.collect::<Vec<_>>()
.join(", ");
let placeholders = (1..=names.len())
.map(|i| format!(":{i}"))
.collect::<Vec<_>>()
.join(", ");
let insert_sql = format!("INSERT INTO {table} ({quoted_cols}) VALUES ({placeholders})");

let mut batch = conn.batch(&insert_sql, BATCH_SIZE).build()?;
let nrows = df.height();
for i in 0..nrows {
// Option<&str> binds as NULL when None; borrows live only this iteration
let row: Vec<Option<&str>> = cols.iter().map(|c| c.get(i)).collect();
let binds: Vec<&dyn oracle::sql_type::ToSql> = row
.iter()
.map(|v| v as &dyn oracle::sql_type::ToSql)
.collect();
batch.append_row(&binds)?;
}
batch.execute()?; // flush any partial batch
conn.commit()?;

let elapsed = start.elapsed();

println!(
"loaded {nrows} rows into {table} elapsed {:?}",
elapsed.as_secs()
);

Ok(())
}

// export ORA_USER=xxx ORA_PASS=xxx ORA_DSN=host:1521/service
// cargo run --bin csv_to_oracle -- src/bin/test.csv som_edi_test ','

// $ cargo run --bin csv_to_oracle -- src/bin/test.csv som_edi_test ','
// Finished `dev` profile [unoptimized + debuginfo] target(s) in 13.41s
// Running `target\debug\csv_to_oracle.exe src/bin/test.csv som_edi_test ,`
// created table som_edi_test
// loaded 365480 rows into som_edi_test elapsed 15
Edited on