[VER] Veri Analizi

Metraj Verisini Excel'den Kurtarmak: Poz Bazlı Veri Modeli Nasıl Kurulur

28 Haziran 2026 8 dk okuma

Metraj tablosu neden bir süre sonra elimizde patlar

Metraj (keşif) çalışması neredeyse her ofiste aynı şekilde başlar: bir Excel dosyası, birkaç sekme, sağa doğru uzayan sütunlar. İlk gün her şey yolundadır. Sorun, dosya büyüdükçe ve birden fazla kişi dokundukça başlar. Bir poz tanımını üç ayrı sekmede kopyala-yapıştır ile taşımışsınızdır; sonra tanım değişir ama sadece iki yerde güncellenir. metraj_final_v3_SON_gercek.xlsx gibi dosya isimleri çoğalır ve hangisinin güncel olduğu kimsenin bilmediği bir soruya dönüşür. Bir satır silinir, altındaki =TOPLA() formülü sessizce kırılır ve kimse haftalarca fark etmez. Poz numarası bir hücrede metin, diğerinde sayı olduğu için DÜŞEYARA boş döner.

Bunların hiçbiri Excel’in hatası değil; Excel bir hesap tablosu, veri tabanı değil. Metraj verisi ise özünde ilişkisel bir veridir: pozlar var, bu pozlara bağlı ölçü satırları var, ölçüler mahallere dağılıyor, hepsi de dönem dönem hakedişe giriyor. Bu ilişkileri düz bir tablonun içine hapsettiğimizde, veriyi değil sadece görüntüsünü tutmuş oluyoruz. Aşağıda bu veriyi poz bazlı ilişkisel bir modele nasıl taşıyacağımıza bakacağız.

Veriyi varlıklarına ayırmak

İlk iş, “bir metraj dosyası” dediğimiz şeyin aslında birbirinden farklı varlıkların (entity) toplamı olduğunu görmek. Kabaca dört tane var:

TabloAmaçÖnemli alanlar
pozBirim fiyat kütüphanesi (tanım tekil olarak burada durur)id (PK), poz_no, tanim, birim, birim_fiyat
mahalMekan/imalat kırılımıid (PK), ad, kat, blok
metraj_satiriTek bir ölçüm kaydı (formül + sonuç)id (PK), poz_id (FK), mahal_id (FK), formul, miktar
hakedis_kalemDönem bazlı gerçekleşen imalatid (PK), hakedis_id (FK), poz_id (FK), imalat_miktari

Buradaki kritik karar şu: poz tanımı sadece bir yerde, poz tablosunda yaşar. Metraj satırı poz tanımını kopyalamaz, ona poz_id ile referans verir. Böylece bir pozun tanımı ya da birim fiyatı değiştiğinde tek bir satırı güncellersiniz; ona bağlı yüzlerce metraj satırı otomatik olarak doğru referansı gösterir. Kopyala-yapıştır tutarsızlığının ilaç seviyesindeki çözümü budur.

Şemayı kurmak

SQLite üzerinde bu modeli kurmak birkaç CREATE TABLE ifadesinden ibaret. Yabancı anahtar denetimini açtığımıza dikkat edin; SQLite bunu varsayılan olarak kapalı tutar.

PRAGMA foreign_keys = ON;

CREATE TABLE poz (
    id          INTEGER PRIMARY KEY,
    poz_no      TEXT    NOT NULL UNIQUE,   -- örn. '15.150.1002'
    tanim       TEXT    NOT NULL,
    birim       TEXT    NOT NULL,          -- m3, m2, ton...
    birim_fiyat REAL
);

CREATE TABLE mahal (
    id    INTEGER PRIMARY KEY,
    ad    TEXT NOT NULL,                   -- 'Zemin kat ıslak hacim'
    kat   TEXT,
    blok  TEXT
);

CREATE TABLE metraj_satiri (
    id        INTEGER PRIMARY KEY,
    poz_id    INTEGER NOT NULL REFERENCES poz(id),
    mahal_id  INTEGER REFERENCES mahal(id),
    aciklama  TEXT,
    formul    TEXT,                        -- '4 * 3.20 * 0.25' gibi ham ifade
    miktar    REAL    NOT NULL,            -- formülün hesaplanmış sonucu
    CHECK (miktar >= 0)
);

CREATE INDEX idx_metraj_poz   ON metraj_satiri(poz_id);
CREATE INDEX idx_metraj_mahal ON metraj_satiri(mahal_id);

Dikkat çekmek istediğim iki detay var. Birincisi formul alanı: ölçünün nasıl elde edildiğini (4 * 3.20 * 0.25) ham metin olarak saklıyoruz, miktar ise bunun hesaplanmış sonucu. Excel’de formülü hücrenin içinde saklarız ve satır taşınınca referans kırılır; burada formül veriyle birlikte kayıtlı olduğu için denetlenebilir ve yeniden hesaplanabilir kalır. İkincisi CHECK ve REFERENCES kısıtları: veri tabanı, olmayan bir poza bağlı satır ya da negatif miktar girilmesini en baştan reddeder. Excel bu tür kuralları hatırlatmaz bile.

Normalizasyon: aynı bilgiyi iki kez yazmamak

Yaptığımız şeyin adı normalizasyon. Amacı, her olguyu (fact) yalnızca tek bir yerde tutmak. Klasik metraj Excel’i genelde 1NF’yi bile sağlamaz: tek bir hücreye “4 adet 3.20x0.25 kiriş” gibi birden çok değer sıkıştırılır. Modelimizde her ölçü kendi satırında, her alan atomik.

