MSSQL

MSSQL #

Microsoft SQL Server (MSSQL) adalah database enterprise yang banyak digunakan di lingkungan korporat, terutama di ekosistem Microsoft. Jika kamu bekerja di perusahaan yang sudah memiliki infrastruktur Windows Server atau Azure, kemungkinan besar SQL Server adalah database yang harus kamu integrasikan dari aplikasi Ruby. Ada dua pendekatan utama untuk terhubung ke SQL Server dari Ruby: tiny_tds yang menggunakan protokol TDS (Tabular Data Stream) native dan lebih cepat, atau ODBC melalui ruby-odbc yang lebih universal tapi memerlukan konfigurasi driver tambahan. Artikel ini membahas kedua pendekatan beserta integrasi dengan ActiveRecord dan Sequel untuk konteks Rails.

Opsi Koneksi ke SQL Server dari Ruby #

flowchart TD
    A[Aplikasi Ruby] --> B{Driver yang Digunakan}
    B --> C["tiny_tds\n(TDS Protocol Native)"]
    B --> D["ruby-odbc\n(ODBC Universal)"]
    B --> E["activerecord-sqlserver-adapter\n(Rails Integration)"]
    C --> F[Langsung ke SQL Server\nTanpa ODBC driver]
    D --> G[Butuh ODBC Driver\nFreeTDS / Microsoft ODBC]
    E --> H[Gunakan tiny_tds\nsebagai backend]
    C --> I[Sequel dengan adapter :tinytds]
    style C fill:#6bcb77
    style E fill:#6bcb77
Pilih berdasarkan kebutuhan:
  tiny_tds       → Koneksi langsung, performa terbaik, paling populer
  ruby-odbc      → Jika sudah ada ODBC infrastructure, lebih universal
  sqlserver adapter → Jika menggunakan Rails + ActiveRecord
  Sequel         → Query builder fleksibel tanpa ORM penuh

tiny_tds — Driver TDS Native #

tiny_tds menggunakan library FreeTDS untuk berkomunikasi langsung dengan SQL Server menggunakan protokol TDS — tanpa overhead ODBC:

Instalasi #

# Ubuntu / Debian — install FreeTDS terlebih dahulu
sudo apt install freetds-dev freetds-bin

# macOS dengan Homebrew
brew install freetds

# CentOS / RHEL / Fedora
sudo dnf install freetds-devel

# Kemudian install gem
gem install tiny_tds
# Gemfile
gem 'tiny_tds', '~> 2.1'

Koneksi Dasar #

require 'tiny_tds'

# Autentikasi SQL Server (username + password)
klien = TinyTds::Client.new(
  username:      "sa",
  password:      ENV["MSSQL_PASSWORD"],
  host:          "localhost",    # atau IP server
  port:          1433,           # port default SQL Server
  database:      "TokoDB",
  timeout:       30,
  connect_timeout: 10,
  encoding:      "UTF-8",
  azure:         false           # set true jika Azure SQL
)

puts "Terhubung: #{klien.active?}"
puts "SQL Server versi: #{klien.execute("SELECT @@VERSION").first[""]}"

# Autentikasi Windows (hanya dari Windows host)
klien_windows = TinyTds::Client.new(
  host:     "sql-server.domain.local",
  database: "TokoDB",
  azure:    false,
  # Tanpa username/password = Windows Integrated Authentication
)

# Azure SQL Database
klien_azure = TinyTds::Client.new(
  username: "#{ENV['AZURE_DB_USER']}@#{ENV['AZURE_SERVER_NAME']}",
  password: ENV["AZURE_DB_PASSWORD"],
  host:     "#{ENV['AZURE_SERVER_NAME']}.database.windows.net",
  port:     1433,
  database: ENV["AZURE_DB_NAME"],
  azure:    true,
  ssl:      :require
)

# Tutup koneksi
klien.close

Konfigurasi FreeTDS #

Jika terhubung ke instance bernama (named instance) atau port non-standar, konfigurasikan FreeTDS:

