Scarlet 1
Scarlet
emirhanHCL 1
emirhanHCL
R 1
Roksam
farkmt2official 1
farkmt2official
TuZaKK 1
TuZaKK
sermin 1
sermin
berkmenoo 1
berkmenoo
mannaxxx 1
mannaxxx
Agora Metin2 1
Agora Metin2
[DEV]AB 1
[DEV]AB
Hikaye Ekle

[NODE JS] Prisma ile İlişkisel Veri Modelleme

  • Konuyu başlatan Konuyu başlatan Silverhand
  • Başlangıç tarihi Başlangıç tarihi
  • Cevaplar Cevaplar 1
  • Görüntüleme Görüntüleme 9

Türkiye'de ilk Mobil & PC Aynı Anda Metin2 Oyna. Triarchonline kalıcı ve uzun ömürlü yapısı ile 24 Temmuz'da açılıyor. | 1-99 Mobil Metin2 Triarch HEMEN TIKLA!

nodejs.png


[NODE JS] Prisma ile İlişkisel Veri Modelleme​


Veri modelleme, herhangi bir uygulamanın temelini oluşturan kritik bir süreçtir. Doğru veri modeli, uygulamanızın performansını, ölçeklenebilirliğini ve bakım kolaylığını doğrudan etkiler. Prisma Schema Language ile veri modellemesi yapmak, hem okunabilir hem de güçlü bir deneyim sunar. Şema dosyanız, veritabanınızın tek gerçek kaynağı (single source of truth) haline gelir.

İlişkisel veri modellemenin üç temel ilişki türü vardır: bire bir (1:1), bire çok (1:N) ve çoka çok (N:M). Prisma'da bu ilişkileri tanımlamak son derece kolay ve anlaşılır. Her ilişki, ilgili modellerin her ikisinde de tanımlanmalıdır — Prisma buna "ilişki alanları" der.

Bire bir ilişkide, bir kaydın diğer tablodan sadece bir karşılığı vardır. Örneğin her kullanıcının tek bir profili olması. Prisma'da bunu @relation dekoratörü ve @unique kısıtlamasıyla yabancı anahtar alanına uygulayarak tanımlarız. İsteğe bağlı (optional) ilişkiler için türün sonuna soru işareti (?) eklenir.

Bire çok ilişki en yaygın olanıdır. Bir kategorinin birden fazla ürünü olması, bir yazarın birden fazla makalesi olması gibi. Prisma'da "bir" tarafında model türü dizisi (örneğin Urun[]), "çok" tarafında ise tek model türü ve yabancı anahtar alanı tanımlanır.

Çoka çok ilişkiler iki şekilde tanımlanabilir: örtük (implicit) ve açık (explicit). Örtük ilişkide Prisma ara tabloyu otomatik oluşturur. Açık ilişkide ise ara tabloyu kendiniz tanımlarsınız ve bu tabloya ek alanlar ekleyebilirsiniz. Ara tabloya ek veri eklemeniz gerekiyorsa (örneğin sipariş kalemi tablosundaki adet ve birim fiyat) açık ilişki kullanmalısınız.

Kendine referans veren (self-referencing) ilişkiler de Prisma'da desteklenir. Bir çalışanın yöneticisinin de bir çalışan olması, bir kategorinin alt kategorilerinin olması gibi durumlar bu şekilde modellenir. Bu tür ilişkilerde ilişki adlarının açıkça belirtilmesi gerekir.

JavaScript:
// Prisma ile İlişkisel Veri Modelleme

