Oracle #
Oracle Database adalah RDBMS enterprise yang dominan di sektor perbankan, pemerintahan, dan perusahaan besar di Indonesia maupun seluruh dunia. Integrasinya dengan Ruby memerlukan langkah yang lebih banyak dari MySQL atau PostgreSQL karena ketergantungan pada Oracle Instant Client — library C yang harus diunduh secara terpisah dari Oracle. Setelah berhasil terpasang, ekosistem Ruby menyediakan ruby-oci8 sebagai driver tingkat rendah, oracle-enhanced adapter untuk ActiveRecord Rails, dan Sequel sebagai query builder yang mendukung Oracle. Artikel ini membahas semua tahap dari instalasi Oracle Instant Client, koneksi, CRUD dengan bind variables (cara yang benar untuk mencegah SQL injection di Oracle), hingga memanggil stored procedure PL/SQL dari Ruby.
Instalasi Oracle Instant Client #
Oracle Instant Client adalah library yang diperlukan ruby-oci8 untuk berkomunikasi dengan Oracle Database. Kamu harus mengunduhnya dari situs Oracle (memerlukan akun gratis):
# 1. Unduh dari https://www.oracle.com/database/technologies/instant-client/downloads.html
# Pilih: Basic Package + SDK Package
# Contoh untuk Linux x86-64: instantclient-basic-linux.x64-21.11.0.0.0.zip
# instantclient-sdk-linux.x64-21.11.0.0.0.zip
# 2. Ekstrak ke direktori permanen
sudo mkdir -p /opt/oracle
sudo unzip instantclient-basic-linux.x64-21.11.0.0.0.zip -d /opt/oracle
sudo unzip instantclient-sdk-linux.x64-21.11.0.0.0.zip -d /opt/oracle
# 3. Buat symlink versi (mungkin diperlukan ruby-oci8)
cd /opt/oracle/instantclient_21_11
sudo ln -s libclntsh.so.21.1 libclntsh.so
# 4. Konfigurasi library path
echo "/opt/oracle/instantclient_21_11" | sudo tee /etc/ld.so.conf.d/oracle-instantclient.conf
sudo ldconfig
# 5. Set environment variable — tambahkan ke ~/.bashrc atau ~/.zshrc
export ORACLE_HOME=/opt/oracle/instantclient_21_11
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH
export PATH=$ORACLE_HOME:$PATH
# macOS dengan Homebrew
brew tap InstantClientTap/instantclient
brew install instantclient-basic instantclient-sdk
# 6. Install ruby-oci8
gem install ruby-oci8
# Jika gagal, tentukan path Instant Client:
gem install ruby-oci8 -- --with-instant-client-dir=/opt/oracle/instantclient_21_11
# Gemfile
gem 'ruby-oci8', '~> 2.2' # driver Oracle
gem 'activerecord-oracle_enhanced-adapter', '~> 7.1' # untuk Rails
gem 'sequel', '~> 5.75' # query builder opsional
String Koneksi Oracle #
Oracle punya beberapa format string koneksi yang berbeda:
require 'oci8'
# Format 1: Easy Connect (paling mudah, tidak perlu tnsnames.ora)
# Format: host[:port][/service_name]
klien = OCI8.new(
"appuser", # username
ENV["ORACLE_PASSWORD"], # password
"//localhost:1521/ORCL" # //host:port/service_name
)
# Format 2: TNS alias (memerlukan file tnsnames.ora)
# Pastikan ORACLE_HOME dan TNS_ADMIN sudah diset
ENV["TNS_ADMIN"] = "/etc/oracle" # direktori tnsnames.ora
klien = OCI8.new("appuser", ENV["ORACLE_PASSWORD"], "PROD_DB")
# Format 3: Connection descriptor lengkap (tanpa tnsnames.ora)
descriptor = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))" \
"(CONNECT_DATA=(SERVICE_NAME=ORCL)))"
klien = OCI8.new("appuser", ENV["ORACLE_PASSWORD"], descriptor)
# Format 4: Oracle Autonomous Database (ATP/ADW) dengan Wallet
ENV["TNS_ADMIN"] = "/path/to/wallet" # direktori berisi wallet
klien = OCI8.new("admin", ENV["ORACLE_PASSWORD"], "mydb_high")
# Periksa koneksi
puts klien.ping ? "Terhubung!" : "Gagal!"
# Informasi versi
cursor = klien.exec("SELECT * FROM v$version WHERE ROWNUM = 1")
puts cursor.fetch.first
cursor.close
# Tutup koneksi
klien.logoff
tnsnames.ora — Konfigurasi TNS #
# /etc/oracle/tnsnames.ora
# atau $ORACLE_HOME/network/admin/tnsnames.ora
PROD_DB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-prod.company.com)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = PRODDB.company.com)
)
)
DEV_DB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
(CONNECT_DATA =
(SERVICE_NAME = ORCL)
)
)
Query Dasar dengan ruby-oci8 #
SELECT — Membaca Data #
require 'oci8'
klien = OCI8.new("appuser", ENV["ORACLE_PASSWORD"], "//localhost/ORCL")
# Cara 1: exec — langsung eksekusi, iterasi cursor
cursor = klien.exec("SELECT * FROM PRODUK WHERE ROWNUM <= 10")
while (baris = cursor.fetch)
puts "#{baris[0]}: #{baris[1]} - Rp #{baris[2]}"
end
cursor.close
# Cara 2: parse + execute — untuk query yang akan dieksekusi berulang
cursor = klien.parse("SELECT ID, NAMA, HARGA FROM PRODUK WHERE AKTIF = 1")
cursor.exec
while (baris = cursor.fetch_hash) # fetch_hash → Hash dengan nama kolom
puts baris.inspect
# => {"ID"=>1, "NAMA"=>"Laptop", "HARGA"=>BigDecimal("15000000")}
end
cursor.close
# Cara 3: fetch semua sekaligus
cursor = klien.exec("SELECT ID, NAMA, HARGA FROM PRODUK ORDER BY NAMA")
semua_baris = cursor.fetch_all # Array of Arrays
puts semua_baris.first.inspect # => [1, "Keyboard", BigDecimal("450000")]
cursor.close
# Metadata kolom
cursor = klien.parse("SELECT ID, NAMA, HARGA FROM PRODUK")
cursor.exec
puts cursor.column_metadata.map { |col| "#{col.name}(#{col.data_type})" }.inspect
# => ["ID(NUMBER)", "NAMA(VARCHAR2)", "HARGA(NUMBER)"]
cursor.close
Oracle mengembalikan semua nama kolom dalam huruf kapital secara default. Saat mengakses hasilfetch_hash, gunakanbaris["NAMA"], bukanbaris["nama"]ataubaris[:nama]. Ini berbeda dari MySQL/PostgreSQL yang biasanya menggunakan huruf kecil.
Bind Variables — Cara yang Benar di Oracle #
Bind variable adalah cara yang direkomendasikan untuk menyisipkan nilai ke query Oracle. Berbeda dari MySQL yang menggunakan ?, Oracle menggunakan :nama_variabel:
# ANTI-PATTERN: string interpolasi — SQL injection dan tidak efisien
nama = params[:nama]
cursor = klien.exec("SELECT * FROM PRODUK WHERE NAMA = '#{nama}'")
# BENAR: bind variable — aman dan lebih efisien
# Oracle me-cache execution plan sehingga query berikutnya lebih cepat
cursor = klien.parse("SELECT * FROM PRODUK WHERE NAMA = :nama AND HARGA < :harga")
cursor.bind_param(":nama", nama_input)
cursor.bind_param(":harga", harga_maks)
cursor.exec
while (baris = cursor.fetch_hash)
puts "#{baris['NAMA']}: #{baris['HARGA']}"
end
cursor.close
# Atau bind positional (menggunakan nomor)
cursor = klien.parse("SELECT * FROM PRODUK WHERE KATEGORI_ID = :1 AND AKTIF = :2")
cursor.bind_param(1, kategori_id)
cursor.bind_param(2, 1)
cursor.exec
Keunggulan Bind Variable di Oracle:
✓ Mencegah SQL injection
✓ Oracle meng-cache execution plan — query identik lebih cepat
✓ Mengurangi parsing overhead di server Oracle
✓ Standar yang sangat dianjurkan oleh Oracle
✗ Sedikit lebih verbose dari interpolasi langsung
CRUD Lengkap #
class ProdukOracleRepository
def initialize(klien)
@klien = klien
end
# CREATE — Oracle tidak punya AUTO_INCREMENT, gunakan SEQUENCE
def buat(nama:, harga:, stok:, kategori_id:)
# Dapatkan ID berikutnya dari sequence
id_cursor = @klien.exec("SELECT SEQ_PRODUK.NEXTVAL FROM DUAL")
id_baru = id_cursor.fetch.first.to_i
id_cursor.close
cursor = @klien.parse(<<~SQL)
INSERT INTO PRODUK (ID, NAMA, HARGA, STOK, KATEGORI_ID, AKTIF, CREATED_AT)
VALUES (:id, :nama, :harga, :stok, :kategori_id, 1, SYSDATE)
SQL
cursor.bind_param(":id", id_baru)
cursor.bind_param(":nama", nama)
cursor.bind_param(":harga", harga)
cursor.bind_param(":stok", stok)
cursor.bind_param(":kategori_id", kategori_id)
cursor.exec
@klien.commit
cursor.close
temukan(id_baru)
end
# READ
def temukan(id)
cursor = @klien.parse(
"SELECT * FROM PRODUK WHERE ID = :id"
)
cursor.bind_param(":id", id)
cursor.exec
baris = cursor.fetch_hash
cursor.close
baris
end
def cari_semua(aktif: true, limit: 50, offset: 0)
aktif_val = aktif ? 1 : 0
cursor = @klien.parse(<<~SQL)
SELECT p.*, k.NAMA AS KATEGORI_NAMA
FROM PRODUK p
LEFT JOIN KATEGORI k ON p.KATEGORI_ID = k.ID
WHERE p.AKTIF = :aktif
ORDER BY p.CREATED_AT DESC
OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY
SQL
cursor.bind_param(":aktif", aktif_val)
cursor.bind_param(":offset", offset)
cursor.bind_param(":limit", limit)
cursor.exec
hasil = []
while (baris = cursor.fetch_hash)
hasil << baris
end
cursor.close
hasil
end
# UPDATE
def perbarui(id, harga: nil, stok: nil, nama: nil)
update_parts = []
params = {}
if harga
update_parts << "HARGA = :harga"
params[:harga] = harga
end
if stok
update_parts << "STOK = :stok"
params[:stok] = stok
end
if nama
update_parts << "NAMA = :nama"
params[:nama] = nama
end
return false if update_parts.empty?
params[:id] = id
sql = "UPDATE PRODUK SET #{update_parts.join(', ')}, UPDATED_AT = SYSDATE WHERE ID = :id"
cursor = @klien.parse(sql)
params.each { |k, v| cursor.bind_param(":#{k}", v) }
cursor.exec
rows = cursor.row_count
@klien.commit
cursor.close
rows > 0
end
# Soft delete
def nonaktifkan(id)
cursor = @klien.parse(
"UPDATE PRODUK SET AKTIF = 0, UPDATED_AT = SYSDATE WHERE ID = :id"
)
cursor.bind_param(":id", id)
cursor.exec
rows = cursor.row_count
@klien.commit
cursor.close
rows > 0
end
end
SEQUENCE — Auto-Increment di Oracle #
Oracle tidak punya AUTO_INCREMENT seperti MySQL. Gunakan SEQUENCE untuk menghasilkan nilai unik berurutan:
-- Buat sequence
CREATE SEQUENCE SEQ_PRODUK
START WITH 1
INCREMENT BY 1
NOCACHE -- tidak cache nilai (lebih aman tapi lebih lambat)
NOCYCLE; -- tidak mulai dari awal setelah mencapai maksimum
-- Nilai berikutnya
SELECT SEQ_PRODUK.NEXTVAL FROM DUAL;
-- Nilai saat ini (setelah setidaknya satu NEXTVAL di session ini)
SELECT SEQ_PRODUK.CURRVAL FROM DUAL;
-- Gunakan di INSERT
INSERT INTO PRODUK (ID, NAMA) VALUES (SEQ_PRODUK.NEXTVAL, 'Laptop');
-- Oracle 12c+ — Identity Column (seperti AUTO_INCREMENT)
CREATE TABLE PRODUK (
ID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
NAMA VARCHAR2(200) NOT NULL
);
# Mendapatkan ID dari sequence sebelum INSERT
def nextval(klien, sequence_name)
cursor = klien.exec("SELECT #{sequence_name}.NEXTVAL FROM DUAL")
id = cursor.fetch.first.to_i
cursor.close
id
end
id = nextval(@klien, "SEQ_PRODUK")
# Atau gunakan RETURNING INTO untuk mendapat ID setelah INSERT
cursor = @klien.parse(<<~SQL)
INSERT INTO PRODUK (ID, NAMA, HARGA)
VALUES (SEQ_PRODUK.NEXTVAL, :nama, :harga)
RETURNING ID INTO :new_id
SQL
cursor.bind_param(":nama", "Monitor")
cursor.bind_param(":harga", 3_500_000)
cursor.bind_param(":new_id", OCI8::Cursor::NULL, Integer) # OUT parameter
cursor.exec
id_baru = cursor[":new_id"]
cursor.close
Pagination di Oracle #
Oracle punya tiga cara untuk paginasi, tergantung versinya:
# Oracle 12c+ — OFFSET FETCH (standar SQL, paling bersih)
def ambil_halaman_modern(halaman, per_halaman = 20)
offset = (halaman - 1) * per_halaman
cursor = @klien.parse(<<~SQL)
SELECT ID, NAMA, HARGA, STOK
FROM PRODUK
WHERE AKTIF = 1
ORDER BY NAMA ASC
OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY
SQL
cursor.bind_param(":offset", offset)
cursor.bind_param(":limit", per_halaman)
cursor.exec
hasil = []
while (b = cursor.fetch_hash)
hasil << b
end
cursor.close
hasil
end
# Oracle 11g ke bawah — ROW_NUMBER() subquery (paling umum)
def ambil_halaman_subquery(halaman, per_halaman = 20)
awal = (halaman - 1) * per_halaman + 1
akhir = halaman * per_halaman
cursor = @klien.parse(<<~SQL)
SELECT * FROM (
SELECT p.*, ROW_NUMBER() OVER (ORDER BY NAMA ASC) AS RN
FROM PRODUK p
WHERE AKTIF = 1
) WHERE RN BETWEEN :awal AND :akhir
SQL
cursor.bind_param(":awal", awal)
cursor.bind_param(":akhir", akhir)
cursor.exec
hasil = []
while (b = cursor.fetch_hash)
hasil << b.reject { |k, _| k == "RN" } # hapus kolom RN dari hasil
end
cursor.close
hasil
end
# Oracle lama — ROWNUM (hanya untuk halaman pertama yang mudah)
# ROWNUM tidak bisa digunakan langsung untuk OFFSET
cursor = @klien.exec(
"SELECT * FROM PRODUK WHERE AKTIF = 1 AND ROWNUM <= 20 ORDER BY NAMA"
)
# Perhatian: ORDER BY dieksekusi SETELAH ROWNUM — hasilnya mungkin tidak konsisten
# Gunakan ROW_NUMBER() atau OFFSET FETCH untuk pagination yang benar
PL/SQL dan Stored Procedure #
Oracle sangat kuat dalam stored procedure PL/SQL. Memanggil SP dari Ruby adalah kebutuhan umum di lingkungan enterprise:
# Memanggil procedure PL/SQL
def panggil_sp_update_stok(klien, produk_id, jumlah_delta)
cursor = klien.parse("BEGIN SP_UPDATE_STOK(:p_id, :p_delta); END;")
cursor.bind_param(":p_id", produk_id, Integer)
cursor.bind_param(":p_delta", jumlah_delta, Integer)
cursor.exec
cursor.close
klien.commit
end
# Function PL/SQL — mengembalikan nilai
def panggil_fn_harga_setelah_diskon(klien, produk_id, persen_diskon)
cursor = klien.parse(
"BEGIN :hasil := FN_HARGA_DISKON(:p_id, :p_diskon); END;"
)
cursor.bind_param(":hasil", nil, OCI8::BDPARAM_TYPE_OUT, Float)
cursor.bind_param(":p_id", produk_id, Integer)
cursor.bind_param(":p_diskon", persen_diskon, Float)
cursor.exec
harga_diskon = cursor[":hasil"]
cursor.close
harga_diskon
end
# Procedure dengan OUT parameter
def dapatkan_info_stok(klien, produk_id)
cursor = klien.parse(<<~SQL)
BEGIN
SP_GET_STOK_INFO(
P_PRODUK_ID => :p_id,
P_NAMA => :p_nama,
P_STOK => :p_stok,
P_STATUS => :p_status
);
END;
SQL
cursor.bind_param(":p_id", produk_id, Integer)
cursor.bind_param(":p_nama", nil, OCI8::BDPARAM_TYPE_OUT, String, 200)
cursor.bind_param(":p_stok", nil, OCI8::BDPARAM_TYPE_OUT, Integer)
cursor.bind_param(":p_status", nil, OCI8::BDPARAM_TYPE_OUT, String, 20)
cursor.exec
hasil = {
nama: cursor[":p_nama"],
stok: cursor[":p_stok"],
status: cursor[":p_status"]
}
cursor.close
hasil
end
# Blok PL/SQL anonim — tangguhkan logika di Ruby
cursor = klien.parse(<<~SQL)
DECLARE
v_total NUMBER := 0;
v_count NUMBER := 0;
BEGIN
FOR r IN (SELECT HARGA FROM PRODUK WHERE KATEGORI_ID = :kat_id AND AKTIF = 1) LOOP
v_total := v_total + r.HARGA;
v_count := v_count + 1;
END LOOP;
:p_total := v_total;
:p_count := v_count;
END;
SQL
cursor.bind_param(":kat_id", 2, Integer)
cursor.bind_param(":p_total", nil, OCI8::BDPARAM_TYPE_OUT, Float)
cursor.bind_param(":p_count", nil, OCI8::BDPARAM_TYPE_OUT, Integer)
cursor.exec
puts "Total: #{cursor[':p_total']}, Count: #{cursor[':p_count']}"
cursor.close
Transaksi #
ruby-oci8 tidak menggunakan auto-commit secara default — setiap perubahan harus di-commit secara eksplisit:
# Oracle: auto-commit TIDAK aktif secara default di ruby-oci8
klien.autocommit = false # ini adalah default
def dengan_transaksi(klien)
begin
hasil = yield
klien.commit
hasil
rescue => e
klien.rollback
raise e
end
end
# Penggunaan
dengan_transaksi(@klien) do
# Semua operasi dalam satu transaksi
cursor = @klien.parse(
"UPDATE REKENING SET SALDO = SALDO - :jumlah WHERE ID = :id"
)
cursor.bind_param(":jumlah", 500_000)
cursor.bind_param(":id", 1)
cursor.exec
cursor.close
cursor = @klien.parse(
"UPDATE REKENING SET SALDO = SALDO + :jumlah WHERE ID = :id"
)
cursor.bind_param(":jumlah", 500_000)
cursor.bind_param(":id", 2)
cursor.exec
cursor.close
end
# SAVEPOINT — transaksi bertingkat
klien.exec("SAVEPOINT sp_awal")
begin
# operasi berisiko
klien.exec("SAVEPOINT sp_tengah")
# lebih banyak operasi
klien.commit
rescue => e
klien.exec("ROLLBACK TO SAVEPOINT sp_tengah")
# tangani sebagian
end
ActiveRecord dengan Oracle Enhanced Adapter #
gem install activerecord-oracle_enhanced-adapter
# config/database.yml
default: &default
adapter: oracle_enhanced
database: //localhost:1521/ORCL
username: <%= ENV["ORACLE_USER"] %>
password: <%= ENV["ORACLE_PASSWORD"] %>
schema: APPSCHEMA # schema Oracle (opsional)
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: //localhost:1521/ORCL
production:
<<: *default
database: <%= ENV["ORACLE_CONNECTION"] %>
# config/initializers/oracle.rb
# Konfigurasi oracle_enhanced untuk konvensi Rails
ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.tap do |config|
# Gunakan emulated booleans — kolom NUMBER(1) diperlakukan sebagai boolean
config.emulate_booleans = true
# Sequence naming convention — misal untuk tabel "produk" → "seq_produk"
config.default_sequence_start_value = 1
# Konversi nama kolom UPPERCASE ke snake_case
config.cache_columns = true
end
end
# Model dengan oracle_enhanced
class Produk < ApplicationRecord
self.table_name = "PRODUK" # nama tabel Oracle uppercase
self.primary_key = "ID"
# oracle_enhanced otomatis cari sequence SEQ_PRODUK_ID atau PRODUK_SEQ
self.sequence_name = "SEQ_PRODUK"
belongs_to :kategori, foreign_key: "KATEGORI_ID"
# Emulated boolean — kolom NUMBER(1) diperlakukan sebagai true/false
attribute :aktif, :boolean
scope :aktif, -> { where(AKTIF: 1) }
scope :terbaru, -> { order(CREATED_AT: :desc) }
validates :NAMA, presence: true, length: { maximum: 200 }
validates :HARGA, numericality: { greater_than: 0 }
end
# Query sama seperti ActiveRecord biasa
Produk.aktif.terbaru.limit(10)
Produk.where("HARGA BETWEEN :min AND :max", min: 100_000, max: 5_000_000)
Produk.includes(:kategori).aktif
Sequel dengan Oracle #
require 'sequel'
DB = Sequel.connect(
adapter: "oracle",
database: "//localhost:1521/ORCL",
user: "appuser",
password: ENV["ORACLE_PASSWORD"]
)
# Query dengan Sequel
DB[:PRODUK].all
DB[:PRODUK].where(AKTIF: 1).all
DB[:PRODUK].where { HARGA < 5_000_000 }.all
DB[:PRODUK].order(Sequel.desc(:CREATED_AT)).limit(10).all
# Pagination — Sequel otomatis generate OFFSET FETCH atau ROW_NUMBER
DB[:PRODUK].where(AKTIF: 1).limit(20).offset(40).all
# INSERT
DB[:PRODUK].insert(
ID: Sequel.function(:SEQ_PRODUK__NEXTVAL), # sequence di Oracle via Sequel
NAMA: "Monitor",
HARGA: 3_500_000,
STOK: 15,
KATEGORI_ID: 2,
AKTIF: 1,
CREATED_AT: Sequel::CURRENT_TIMESTAMP
)
# Bind variable via Sequel (otomatis safe)
nama_input = params[:nama]
DB[:PRODUK].where(NAMA: nama_input).all # AMAN, Sequel parameterize otomatis
# Raw PL/SQL
DB.run("BEGIN SP_UPDATE_STOK(1, -5); END;")
DB.fetch("SELECT * FROM PRODUK WHERE ROWNUM <= ?", 10).all
Tipe Data Oracle ↔ Ruby #
Oracle Ruby (ruby-oci8) Keterangan
─────────────────────────────────────────────────────────────────
NUMBER(p,s) BigDecimal Numerik presisi tinggi
NUMBER(p,0) / INTEGER Integer Bilangan bulat
FLOAT Float Floating point
VARCHAR2(n) String String max n byte
NVARCHAR2(n) String String Unicode max n karakter
CHAR(n) String String fixed-length
CLOB String / OCI8::CLOB Teks panjang
BLOB String / OCI8::BLOB Data biner
DATE Time Tanggal + waktu (jam/menit/detik)
TIMESTAMP Time Waktu presisi nanosecond
TIMESTAMP WITH TZ Time Waktu dengan timezone
INTERVAL String Durasi waktu
XMLTYPE String Data XML
RAW(n) String (biner) Data biner fixed-length
Perbedaan Sintaks Oracle vs Database Lain #
-- DUAL TABLE — Oracle butuh FROM DUAL untuk query tanpa tabel nyata
-- MySQL/PostgreSQL: SELECT 1+1;
-- Oracle: SELECT 1+1 FROM DUAL;
SELECT SYSDATE FROM DUAL;
SELECT SEQ_PRODUK.NEXTVAL FROM DUAL;
-- LIMIT / TOP
-- MySQL: SELECT * FROM tabel LIMIT 10
-- SQL Server: SELECT TOP 10 * FROM tabel
-- Oracle 12c+: SELECT * FROM tabel FETCH FIRST 10 ROWS ONLY
-- Oracle lama: SELECT * FROM (SELECT * FROM tabel) WHERE ROWNUM <= 10
-- NULL-SAFE CONCAT
-- MySQL/PG: 'a' || NULL = NULL atau CONCAT('a', NULL) = 'a' (MySQL)
-- Oracle: 'a' || NULL = 'a' (Oracle memperlakukan '' = NULL juga!)
-- EMPTY STRING vs NULL
-- Oracle menganggap '' (string kosong) SAMA dengan NULL
-- Ini berbeda dari semua database lain!
INSERT INTO tabel (nama) VALUES (''); -- di Oracle ini = NULL!
-- OUTER JOIN syntax lama Oracle
-- Oracle: WHERE a.id = b.id(+) -- (+) di sisi yang opsional
-- Standar SQL: LEFT JOIN ... ON ... -- Oracle 9i+ mendukung ini juga
-- SYSDATE vs NOW()
-- MySQL/PG: NOW(), CURRENT_TIMESTAMP
-- Oracle: SYSDATE (tanggal+waktu lokal), SYSTIMESTAMP (dengan timezone)
-- CURRENT_DATE (tanggal), CURRENT_TIMESTAMP (dengan timezone)
Ringkasan #
- Oracle Instant Client wajib diinstal sebelum ruby-oci8 — unduh Basic Package + SDK dari situs Oracle, set
LD_LIBRARY_PATH, dan install dengangem install ruby-oci8.- Bind variable, bukan string interpolasi — Oracle menggunakan
:nama_paramsebagai placeholder; bind variable mencegah SQL injection DAN meningkatkan performa karena Oracle me-cache execution plan.- SEQUENCE, bukan AUTO_INCREMENT — Oracle tidak punya auto-increment native (kecuali Oracle 12c+ dengan Identity Column); buat
CREATE SEQUENCEdan gunakan.NEXTVALsaat INSERT.- Semua nama kolom/tabel uppercase — Oracle menyimpan nama objek dalam huruf kapital; akses
fetch_hashdenganbaris["NAMA"], bukanbaris["nama"].- Pagination dengan
OFFSET FETCH(Oracle 12c+) — untuk versi lebih lama gunakanROW_NUMBER() OVER (ORDER BY ...)dalam subquery; hindariROWNUMuntuk pagination bertingkat.auto_committidak aktif di ruby-oci8 — harus panggilklien.commitsecara eksplisit setelah setiap perubahan data; gunakan wrapper transaksi untuk keamanan.DUALtable untuk query tanpa tabel —SELECT SYSDATE FROM DUAL,SELECT SEQ.NEXTVAL FROM DUAL— Oracle memerlukanFROMbahkan untuk ekspresi sederhana.- String kosong
''= NULL di Oracle — ini berbeda dari semua database lain; pastikan validasi di sisi Ruby, bukan mengandalkanNOT NULLconstraint saja.- Oracle Enhanced Adapter untuk Rails — mendukung sequence naming convention, emulated booleans untuk kolom
NUMBER(1), dan konversi nama kolom uppercase ke snake_case.- PL/SQL untuk logika bisnis kompleks — Oracle sangat kuat dalam stored procedure; gunakan bind parameters OUT untuk mendapatkan nilai kembalian dari procedure.