# /etc/freetds/freetds.conf
[sql-server-produksi]
  host = 192.168.1.100
  port = 1433
  tds version = 7.4
  client charset = UTF-8
  text size = 64512

[sql-server-dev]
  host = localhost
  port = 1433
  tds version = 7.4
# Gunakan nama server dari freetds.conf
klien = TinyTds::Client.new(
  dataserver: "sql-server-produksi",
  database:   "TokoDB",
  username:   "appuser",
  password:   ENV["MSSQL_PASSWORD"]
)

Query Dasar #

SELECT — Membaca Data #

require 'tiny_tds'

klien = TinyTds::Client.new(
  username: "sa", password: ENV["MSSQL_PASSWORD"],
  host: "localhost", database: "TokoDB"
)

# Query dasar — mengembalikan TinyTds::Result (Enumerable)
hasil = klien.execute("SELECT TOP 10 * FROM Produk ORDER BY CreatedAt DESC")

# Iterasi hasil
hasil.each do |baris|
  puts "#{baris['Id']}: #{baris['Nama']} - Rp #{baris['Harga']}"
end

# Konversi ke Array of Hash
produk = klien.execute("SELECT Id, Nama, Harga FROM Produk WHERE Aktif = 1").to_a
puts produk.first.inspect
# => {"Id"=>1, "Nama"=>"Laptop", "Harga"=>15000000.0}

# Metadata — nama kolom
puts hasil.fields.inspect   # => ["Id", "Nama", "Harga", "Stok", "Aktif"]

# Satu baris
satu = klien.execute("SELECT * FROM Produk WHERE Id = 1").first
SQL Server menggunakan nama kolom dengan huruf kapital secara konvensional (Id, Nama, CreatedAt) — berbeda dari PostgreSQL dan MySQL yang biasanya menggunakan snake_case. Saat mengakses hasil query dengan tiny_tds, gunakan nama kolom persis seperti yang didefinisikan di database.

Parameterized Query — Wajib untuk Keamanan #

tiny_tds tidak punya prepared statement seperti mysql2, tapi kamu bisa escape nilai secara manual atau menggunakan format query yang aman:

# ANTI-PATTERN: interpolasi langsung — SQL injection!
nama_input = params[:nama]
klien.execute("SELECT * FROM Produk WHERE Nama = '#{nama_input}'")

# BENAR: escape manual dengan TinyTds
def escape(klien, nilai)
  klien.escape(nilai.to_s)
end

nama_aman = escape(klien, nama_input)
hasil = klien.execute("SELECT * FROM Produk WHERE Nama = '#{nama_aman}'")

# BENAR: gunakan parameterized query via Sequel atau ActiveRecord
# (dibahas di bagian selanjutnya)

# BENAR: sp_executesql untuk parameterized query di T-SQL
# Ini cara paling aman menggunakan tiny_tds langsung
sql = <<~SQL
  EXEC sp_executesql
    N'SELECT * FROM Produk WHERE Nama = @Nama AND Harga < @Harga',
    N'@Nama NVARCHAR(200), @Harga DECIMAL(15,2)',
    @Nama = N'#{escape(klien, nama_input)}',
    @Harga = #{harga_maks.to_f}
SQL
hasil = klien.execute(sql)

CRUD Lengkap dengan tiny_tds #