// ============================================
// prisma/schema.prisma - Kapsamlı İlişki Modelleri
// ============================================
/*
// === E-Ticaret Veri Modeli ===

// Kullanıcı - Temel kullanıcı bilgileri
model Kullanici {
  id              Int           @id @default(autoincrement())
  email           String        @unique
  ad              String
  soyad           String
  sifre           String
  rol             KullaniciRol  @default(MUSTERI)
  aktif           Boolean       @default(true)

  // Bire Bir: Kullanıcı - Profil
  profil          Profil?

  // Bire Çok: Kullanıcı - Adresler
  adresler        Adres[]

  // Bire Çok: Kullanıcı - Siparişler
  siparisler      Siparis[]

  // Bire Çok: Kullanıcı - Değerlendirmeler
  degerlendirmeler Degerlendirme[]

  // Bire Çok: Kullanıcı - Favoriler
  favoriler       Favori[]

  olusturmaTarihi DateTime      @default(now())
  guncellemeTarihi DateTime     @updatedAt

  @@map("kullanicilar")
}

// Profil - Bire bir ilişki
model Profil {
  id              Int       @id @default(autoincrement())
  biyografi       String?   @db.Text
  avatarUrl       String?
  telefon         String?
  dogumTarihi     DateTime?
  cinsiyet        Cinsiyet?

  // İlişki tanımı
  kullanici       Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
  kullaniciId     Int       @unique

  @@map("profiller")
}

// Adres - Bire çok ilişki
model Adres {
  id              Int       @id @default(autoincrement())
  baslik          String    // "Ev", "İş" gibi
  adSoyad         String
  telefon         String
  il              String
  ilce            String
  mahalle         String
  adresDetay      String    @db.Text
  postaKodu       String?
  varsayilan      Boolean   @default(false)

  kullanici       Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
  kullaniciId     Int

  siparisler      Siparis[]

  @@map("adresler")
}

// Kategori - Kendine referans (self-relation)
model Kategori {
  id              Int       @id @default(autoincrement())
  ad              String
  slug            String    @unique
  aciklama        String?
  gorselUrl       String?
  sira            Int       @default(0)
  aktif           Boolean   @default(true)

  // Kendine referans: Üst-alt kategori ilişkisi
  ustKategori     Kategori? @relation("KategoriHiyerarsisi", fields: [ustKategoriId], references: [id])
  ustKategoriId   Int?
  altKategoriler  Kategori[] @relation("KategoriHiyerarsisi")

  urunler         Urun[]

  @@map("kategoriler")
}

// Ürün
model Urun {
  id              Int       @id @default(autoincrement())
  ad              String
  slug            String    @unique
  aciklama        String    @db.Text
  fiyat           Decimal   @db.Decimal(10, 2)
  indirimOrani    Int       @default(0)
  stok            Int       @default(0)
  sku             String    @unique
  agirlik         Float?
  aktif           Boolean   @default(true)

  // Bire Çok: Kategori - Ürün
  kategori        Kategori  @relation(fields: [kategoriId], references: [id])
  kategoriId      Int

  // Bire Çok: Ürün - Görseller
  gorseller       UrunGorsel[]

  // Bire Çok: Ürün - Varyantlar
  varyantlar      UrunVaryant[]

  // Çoka Çok: Ürün - Etiketler (açık ilişki)
  etiketler       UrunEtiket[]

  // Sipariş kalemleri
  siparisKalemleri SiparisKalemi[]

  // Değerlendirmeler
  degerlendirmeler Degerlendirme[]

  // Favoriler
  favoriler       Favori[]

  olusturmaTarihi DateTime  @default(now())
  guncellemeTarihi DateTime @updatedAt

  @@map("urunler")
  @@index([kategoriId])
  @@index([fiyat])
}

// Ürün Görselleri
model UrunGorsel {
  id              Int       @id @default(autoincrement())
  url             String
  altMetin        String?
  sira            Int       @default(0)
  anaGorsel       Boolean   @default(false)

  urun            Urun      @relation(fields: [urunId], references: [id], onDelete: Cascade)
  urunId          Int

  @@map("urun_gorseller")
}

// Ürün Varyantları
model UrunVaryant {
  id              Int       @id @default(autoincrement())
  ad              String    // "Kırmızı - L", "Mavi - M"
  sku             String    @unique
  fiyatFarki      Decimal   @default(0) @db.Decimal(10, 2)
  stok            Int       @default(0)
  renk            String?
  beden           String?
  aktif           Boolean   @default(true)

  urun            Urun      @relation(fields: [urunId], references: [id], onDelete: Cascade)
  urunId          Int

  @@map("urun_varyantlar")
}

// Etiketler
model Etiket {
  id              Int       @id @default(autoincrement())
  ad              String    @unique
  slug            String    @unique

  urunler         UrunEtiket[]

  @@map("etiketler")
}

// Ürün-Etiket ara tablosu (açık çoka çok)
model UrunEtiket {
  urun            Urun      @relation(fields: [urunId], references: [id], onDelete: Cascade)
  urunId          Int
  etiket          Etiket    @relation(fields: [etiketId], references: [id], onDelete: Cascade)
  etiketId        Int

  @@id([urunId, etiketId])
  @@map("urun_etiketler")
}

// Sipariş
model Siparis {
  id              Int           @id @default(autoincrement())
  siparisNo       String        @unique @default(cuid())
  durum           SiparisDurumu @default(BEKLEMEDE)
  toplamTutar     Decimal       @db.Decimal(10, 2)
  kargoUcreti     Decimal       @default(0) @db.Decimal(10, 2)
  indirimTutari   Decimal       @default(0) @db.Decimal(10, 2)
  not             String?

  musteri         Kullanici     @relation(fields: [musteriId], references: [id])
  musteriId       Int

  teslimatAdresi  Adres         @relation(fields: [adresId], references: [id])
  adresId         Int

  kalemler        SiparisKalemi[]

  siparisTarihi   DateTime      @default(now())
  guncellemeTarihi DateTime     @updatedAt

  @@map("siparisler")
  @@index([musteriId])
  @@index([durum])
}

// Sipariş Kalemi - Açık çoka çok (Sipariş-Ürün arasında ek verili)
model SiparisKalemi {
  id              Int       @id @default(autoincrement())
  adet            Int
  birimFiyat      Decimal   @db.Decimal(10, 2)
  indirimOrani    Int       @default(0)

  siparis         Siparis   @relation(fields: [siparisId], references: [id], onDelete: Cascade)
  siparisId       Int

  urun            Urun      @relation(fields: [urunId], references: [id])
  urunId          Int

  @@map("siparis_kalemleri")
}

// Değerlendirme
model Degerlendirme {
  id              Int       @id @default(autoincrement())
  puan            Int       // 1-5
  yorum           String?   @db.Text
  onaylandi       Boolean   @default(false)

  kullanici       Kullanici @relation(fields: [kullaniciId], references: [id])
  kullaniciId     Int

  urun            Urun      @relation(fields: [urunId], references: [id])
  urunId          Int

  olusturmaTarihi DateTime  @default(now())

  @@unique([kullaniciId, urunId])
  @@map("degerlendirmeler")
}

// Favori
model Favori {
  kullanici       Kullanici @relation(fields: [kullaniciId], references: [id], onDelete: Cascade)
  kullaniciId     Int

  urun            Urun      @relation(fields: [urunId], references: [id], onDelete: Cascade)
  urunId          Int

  eklenmeTarihi   DateTime  @default(now())

  @@id([kullaniciId, urunId])
  @@map("favoriler")
}

enum KullaniciRol {
  MUSTERI
  SATICI
  ADMIN
}

enum Cinsiyet {
  ERKEK
  KADIN
  BELIRTILMEMIS
}

enum SiparisDurumu {
  BEKLEMEDE
  ONAYLANDI
  HAZIRLANIYOR
  KARGODA
  TESLIM_EDILDI
  IPTAL_EDILDI
  IADE_EDILDI
}
*/

