Metraj Verisini Excel'den Kurtarmak: Poz Bazlı Veri Modeli Nasıl Kurulur
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:
| Tablo | Amaç | Önemli alanlar |
|---|---|---|
poz | Birim fiyat kütüphanesi (tanım tekil olarak burada durur) | id (PK), poz_no, tanim, birim, birim_fiyat |
mahal | Mekan/imalat kırılımı | id (PK), ad, kat, blok |
metraj_satiri | Tek bir ölçüm kaydı (formül + sonuç) | id (PK), poz_id (FK), mahal_id (FK), formul, miktar |
hakedis_kalem | Dönem bazlı gerçekleşen imalat | id (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_satirivehakedis_kalemgibi 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;
CHECKveREFERENCESkı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
JOINsorgusuyla ve kırılmadan üretilir. - Poz aramasını
LIKEyerine FTS5 external-content dizini üzerine kurun; tetikleyicilerle güncel kalır,MATCHvebm25()ile kelime bazlı, alakaya göre sıralı arama sağlar.