class ProdukRepository
  def initialize(klien)
    @klien = klien
  end

  # CREATE — gunakan OUTPUT INSERTED untuk dapat ID baru
  def buat(nama:, harga:, stok:, kategori_id:)
    nama_aman    = @klien.escape(nama)
    kategori_aman = kategori_id.to_i

    sql = <<~SQL
      INSERT INTO Produk (Nama, Harga, Stok, KategoriId, Aktif, CreatedAt)
      OUTPUT INSERTED.Id, INSERTED.Nama, INSERTED.CreatedAt
      VALUES (N'#{nama_aman}', #{harga.to_f}, #{stok.to_i}, #{kategori_aman}, 1, GETDATE())
    SQL

    hasil = @klien.execute(sql)
    baris = hasil.first
    baris
  end

  # READ
  def temukan(id)
    hasil = @klien.execute(
      "SELECT * FROM Produk WHERE Id = #{id.to_i}"
    )
    hasil.first
  end

  def cari_semua(aktif: true, limit: 50, offset: 0)
    aktif_int = aktif ? 1 : 0
    sql = <<~SQL
      SELECT p.*, k.Nama AS KategoriNama
      FROM Produk p
      LEFT JOIN Kategori k ON p.KategoriId = k.Id
      WHERE p.Aktif = #{aktif_int}
      ORDER BY p.CreatedAt DESC
      OFFSET #{offset.to_i} ROWS
      FETCH NEXT #{limit.to_i} ROWS ONLY
    SQL
    @klien.execute(sql).to_a
  end

  # UPDATE
  def perbarui(id, harga: nil, stok: nil, nama: nil)
    update_parts = []
    update_parts << "Harga = #{harga.to_f}" if harga
    update_parts << "Stok = #{stok.to_i}"   if stok
    update_parts << "Nama = N'#{@klien.escape(nama)}'" if nama
    return false if update_parts.empty?

    sql = <<~SQL
      UPDATE Produk
      SET #{update_parts.join(', ')}, UpdatedAt = GETDATE()
      WHERE Id = #{id.to_i}
    SQL

    @klien.execute(sql)
    @klien.affected_rows > 0
  end

  # DELETE (soft delete)
  def nonaktifkan(id)
    @klien.execute(
      "UPDATE Produk SET Aktif = 0, UpdatedAt = GETDATE() WHERE Id = #{id.to_i}"
    )
    @klien.affected_rows > 0
  end
end

Perbedaan T-SQL vs MySQL/PostgreSQL #

SQL Server menggunakan dialek T-SQL (Transact-SQL) yang punya beberapa perbedaan sintaks penting:

-- LIMITING ROWS
-- MySQL:       SELECT * FROM tabel LIMIT 10 OFFSET 20
-- PostgreSQL:  SELECT * FROM tabel LIMIT 10 OFFSET 20
-- SQL Server:  SELECT * FROM tabel ORDER BY Id
--              OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
-- SQL Server (lama): SELECT TOP 10 * FROM tabel  (tanpa OFFSET)

-- AUTO INCREMENT
-- MySQL:       id INT AUTO_INCREMENT PRIMARY KEY
-- PostgreSQL:  id SERIAL PRIMARY KEY
-- SQL Server:  Id INT IDENTITY(1,1) PRIMARY KEY

-- STRING CONCATENATION
-- MySQL/PostgreSQL: 'halo' || ' dunia'  atau CONCAT('halo', ' dunia')
-- SQL Server:  'halo' + ' dunia'

-- CURRENT TIMESTAMP
-- MySQL:       NOW(), CURRENT_TIMESTAMP
-- PostgreSQL:  NOW(), CURRENT_TIMESTAMP
-- SQL Server:  GETDATE(), GETUTCDATE(), SYSDATETIME()

-- IF NOT EXISTS
-- MySQL:       CREATE TABLE IF NOT EXISTS ...
-- SQL Server:  IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES
--              WHERE TABLE_NAME = 'Produk') CREATE TABLE Produk (...)

-- BOOLEAN
-- MySQL:       TINYINT(1) atau BOOLEAN
-- PostgreSQL:  BOOLEAN
-- SQL Server:  BIT (0 atau 1, bukan TRUE/FALSE)