// ============================================
// İlişkisel sorgulama örnekleri
// ============================================
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// Derin ilişki sorgulama
async function urunDetayGetir(urunSlug) {
  const urun = await prisma.urun.findUnique({
    where: { slug: urunSlug },
    include: {
      kategori: {
        include: {
          ustKategori: true
        }
      },
      gorseller: {
        orderBy: { sira: 'asc' }
      },
      varyantlar: {
        where: { aktif: true, stok: { gt: 0 } }
      },
      etiketler: {
        include: { etiket: true }
      },
      degerlendirmeler: {
        where: { onaylandi: true },
        include: {
          kullanici: {
            select: { ad: true, soyad: true }
          }
        },
        orderBy: { olusturmaTarihi: 'desc' },
        take: 10
      },
      _count: {
        select: {
          degerlendirmeler: true,
          favoriler: true
        }
      }
    }
  });

  if (!urun) {
    console.log('Ürün bulunamadı');
    return null;
  }

  // Ortalama puan hesaplama
  const puanOzeti = await prisma.degerlendirme.aggregate({
    where: { urunId: urun.id, onaylandi: true },
    _avg: { puan: true },
    _count: { puan: true }
  });

  console.log(`Ürün: ${urun.ad}`);
  console.log(`Kategori: ${urun.kategori.ad}`);
  console.log(`Fiyat: ${urun.fiyat} TL`);
  console.log(`Stok: ${urun.stok}`);
  console.log(`Varyant sayısı: ${urun.varyantlar.length}`);
  console.log(`Etiketler: ${urun.etiketler.map(e => e.etiket.ad).join(', ')}`);
  console.log(`Ortalama puan: ${puanOzeti._avg.puan || 'Henüz yok'}`);
  console.log(`Değerlendirme sayısı: ${puanOzeti._count.puan}`);
  console.log(`Favori sayısı: ${urun._count.favoriler}`);

  return urun;
}