Asıl kazanç 3NF’de: poz tanımı yalnızca poz_no’ya bağlıdır, dolayısıyla metraj satırında tanim veya birim tekrar edilmez. Bu “geçişli bağımlılığı” (transitive dependency) kaldırmak, güncelleme anomalilerini bitirir. Bir pozun birimini m2’den m3’e çevirdiğinizde, ilgili tüm metraj satırlarını gezip düzeltmeniz gerekmez; tek satır değişir, geri kalan her şey JOIN ile tutarlı gelir:

SELECT p.poz_no, p.tanim, p.birim,
       SUM(m.miktar)                    AS toplam_miktar,
       SUM(m.miktar * p.birim_fiyat)    AS toplam_tutar
FROM   metraj_satiri m
JOIN   poz p ON p.id = m.poz_id
GROUP  BY p.id
ORDER  BY p.poz_no;

Bu tek sorgu, Excel’de sekmeler arası TOPLA.ÇARPIM ve DÜŞEYARA yığınıyla yapmaya çalıştığımız icmal tablosunun tamamını üretir ve satır sildiğinizde bozulmaz.

Neden SQLite

Bu iş için ağır bir sunucu veri tabanı kurmanız gerekmez. SQLite tam da bu senaryo için biçilmiş kaftan: veri tabanının tamamı tek bir dosyadır (metraj.db), sunucu süreci yoktur, kütüphane uygulamanın içine gömülüdür. Yani Excel dosyası kadar taşınabilir ama Excel gibi kırılgan değil. Aynı dosyayı e-postayla gönderebilir, sürüm kontrolüne koyabilir, yedekleyebilirsiniz; içindeki ilişkiler ve kısıtlar dosyayla birlikte gelir. Standart SQL desteklediği için de ileride PostgreSQL gibi bir sunucuya taşımak istediğinizde şemanız büyük ölçüde aynı kalır.

FTS5 ile poz araması

Poz kütüphaneleri binlerce kalem içerir ve mühendis genelde poz numarasını değil “şap”, “brüt beton”, “C30” gibi kelimeleri hatırlar. LIKE '%şap%' sorgusu hem yavaştır hem de kelime sırasına, eklere takılır. SQLite’ın FTS5 (Full-Text Search) modülü tam bu iş için var: tam metin dizini kurar, kelime bazlı arar ve alaka sırasına göre (bm25) sıralar.

En temiz kullanım, FTS tablosunu asıl poz tablosuyla senkron tutan “external content” desenidir. Dizin veriyi ikinci kez saklamaz; sadece poz tablosuna işaret eder ve tetikleyicilerle güncel kalır:

CREATE VIRTUAL TABLE poz_fts USING fts5(
    poz_no,
    tanim,
    content='poz',          -- veriyi 'poz' tablosundan okur
    content_rowid='id'
);

-- poz tablosundaki değişiklikleri dizine yansıtan tetikleyiciler
CREATE TRIGGER poz_ai AFTER INSERT ON poz BEGIN
    INSERT INTO poz_fts(rowid, poz_no, tanim)
    VALUES (new.id, new.poz_no, new.tanim);
END;

CREATE TRIGGER poz_ad AFTER DELETE ON poz BEGIN
    INSERT INTO poz_fts(poz_fts, rowid, poz_no, tanim)
    VALUES ('delete', old.id, old.poz_no, old.tanim);
END;

CREATE TRIGGER poz_au AFTER UPDATE ON poz BEGIN
    INSERT INTO poz_fts(poz_fts, rowid, poz_no, tanim)
    VALUES ('delete', old.id, old.poz_no, old.tanim);
    INSERT INTO poz_fts(rowid, poz_no, tanim)
    VALUES (new.id, new.poz_no, new.tanim);
END;

Dizin kurulduktan sonra arama tek satır. MATCH operatörü ve bm25() sıralaması ile en alakalı pozlar üste gelir:

SELECT p.poz_no, p.tanim
FROM   poz_fts f
JOIN   poz p ON p.id = f.rowid
WHERE  poz_fts MATCH 'beton AND C30'
ORDER  BY bm25(poz_fts)
LIMIT  20;

FTS5 AND, OR, NOT ve önek araması (bet*) gibi operatörleri destekler; yani “şap ama tesviye değil” tarzı sorgular doğrudan ifade edilebilir. Bu, poz seçiminde harcanan zamanı ve yanlış poz girme riskini gözle görülür biçimde azaltır.

Özet

  • Metraj verisi ilişkiseldir; onu düz bir Excel tablosuna sıkıştırmak sürüm karmaşasına, kopyala-yapıştır tutarsızlığına ve sessizce kırılan formüllere yol açar.
  • Veriyi poz, mahal, metraj_satiri ve hakedis_kalem gibi varlıklara ayırıp yabancı anahtarlarla ilişkilendirin; poz tanımını tek bir yerde tutmak (3NF) güncelleme anomalilerini bitirir.
  • Ölçü formülünü ham metin, sonucu ayrı bir sayısal alan olarak saklayın; CHECK ve REFERENCES kısıtları hatalı veriyi giriş anında engeller.
  • SQLite tek dosyalık, sunucusuz yapısıyla Excel kadar taşınabilir ama ilişkileri ve kuralları veriyle birlikte taşır; icmaller tek JOIN sorgusuyla ve kırılmadan üretilir.
  • Poz aramasını LIKE yerine FTS5 external-content dizini üzerine kurun; tetikleyicilerle güncel kalır, MATCH ve bm25() ile kelime bazlı, alakaya göre sıralı arama sağlar.