-- STRING TYPE
-- MySQL:       VARCHAR(255)
-- SQL Server:  VARCHAR(255) atau NVARCHAR(255)
--              N prefix untuk Unicode: N'teks unicode'
--              Gunakan NVARCHAR untuk teks yang bisa berisi karakter non-ASCII
# Pagination dengan OFFSET FETCH (SQL Server 2012+)
def ambil_halaman(halaman, per_halaman = 20, urut_kolom = "CreatedAt")
  offset = (halaman - 1) * per_halaman
  sql = <<~SQL
    SELECT Id, Nama, Harga, Stok
    FROM Produk
    WHERE Aktif = 1
    ORDER BY #{urut_kolom} DESC
    OFFSET #{offset} ROWS
    FETCH NEXT #{per_halaman} ROWS ONLY
  SQL
  @klien.execute(sql).to_a
end

# Untuk SQL Server 2008 ke bawah — gunakan ROW_NUMBER()
def ambil_halaman_lama(halaman, per_halaman = 20)
  offset_awal = (halaman - 1) * per_halaman + 1
  offset_akhir = halaman * per_halaman
  sql = <<~SQL
    SELECT * FROM (
      SELECT *, ROW_NUMBER() OVER (ORDER BY CreatedAt DESC) AS RowNum
      FROM Produk
      WHERE Aktif = 1
    ) AS BerpenomoranBaris
    WHERE RowNum BETWEEN #{offset_awal} AND #{offset_akhir}
  SQL
  @klien.execute(sql).to_a
end

Stored Procedure #

SQL Server sangat mengutamakan stored procedure. Memanggil stored procedure dari Ruby adalah kebutuhan yang sering muncul di lingkungan enterprise:

# Memanggil stored procedure sederhana
hasil = klien.execute("EXEC sp_GetProdukById @Id = 1")
puts hasil.first.inspect

# Stored procedure dengan banyak parameter
def panggil_sp_cari_produk(klien, kata_kunci:, kategori_id: nil, maks_harga: nil)
  params = ["@KataKunci = N'#{klien.escape(kata_kunci)}'"]
  params << "@KategoriId = #{kategori_id.to_i}" if kategori_id
  params << "@MaksHarga = #{maks_harga.to_f}" if maks_harga

  sql = "EXEC sp_CariProduk #{params.join(', ')}"
  klien.execute(sql).to_a
end

produk = panggil_sp_cari_produk(
  klien,
  kata_kunci: "laptop",
  kategori_id: 2,
  maks_harga: 20_000_000
)

# Stored procedure dengan OUTPUT parameter
def dapatkan_jumlah_stok(klien, produk_id)
  sql = <<~SQL
    DECLARE @JumlahStok INT
    EXEC sp_GetJumlahStok
      @ProdukId = #{produk_id.to_i},
      @JumlahStok = @JumlahStok OUTPUT
    SELECT @JumlahStok AS JumlahStok
  SQL
  klien.execute(sql).first["JumlahStok"]
end

# Multiple result sets dari satu stored procedure
klien.execute("EXEC sp_DashboardData").each_with_object([]) do |set, hasil|
  # Setiap iterasi adalah result set berbeda
  hasil << set
end

Transaksi #

def transfer_stok_aman(klien, dari_id, ke_id, jumlah)
  klien.execute("BEGIN TRANSACTION")

  begin
    # Cek dan lock baris dengan UPDLOCK + ROWLOCK
    sumber = klien.execute(
      "SELECT Stok FROM Produk WITH (UPDLOCK, ROWLOCK) WHERE Id = #{dari_id.to_i}"
    ).first

    raise "Produk sumber tidak ditemukan" unless sumber
    raise "Stok tidak mencukupi: #{sumber['Stok']} < #{jumlah}" if sumber["Stok"] < jumlah

    klien.execute(
      "UPDATE Produk SET Stok = Stok - #{jumlah.to_i} WHERE Id = #{dari_id.to_i}"
    )
    klien.execute(
      "UPDATE Produk SET Stok = Stok + #{jumlah.to_i} WHERE Id = #{ke_id.to_i}"
    )

    klien.execute("COMMIT TRANSACTION")
    true

  rescue => e
    klien.execute("ROLLBACK TRANSACTION")
    raise e
  end
end