// Sipariş oluşturma (Transaction ile)
async function siparisOlustur(musteriId, adresId, kalemler) {
  return await prisma.$transaction(async (tx) => {
    let toplamTutar = 0;

    // Stok kontrolü ve toplam hesaplama
    for (const kalem of kalemler) {
      const urun = await tx.urun.findUnique({
        where: { id: kalem.urunId }
      });

      if (!urun) throw new Error(`Ürün bulunamadı: ${kalem.urunId}`);
      if (urun.stok < kalem.adet) {
        throw new Error(`Yetersiz stok: ${urun.ad} (Mevcut: ${urun.stok}, İstenen: ${kalem.adet})`);
      }

      const birimFiyat = Number(urun.fiyat) * (1 - urun.indirimOrani / 100);
      toplamTutar += birimFiyat * kalem.adet;

      // Stok düşür
      await tx.urun.update({
        where: { id: urun.id },
        data: { stok: { decrement: kalem.adet } }
      });
    }

    // Kargo ücreti hesaplama
    const kargoUcreti = toplamTutar >= 500 ? 0 : 29.99;

    // Siparişi oluştur
    const siparis = await tx.siparis.create({
      data: {
        musteriId,
        adresId,
        toplamTutar,
        kargoUcreti,
        kalemler: {
          create: kalemler.map(k => ({
            urunId: k.urunId,
            adet: k.adet,
            birimFiyat: k.birimFiyat
          }))
        }
      },
      include: {
        kalemler: {
          include: { urun: { select: { ad: true } } }
        },
        musteri: { select: { ad: true, soyad: true, email: true } }
      }
    });

    console.log(`Sipariş oluşturuldu: ${siparis.siparisNo}`);
    console.log(`Müşteri: ${siparis.musteri.ad} ${siparis.musteri.soyad}`);
    console.log(`Toplam: ${toplamTutar + kargoUcreti} TL`);
    siparis.kalemler.forEach(k => {
      console.log(`  - ${k.urun.ad} x${k.adet}`);
    });

    return siparis;
  });
}

// Kategori ağacını getir (kendine referans)
async function kategoriAgaciGetir() {
  const anaKategoriler = await prisma.kategori.findMany({
    where: {
      ustKategoriId: null,
      aktif: true
    },
    include: {
      altKategoriler: {
        where: { aktif: true },
        include: {
          altKategoriler: {
            where: { aktif: true }
          },
          _count: { select: { urunler: true } }
        },
        orderBy: { sira: 'asc' }
      },
      _count: { select: { urunler: true } }
    },
    orderBy: { sira: 'asc' }
  });

  console.log('
Kategori Ağacı:');
  anaKategoriler.forEach(kat => {
    console.log(`${kat.ad} (${kat._count.urunler} ürün)`);
    kat.altKategoriler.forEach(alt => {
      console.log(`  └─ ${alt.ad} (${alt._count.urunler} ürün)`);
      alt.altKategoriler.forEach(altAlt => {
        console.log(`      └─ ${altAlt.ad}`);
      });
    });
  });

  return anaKategoriler;
}

async function main() {
  try {
    await urunDetayGetir('kablosuz-kulaklik');
    await kategoriAgaciGetir();
  } catch (hata) {
    console.error('Hata:', hata.message);
  } finally {
    await prisma.$disconnect();
  }
}

main();

Prisma ile ilişkisel veri modelleme, şema dosyanızı veritabanınızın merkezi tanım dosyası haline getirir. Bire bir, bire çok ve çoka çok ilişkilerin yanı sıra kendine referans, bileşik anahtarlar ve kademeli silme gibi gelişmiş özellikler de desteklenir. İyi tasarlanmış bir veri modeli, uygulamanızın sağlam temeller üzerinde yükselmesini sağlar ve ilerideki değişikliklere uyum sağlamayı kolaylaştırır.
 
Emeğiniz ve katkınız için teşekkür ederiz!
Konunuz gerekli incelemelerden geçerek onaylandı. Topluluğumuza katkılarınızın devamını bekliyoruz.
İyi forumlar! 🎉
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst