ProgramlamaVeri Mühendisi

SQL'de hiyerarşik (ağaçsal) verilerin etkili bir şekilde işlenmesi ve saklanması nasıl gerçekleştirilir? Bu amaçla hangi yöntemler mevcuttur ve görev için uygun olanı nasıl seçeriz?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap.

Hiyerarşik yapılarla çalışma, ilişkisel veritabanlarının klasik bir konusudur. Örnekler: dizinler, ağaçsal menüler, organizasyon yapılarını içerir.

Konuya Giriş: Veritabanları, tablodan oluşmuş bir modeldir. Ağaçsal verilere yönelik birkaç yaklaşım vardır ve her birinin kendine özgü artıları ve eksileri bulunmaktadır.

Sorun: Standart parent_id modeli, yavaş tekrarlayan SELECT'lere yol açar. Gerçek görevler (tüm çocukların aranması, yollar, alt ağaçların sayılması) optimizasyon gerektirir.

Çözüm:

  • Adjacency List — parent_id için basit bir referanstır.
  • Materialized Path — tüm yolu yansıtan bir dizedir.
  • Nested Sets — sol/sağ sınırların (left/right) depolanması, alt ağaçların kolayca elde edilmesini sağlar.
  • Closure Table — ağaçtaki tüm ilişkileri yansıtan ayrı bir ilişki tablosudur (from->to).

Materialized Path için örnek kod (PostgreSQL):

CREATE TABLE categories ( id SERIAL PRIMARY KEY, name TEXT, path TEXT ); -- Path'in saklanma örneği: '1/2/5/' (kök/alt kategori/mevcut) SELECT * FROM categories WHERE path LIKE '1/2/%'; -- 2'nin tüm çocukları

Nested Sets için örnek kod:

CREATE TABLE nested_categories ( id SERIAL PRIMARY KEY, name TEXT, lft INT NOT NULL, rgt INT NOT NULL ); -- Alt düğümler SELECT * FROM nested_categories WHERE lft > 2 AND rgt < 15;

Anahtar özellikler:

  • Adjacency List, basit ağaçsal yapılar (düşük derinlik) için uygundur.
  • Materialized Path — alt ağaçların hızlı çıkarımı, kolay uygulanabilir.
  • Nested Sets — tüm çocukların anında alınması, hızlı okuma sağlar ama modifikasyon zordur.

Kandırmaca Soruları.

Herhangi bir derinlikte tüm çocukları ele almak için sadece parent_id ile iç içe SELECT kullanabilir miyiz?

Bu, küçük derinlikler için çalışır. Rekürsif arama için ya rekürsif CTE (WITH RECURSIVE) ya da daha karmaşık şemalar gereklidir; çünkü basit JOIN'ler büyük sayıda sorguya ve kötü performansa yol açar.

Örnek:

WITH RECURSIVE cte AS ( SELECT id, parent_id, name FROM categories WHERE id = 1 UNION ALL SELECT c.id, c.parent_id, c.name FROM categories c INNER JOIN cte ON c.parent_id = cte.id ) SELECT * FROM cte;

Moleküler ağaç (Nested Sets) neden alt ağaçları hızlı çıkarıyor ama düğümlerin eklenmesi/silinmesi yavaş?

Ağacın değiştirilmesi gerektiğinde, lft/rgt'yi birçok satırda hemen değiştirmek gerekir (değişim adımı — ekleme/silmeden büyük tüm değerler). Okuma için ideal bir yaklaşım ama sık sık değişiklikler için başka yöntemler kullanılmalıdır.

Closure Table'ı ne zaman kullanmalıyız, parent_id veya materialized path yerine?

Closure Table, herhangi bir seviyedeki çocuklara sık sık yapılan sorgular ve düzenli ilişki hesaplamaları için mükemmel şekilde çalışır. Eksisi, daha fazla alan gerektirmesidir.

Örnek:

CREATE TABLE closure ( ancestor INT, descendant INT, depth INT );

Yaygın Hatalar ve Anti-Patternler

  • Hızlı iç içe yapı araması gerektiğinde hiyerarşiyi sadece parent_id ile saklamak.
  • Yardımcı işlevler olmaksızın yolları veya lft/rgt'yi manuel olarak yeniden hesaplamak.
  • Anahtar sütunların (parent_id/path/lft/rgt) indekslenmemesi.

Gerçek Hayat Örneği

Olumsuz Durum

Bir çevrimiçi mağazada kategoriler, parent_id aracılığıyla uygulanmıştır. Tüm iç içe yapılar elle yerleştirilmekte, alt kategorileri arama işlemi iç içe SELECT'lerle yapılmaktadır.

Artılar:

  • Basitlik, genişletilmiş destek gerektirmiyor.

Eksiler:

  • Performans 3-4 katmanlı iç içe yapılarla bile düşer.
  • Düğümlerin taşınma işlemleri - anlaşılmazdır, mantıksal hatalara yol açar.

Olumlu Durum

Materialized path veya closure table kullanılıyor. Tüm iç içe kategori sorguları anındadır, hesaplamalar grup scriptleri ile yapılmaktadır.

Artılar:

  • Ölçeklenebilirlik.
  • Hızlı iç içe seçimler.

Eksiler:

  • Ekstra planlama gerektirir.
  • Yapıdaki değişikliklerde yükü artırır.