# Helper transaksi
def dengan_transaksi(klien, savepoint: nil)
  if savepoint
    klien.execute("SAVE TRANSACTION #{savepoint}")
  else
    klien.execute("BEGIN TRANSACTION")
  end

  begin
    hasil = yield
    klien.execute("COMMIT TRANSACTION") unless savepoint
    hasil
  rescue => e
    if savepoint
      klien.execute("ROLLBACK TRANSACTION #{savepoint}")
    else
      klien.execute("ROLLBACK TRANSACTION")
    end
    raise e
  end
end

Sequel dengan SQL Server #

Sequel mendukung SQL Server melalui adapter tinytds atau odbc:

require 'sequel'

# Koneksi Sequel via tiny_tds
DB = Sequel.connect(
  adapter:   "tinytds",
  host:      "localhost",
  port:      1433,
  database:  "TokoDB",
  username:  "sa",
  password:  ENV["MSSQL_PASSWORD"],
  timeout:   30
)

# Query dasar
DB[:Produk].all
DB[:Produk].where(Aktif: true).all
DB[:Produk].where { Harga < 5_000_000 }.all
DB[:Produk].order(Sequel.desc(:CreatedAt)).limit(10).all

# Pagination — Sequel otomatis generate OFFSET FETCH untuk SQL Server
DB[:Produk].where(Aktif: true).limit(20).offset(40).all

# INSERT dengan OUTPUT
id_baru = DB[:Produk].insert(
  Nama:       "Monitor",
  Harga:      3_500_000,
  Stok:       15,
  KategoriId: 2,
  Aktif:      true,
  CreatedAt:  Time.now
)

# UPDATE
DB[:Produk].where(Id: id_baru).update(Harga: 3_200_000, UpdatedAt: Time.now)

# Parameterized query — Sequel otomatis escape dan parameterize
nama_input = params[:nama]   # input dari user
DB[:Produk].where(Nama: nama_input).all  # AMAN — tidak perlu escape manual

# JOIN
DB[:Produk]
  .join(:Kategori, Id: :KategoriId)
  .select(
    Sequel[:Produk][:Nama],
    Sequel[:Produk][:Harga],
    Sequel[:Kategori][:Nama].as(:KategoriNama)
  )
  .where(Sequel[:Produk][:Aktif] => true)
  .all

# Stored procedure via raw SQL
DB.fetch("EXEC sp_CariProduk @KataKunci = ?", "%laptop%").all

# Transaksi
DB.transaction do
  DB[:Rekening].where(Id: 1).update(Saldo: Sequel[:Saldo] - 500_000)
  DB[:Rekening].where(Id: 2).update(Saldo: Sequel[:Saldo] + 500_000)
end

ActiveRecord dengan SQL Server Adapter #

Untuk aplikasi Rails yang perlu terhubung ke SQL Server:

gem install activerecord-sqlserver-adapter
# Gemfile
gem 'activerecord-sqlserver-adapter', '~> 7.1'
gem 'tiny_tds',                       '~> 2.1'
# config/database.yml
default: &default
  adapter: sqlserver
  encoding: utf8
  username: <%= ENV["MSSQL_USERNAME"] || "sa" %>
  password: <%= ENV["MSSQL_PASSWORD"] %>
  host:     <%= ENV["MSSQL_HOST"] || "localhost" %>
  port:     1433
  timeout:  30
  pool:     <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: TokoDB_Development

test:
  <<: *default
  database: TokoDB_Test

production:
  <<: *default
  database: <%= ENV["MSSQL_DATABASE"] %>
  host:     <%= ENV["MSSQL_HOST"] %>
# app/models/produk.rb
class Produk < ApplicationRecord
  # SQL Server menggunakan nama tabel PascalCase secara konvensional
  self.table_name = "Produk"
  self.primary_key = "Id"

  belongs_to :kategori, foreign_key: "KategoriId"

  # Kolom bit (0/1) di SQL Server — ActiveRecord membacanya sebagai boolean
  scope :aktif,  -> { where(Aktif: true) }
  scope :terbaru, -> { order(CreatedAt: :desc) }

  # Pagination — ActiveRecord sqlserver adapter otomatis generate OFFSET FETCH
  scope :halaman, ->(n, per = 20) { limit(per).offset((n - 1) * per) }

  validates :Nama,  presence: true, length: { maximum: 200 }
  validates :Harga, numericality: { greater_than: 0 }
end

# Penggunaan
Produk.aktif.terbaru.limit(10)
Produk.aktif.halaman(2, 20)
Produk.where("Harga BETWEEN ? AND ?", 100_000, 5_000_000)
Produk.includes(:kategori).aktif

# Memanggil stored procedure
hasil = ActiveRecord::Base.connection.execute("EXEC sp_GetProdukById @Id = 1")

Migrasi dengan SQL Server #

# db/migrate/20240815000001_buat_tabel_produk_sqlserver.rb
class BuatTabelProdukSqlserver < ActiveRecord::Migration[7.1]
  def change
    create_table :Produk, primary_key: :Id do |t|
      t.string   :Nama,       null: false, limit: 200
      t.text     :Deskripsi
      t.decimal  :Harga,      null: false, precision: 15, scale: 2
      t.integer  :Stok,       null: false, default: 0
      t.boolean  :Aktif,      null: false, default: true   # → BIT di SQL Server
      t.integer  :KategoriId, null: false
      t.string   :SKU,        limit: 50

      # SQL Server menggunakan datetime2 untuk presisi lebih tinggi
      t.column :CreatedAt, :datetime2, null: false, default: -> { "GETDATE()" }
      t.column :UpdatedAt, :datetime2
    end

    add_index :Produk, :Aktif
    add_index :Produk, :Harga
    add_index :Produk, [:KategoriId, :Aktif]
    add_index :Produk, :SKU, unique: true

    # Foreign key
    add_foreign_key :Produk, :Kategori, column: :KategoriId
  end
end

Tipe Data SQL Server ↔ Ruby #

SQL Server        Ruby (tiny_tds)    Keterangan
────────────────────────────────────────────────────────────
INT               Integer            Bilangan bulat 32-bit
BIGINT            Integer            Bilangan bulat 64-bit
DECIMAL(p,s)      BigDecimal         Presisi tinggi
FLOAT             Float              Floating point
BIT               true/false         Boolean (0 atau 1)
VARCHAR(n)        String             Teks ASCII max n karakter
NVARCHAR(n)       String             Teks Unicode max n karakter
TEXT              String             Teks panjang (deprecated)
NTEXT             String             Teks Unicode panjang (deprecated)
DATETIME          Time               Presisi milidetik
DATETIME2         Time               Presisi hingga 100 nanosecond
DATE              Date               Hanya tanggal
UNIQUEIDENTIFIER  String             UUID/GUID
VARBINARY         String (binary)    Data biner
XML               String             Data XML

Koneksi via ODBC (Alternatif) #

Jika kamu sudah punya ODBC infrastructure atau butuh koneksi yang lebih universal:

# Ubuntu — install driver Microsoft ODBC
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo apt install msodbcsql17 unixodbc-dev

gem install ruby-odbc
require 'odbc'

# Koneksi via ODBC DSN (Data Source Name)
# DSN dikonfigurasi di /etc/odbc.ini atau ~/.odbc.ini
klien = ODBC.connect("MSSQLServer", "username", "password")

# Atau tanpa DSN — connection string langsung
conn_str = "Driver={ODBC Driver 17 for SQL Server};" \
           "Server=localhost,1433;" \
           "Database=TokoDB;" \
           "UID=sa;" \
           "PWD=#{ENV['MSSQL_PASSWORD']};"

klien = ODBC.connect(conn_str)
stmt = klien.run("SELECT TOP 10 * FROM Produk")
stmt.each { |baris| puts baris.inspect }
stmt.drop
klien.disconnect

Anti-Pattern dan Praktik Terbaik #

# ANTI-PATTERN 1: Interpolasi string — SQL injection
klien.execute("SELECT * FROM Produk WHERE Nama = '#{params[:nama]}'")

# BENAR: escape atau gunakan Sequel/ActiveRecord
DB[:Produk].where(Nama: params[:nama])  # Sequel — aman otomatis

# ANTI-PATTERN 2: SELECT * selalu — ambil semua kolom termasuk yang tidak perlu
klien.execute("SELECT * FROM Produk")

# BENAR: pilih kolom yang dibutuhkan saja
klien.execute("SELECT Id, Nama, Harga, Stok FROM Produk WHERE Aktif = 1")

# ANTI-PATTERN 3: Tidak menutup koneksi
klien = TinyTds::Client.new(...)
# ... gunakan klien ...
# ← lupa klien.close! → connection leak

# BENAR: selalu tutup koneksi
klien = TinyTds::Client.new(...)
begin
  # gunakan klien
ensure
  klien.close
end

# ANTI-PATTERN 4: VARCHAR untuk teks yang mungkin berisi karakter non-ASCII
# "INSERT INTO Produk (Nama) VALUES ('#{nama}')"
# Jika nama = "Rina Wijaya" dengan karakter khusus → bisa corrupt

# BENAR: gunakan NVARCHAR dan prefix N untuk Unicode literal
# "INSERT INTO Produk (Nama) VALUES (N'#{klien.escape(nama)}')"

Ringkasan #

  • tiny_tds adalah pilihan terbaik untuk koneksi SQL Server dari Ruby — menggunakan protokol TDS native, lebih cepat dari ODBC, dan tidak memerlukan konfigurasi driver tambahan di Linux/macOS.
  • Selalu escape atau gunakan Sequel/ActiveRecordtiny_tds tidak punya prepared statement bawaan seperti mysql2; gunakan klien.escape() untuk escape manual, atau lebih baik gunakan Sequel yang parameterize otomatis.
  • NVARCHAR dan prefix N untuk teks Unicode — SQL Server membedakan VARCHAR (ASCII) dan NVARCHAR (Unicode); untuk kolom yang menyimpan karakter non-ASCII (termasuk huruf Indonesia dengan diakritik), gunakan NVARCHAR dan prefix N'...' saat menyisipkan.
  • Pagination gunakan OFFSET FETCH — sintaks ini tersedia sejak SQL Server 2012; untuk versi lama, gunakan ROW_NUMBER() OVER (ORDER BY ...).
  • OUTPUT INSERTED untuk mendapat ID setelah INSERT — SQL Server tidak punya LAST_INSERT_ID() seperti MySQL; gunakan OUTPUT INSERTED.Id dalam pernyataan INSERT, atau SELECT SCOPE_IDENTITY() setelah INSERT.
  • Stored procedure adalah warga kelas satu di SQL Server — enterprise SQL Server sering menyembunyikan logika bisnis di stored procedure; gunakan EXEC sp_NamaSP @Param = nilai dari Ruby.
  • WITH (UPDLOCK, ROWLOCK) untuk lock yang presisi — saat membaca baris yang akan segera diupdate dalam transaksi, tambahkan hint ini untuk mencegah deadlock dan phantom read.
  • activerecord-sqlserver-adapter untuk Rails — adapter ini menangani perbedaan sintaks T-SQL secara otomatis termasuk pagination, identity column, dan tipe data.
  • Sequel lebih mudah dari raw tiny_tds — Sequel menghasilkan T-SQL yang benar secara otomatis dan parameterize semua input, tanpa overhead ActiveRecord penuh.
  • Konvensi nama SQL Server berbeda — SQL Server secara tradisional menggunakan PascalCase untuk nama tabel dan kolom; sesuaikan kode Ruby dengan konvensi database yang kamu integrasikan.

← Sebelumnya: MySQL   Berikutnya: Oracle →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact