IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis24/65-dars18 daqiqa
Mundarija (36)

Prisma filtrlash: where, some/every/none, cursor pagination va groupBy

Qisqacha: Prisma'da filtr — where obyekti: { izohlarSoni: { gte: 1 }, OR: [...], NOT: {...} }. Relation bo'yicha — some (kamida bittasi), none (hech biri), every (hammasi; bo'sh ro'yxat uchun ham rost). Sahifalash — skip/take (offset) yoki cursor (keyset). Hisob — count, aggregate, groupBy. Biz o'lchagan uchta tuzoq: NOT NULL qatorlarni yo'qotadi, contains % belgisini ekranlamaydi, distinct SQL'da emas, xotirada bajariladi.

Bu darsda

  • where operatorlari va AND/OR/NOT bilan har qanday shartni yozasiz va uning SQL'ini o'qiysiz.
  • Relation filtrlarini (some, every, none, is) to'g'ri tanlaysiz.
  • Offset va cursor sahifalashni Prisma'da yozasiz.
  • count, aggregate, groupBy va _count bilan statistika olasiz.
  • NULL, % va distinct tuzoqlarini oldindan bilasiz.

Oldin bilishingiz kerak: Prisma Client CRUD, Sahifalash: offset va kursor, NULL mantiqi, Agregatlar, GROUP BY va HAVING, Keyset pagination.

1. Nega bu kerak?

25-qismda GET /v1/vazifalar ga ko'p imkoniyat berdik: ?bajarildi=false, ?teg=ish, ?q=bozor, saralash, kursorli sahifalash, jami. 26-qismda PgOmbori ularning hammasini qo'lda yig'ilgan SQL bilan bajardi (SQL injection darsidagi Parametrlar klassi).

Oldingi darsda PrismaOmbori ning ol metodini yozdik. Endi eng katta metod — royxat. Prisma'da SQL bo'laklarini yopishtirish shart emas: shartlar oddiy JavaScript obyektlari, ularni massivga yig'ib AND ga bersa bo'ladi. Lekin har obyekt qanday SQL'ga aylanishini bilish kerak — aks holda natija "deyarli to'g'ri" bo'lib chiqadi.

O'xshatish: bozorda sotuvchiga "qizil olma, kilosi 15 mingdan arzon, lekin Farg'onaniki emas" deysiz. Sotuvchi har so'zni bitta shartga aylantiradi. Prisma ham shunday — faqat "emas" so'zini u siz kutgandan boshqacha tushunishi mumkin.

Chiqishlarni biz ishga tushirdik: PostgreSQL 17.6 (holat 20 namunasi: 8 foydalanuvchi, 48 vazifa), Prisma 7.10.1, Node 24.21.0. SQL — log: query hodisasidan, "public". prefiksisiz.

2. where: shartlar obyekti

2.1 Taqqoslash operatorlari

Maydonga qiymat bersangiz — tenglik. Obyekt bersangiz — operator:

Prisma SQL
{ id: 5n } yoki { equals: 5n } = $1
{ not: 5n } <> $1
{ in: [...] } / { notIn: [...] } IN (…) / NOT IN (…)
{ lt, lte, gt, gte } <, <=, >, >=
{ contains, startsWith, endsWith } LIKE '%…%', '…%', '%…'

Bir obyektdagi bir nechta maydon — AND:

ts
await db.vazifa.findMany({
  where: { egasiId: { in: [2n, 3n] }, izohlarSoni: { gte: 1 } },
  select: { id: true },
  orderBy: { id: "asc" },
});
text
10,14,32
SQL: SELECT "vazifalar"."id" FROM "vazifalar" WHERE ("vazifalar"."egasi_id" IN ($1,$2) AND "vazifalar"."izohlar_soni" >= $3) ORDER BY "vazifalar"."id" ASC OFFSET $4

(Natijada faqat id'larni vergul bilan chiqardik.)

2.2 AND, OR, NOT

ts
await db.vazifa.findMany({
  where: {
    egasiId: 1n,
    OR: [
      { ustuvorlik: "yuqori" },
      { matn: { startsWith: "Bozor" } },
    ],
    NOT: [{ bajarildi: true }],
  },
  select: { id: true },
  orderBy: { id: "asc" },
});
text
1,23,48
SQL: … WHERE ("vazifalar"."egasi_id" = $1 AND ("vazifalar"."ustuvorlik" = CAST($2::text AS "ustuvorlik") OR "vazifalar"."matn"::text LIKE ($3 || '%')) AND (NOT "vazifalar"."bajarildi" = $4)) ORDER BY "vazifalar"."id" ASC OFFSET $5

Prisma qavslarni to'g'ri qo'ydi: OR alohida guruh. AND, OR, NOT massiv ham, bitta obyekt ham qabul qiladi.

2.3 Tuzoq: NOT va NULL

Azizning 13 ta vazifasi bor: 8 tasida muddat yo'q (NULL), 5 tasida bor. "Muddati 1-noyabrdan oldin bo'lmagan" vazifalarni sanaymiz:

ts
const sana = new Date("2026-11-01T00:00:00Z");
await db.vazifa.count({ where: { egasiId: 1n, muddat: null } });
await db.vazifa.count({
  where: { egasiId: 1n, muddat: { not: null } },
});
await db.vazifa.count({
  where: { egasiId: 1n, muddat: { lt: sana } },
});
await db.vazifa.count({
  where: { egasiId: 1n, NOT: { muddat: { lt: sana } } },
});
text
muddat null: 8
muddat not null: 5
muddat < sana: 4
NOT muddat < sana: 1
jami: 13
SQL: … WHERE ("vazifalar"."egasi_id" = $1 AND (NOT "vazifalar"."muddat" < $2)) …

4 + 1 = 5, 13 emas! NOT (NULL < sana) — NOT NULL — bu ham NULL, qator tushib qoladi (NULL mantiqi, uch qiymatli mantiq). Prisma NOT ni so'zma-so'z SQL'ga o'girdi va NULL'larni qo'shmadi. Muddatsiz vazifalar ham kerak bo'lsa — aniq yozing: OR: [{ muddat: null }, { muddat: { gte: sana } }] (3-mashqda tekshiramiz).

null ning o'zi esa to'g'ri ishlaydi: muddat: null → IS NULL, { not: null } → IS NOT NULL.

Tekshirib ko'ring: where: { muddat: { not: sana } } muddatsiz vazifalarni qaytaradimi?

Javob

Yo'q. SQL muddat <> $1 — NULL qatorda natija NULL, qator tushadi. Qoida: NULL bo'lishi mumkin bo'lgan ustunda "teng emas" va "NOT" bilan ishlaganda NULL holatini alohida OR bilan qo'shing.

3. Matn bo'yicha qidiruv

3.1 contains va mode: "insensitive"

ts
await db.vazifa.findMany({
  where: {
    egasiId: 1n,
    matn: { contains: "bozor", mode: "insensitive" },
  },
  select: { id: true },
});
text
SQL: … "vazifalar"."matn"::text LIKE ('%' || $2 || '%') …   (mode'siz)
SQL: … "vazifalar"."matn" ILIKE ('%' || $2 || '%') …        (insensitive)

mode: "insensitive" — ILIKE (katta-kichik harfsiz). Qiymat baribir parametr — injection yo'q.

3.2 Tuzoq: % ekranlanmaydi

Foydalanuvchi qidiruv maydoniga % yozdi:

text
contains '%': 13
contains '\%': 0

Prisma 7.10 % va _ ni ekranlamaydi: LIKE '%' || '%' || '%' — "istalgan matn", Azizning hamma 13 ta vazifasi chiqdi. Bu topilmani kanon ham testda ushlagan edi. Davosi 26-qismdagidek: \, %, _ ni o'zimiz ekranlaymiz (SQL injection, likeEscape). Loyiha qadamida aynan shu qator bor.

Maslahat: Katta jadvalda ILIKE '%…%' indeksni ishlatmaydi. Haqiqiy qidiruv — full-text search yoki pg_trgm (Postgres'da full-text search); Prisma'da ular raw SQL bilan (raw SQL darsi).

4. Relation filtrlari

4.1 some, none, every

Bitta foydalanuvchida ko'p vazifa bor. "Vazifalari orasida…" degan shart uchun uchta so'z:

ts
// kamida bitta izohli vazifasi bor
where: { vazifalar: { some: { izohlarSoni: { gt: 0 } } } }
// hamma vazifasi bajarilgan
where: { vazifalar: { every: { bajarildi: true } } }
// birorta ham yuqori ustuvorlikdagi vazifasi yo'q
where: { vazifalar: { none: { ustuvorlik: "yuqori" } } }

Avval bazaga vazifasi umuman yo'q yangi foydalanuvchi qo'shdik ("Yangi"). Natijalar (ismlarning birinchi so'zi):

text
some izoh: Aziz, Dilnoza, Sardor, Madina, Javohir, Gulnora, Bekzod
every bajarildi: Shahnoza, Yangi
none yuqori: Shahnoza, Yangi
every + some:

"Yangi"da bitta ham vazifa yo'q — lekin every bajarildi uni qaytardi! SQL'ga qarang:

text
SQL: … WHERE NOT EXISTS(SELECT "t0"."egasi_id" FROM "vazifalar" AS "t0" WHERE ((NOT "t0"."bajarildi" = $1) AND ("foydalanuvchilar"."id") = ("t0"."egasi_id") …))

every = "bajarilmagan vazifasi yo'q". Vazifasi yo'q odamda bajarilmagani ham yo'q — shart rost. Mantiqda buni "bo'sh to'plam uchun har qanday 'hammasi' rost" deyishadi. "Hamma vazifasini bajargan va kamida bitta vazifasi bor" kerak bo'lsa — every ga some: {} qo'shing. Oxirgi qatorda shu sinaldi: namunada bunday odam yo'q ekan — Shahnozaning ham vazifasi umuman yo'q, natija bo'sh.

flowchart TB
  S["some: shart"] --> S1["EXISTS (shartga mos bola)"]
  N["none: shart"] --> N1["NOT EXISTS (shartga mos bola)"]
  E["every: shart"] --> E1["NOT EXISTS (shartga MOS EMAS bola)"]
  E1 --> E2["bolasi yo'q ota ham o'tadi"]

Diagrammaga qarang: uchalasi ham EXISTS ichki so'roviga aylanadi (Subquery va LATERAL). every — ikki marta inkor: "mos kelmagani yo'q".

4.2 Bitta bog'langan yozuv: is / isNot

Vazifaning egasi bitta (to-one relation). Uning maydoni bo'yicha filtr — is:

ts
await db.vazifa.count({
  where: { egasi: { is: { email: { endsWith: "@example.com" } } } },
});
text
48
SQL: … FROM "vazifalar" LEFT JOIN "foydalanuvchilar" AS "j0" ON ("j0"."id") = ("vazifalar"."egasi_id") WHERE ("j0"."email"::text LIKE ('%' || $1) AND ("j0"."id" IS NOT NULL)) …

To-one filtr EXISTS emas, LEFT JOIN bo'ldi.

Tekshirib ko'ring: "Bitta ham izohsiz vazifasi yo'q" foydalanuvchilar — qaysi so'z?

Javob

vazifalar: { none: { izohlarSoni: 0 } } yoki vazifalar: { every: { izohlarSoni: { gt: 0 } } } — ikkalasi ham bir xil SQL beradi. Ikkalasi ham vazifasi umuman yo'q foydalanuvchini qaytaradi; uni chiqarish uchun some: {} qo'shing.

5. Saralash

5.1 Bir nechta kalit va NULL o'rni

ts
await db.vazifa.findMany({
  where: { egasiId: 1n },
  orderBy: [
    { muddat: { sort: "asc", nulls: "last" } },
    { id: "asc" },
  ],
  take: 3,
  select: { id: true, muddat: true },
});
text
[{"id":"28n","muddat":"2026-10-10T18:59:00.000Z"},{"id":"23n","muddat":"2026-10-20T13:00:00.000Z"},{"id":"34n","muddat":"2026-10-25T16:00:00.000Z"}]
SQL: … ORDER BY "vazifalar"."muddat"ASC NULLS LAST, "vazifalar"."id" ASC LIMIT $2 OFFSET $3

Oxiriga id — barqaror tartib uchun: bir xil muddatli vazifalar har safar bir xil tartibda chiqsin (ORDER BY va LIMIT).

5.2 Relation soni bo'yicha

ts
await db.foydalanuvchi.findMany({
  orderBy: { vazifalar: { _count: "desc" } },
  take: 3,
  select: { ism: true, _count: { select: { vazifalar: true } } },
});
text
[{"ism":"Aziz Karimov","_count":{"vazifalar":13}},{"ism":"Madina Yusupova","_count":{"vazifalar":9}},{"ism":"Javohir Olimov","_count":{"vazifalar":6}}]

SQL ichida ikkita LEFT JOIN (… GROUP BY egasi_id) bor — biri saralash, biri _count uchun. Katta jadvalda bunday so'rovni EXPLAIN bilan tekshiring (EXPLAIN ANALYZE).

5.3 Matn bo'yicha saralash va collation

Prisma orderBy da collation tanlab bo'lmaydi. Bazaning tartibi (ICU yoki en_US) JavaScript'ning < tartibidan farq qiladi: "anor" va "Olma" ni ular har xil joylashtiradi (ORDER BY va LIMIT). Kursor sahifalashda tartib bir xil bo'lishi shart — shuning uchun kanon matn bo'yicha saralashni raw SQL bilan (matn COLLATE "C") qiladi. Uni raw SQL darsida ko'ramiz.

6. Sahifalash

6.1 Offset: skip va take

ts
await db.vazifa.findMany({
  where: { egasiId: 1n }, orderBy: { id: "asc" },
  skip: 2, take: 3, select: { id: true },
});
text
8,9,17
SQL: … ORDER BY "vazifalar"."id" ASC LIMIT $2 OFFSET $3

Manfiy take: -3 — oxiridan uchta: Prisma tartibni teskari qilib (DESC) so'raydi va natijani o'zi qaytarib beradi: 44,45,48. Offset'ning muammolari (sekinlik, siljish) 25 va 26-qismlarda o'lchangan (Keyset pagination).

6.2 Cursor

ts
const s1 = await db.vazifa.findMany({
  where: { egasiId: 1n }, orderBy: { id: "asc" },
  take: 3, select: { id: true },
});
const oxirgi = s1.at(-1)?.id ?? 0n;
const s2 = await db.vazifa.findMany({
  where: { egasiId: 1n }, orderBy: { id: "asc" },
  cursor: { id: oxirgi }, skip: 1, take: 3, select: { id: true },
});
text
1,2,8 → 9,17,23
SQL: … WHERE ("vazifalar"."egasi_id" = $1 AND "vazifalar"."id" >= (SELECT "vazifalar"."id" FROM "vazifalar" WHERE ("vazifalar"."id") = ($2))) ORDER BY "vazifalar"."id" ASC LIMIT $3 OFFSET $4

cursor — "shu yozuvdan boshla", skip: 1 — "uning o'zini tashla". SQL'ga qarang: Prisma kursor yozuvini ichki so'rov bilan topadi va >= qiladi.

Ikki cheklov:

  1. Kursor yozuvi o'chirilsa — ichki so'rov hech narsa topmaydi va natija bo'sh. Biz cursor: { id: 999n } bilan sinadik — bo'sh massiv. Foydalanuvchi sahifalayotganda bitta vazifa o'chirilsa, "keyingi sahifa" yo'qolib qoladi.
  2. Prisma kursori noyob maydon (id) talab qiladi. yaratilganVaqt DESC, id DESC kabi ko'p kalitli tartibda kursor qiymatlarini o'zimiz saqlashimiz kerak.

Shuning uchun PrismaOmbori Prisma'ning cursor ini emas, 26-qismdagi keyset shartini ishlatadi: oxirgi qatorning saralash qiymatlarini kursorga yozib, k1 > x1 OR (k1 = x1 AND k2 > x2) shartini where obyekti qilib yasaydi. Bu o'chirilgan qatorga bog'liq emas («Loyiha qadami» bo'limi).

7. Hisob: count, aggregate, groupBy, distinct

7.1 count va aggregate

count ni oldingi bo'limlarda ishlatdik. aggregate — bir so'rovda bir nechta hisob:

ts
await db.vazifa.aggregate({
  where: { egasiId: 1n },
  _count: { _all: true, muddat: true },
  _sum: { izohlarSoni: true },
  _min: { yaratilganVaqt: true },
});
text
{"_count":{"_all":13,"muddat":5},"_sum":{"izohlarSoni":6},"_min":{"yaratilganVaqt":"2026-10-01T03:30:00.000Z"}}
SQL: SELECT COUNT("muddat") AS "_count$muddat", COUNT(*) AS "_count$_all", SUM("izohlar_soni") AS "_sum$izohlar_soni", MIN("yaratilgan_vaqt") AS "_min$yaratilgan_vaqt" FROM (SELECT … WHERE "vazifalar"."egasi_id" = $1 OFFSET $2) AS "sub"

_count: { muddat: true } — COUNT(muddat), NULL'larsiz: 5. _all — COUNT(*): 13.

7.2 groupBy va having

ts
await db.vazifa.groupBy({
  by: ["egasiId"],
  _count: { _all: true },
  having: { egasiId: { _count: { gt: 5 } } },
  orderBy: { egasiId: "asc" },
});
text
[{"_count":{"_all":13},"egasiId":"1n"},{"_count":{"_all":6},"egasiId":"2n"},{"_count":{"_all":6},"egasiId":"3n"},{"_count":{"_all":9},"egasiId":"4n"},{"_count":{"_all":6},"egasiId":"5n"}]
SQL: SELECT COUNT(*) AS "_count$_all", "vazifalar"."egasi_id" FROM "vazifalar" WHERE 1=1 GROUP BY "vazifalar"."egasi_id" HAVING COUNT("vazifalar"."egasi_id") > $1 ORDER BY "vazifalar"."egasi_id" ASC OFFSET $2

groupBy faqat o'z modelining ustunlari bo'yicha guruhlaydi. "Teg nomi bo'yicha, faqat o'chirilmagan vazifalarda" — uch jadval orqali — Prisma'da yo'q; kanon teglar() metodi uni raw SQL bilan qiladi.

7.3 _count filtri bilan

ts
await db.foydalanuvchi.findMany({
  where: { id: { lte: 2n } },
  select: {
    ism: true,
    _count: {
      select: { vazifalar: { where: { bajarildi: false } } },
    },
  },
  orderBy: { id: "asc" },
});
text
[{"ism":"Aziz Karimov","_count":{"vazifalar":7}},{"ism":"Dilnoza Rahimova","_count":{"vazifalar":3}}]

Bitta SQL — LEFT JOIN (… WHERE bajarildi = $1 GROUP BY egasi_id).

7.4 Tuzoq: distinct xotirada

ts
await db.vazifa.findMany({
  where: { egasiId: 1n },
  distinct: ["ustuvorlik"],
  select: { ustuvorlik: true },
  orderBy: { ustuvorlik: "asc" },
});
text
[{"ustuvorlik":"past"},{"ustuvorlik":"orta"},{"ustuvorlik":"yuqori"}]
SQL: SELECT "vazifalar"."id", "vazifalar"."ustuvorlik"::text FROM "vazifalar" WHERE "vazifalar"."egasi_id" = $1 ORDER BY "vazifalar"."ustuvorlik" ASC OFFSET $2

Natija to'g'ri — lekin SQL'da DISTINCT yo'q! Prisma Azizning hamma 13 qatorini oldi va takrorlarni JavaScript'da tashladi. 13 qatorda farq sezilmaydi; 500 000 qatorli jadvalda esa hammasi tarmoqdan o'tib xotiraga tushadi. Katta jadvalda noyob qiymatlar kerak bo'lsa — groupBy({ by: ["ustuvorlik"] }) (SQL'da GROUP BY) yoki raw SQL. Prisma 7 hujjatiga ko'ra previewFeatures = ["nativeDistinct"] flagi distinct ni bazaga o'tkazadi — lekin bu preview imkoniyat, biz uni sinamadik.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
NOT dan keyin qatorlar kam NULL — NOT NULL ham NULL OR: [{ x: null }, …]
every bo'sh ro'yxatli yozuvni qaytaradi "mos kelmagani yo'q" — rost some: {} qo'shing
qidiruv % bilan hammasini topadi contains ekranlamaydi \, %, _ ni ekranlang
keyingi sahifa bo'sh kursor yozuvi o'chirilgan keyset (qiymatlar bilan)
distinct sekin xotirada bajariladi groupBy yoki raw SQL
sahifalar orasida takror/yo'qolish orderBy da noyob kalit yo'q oxiriga id qo'shing

9. Mashqlar

Mashqlar kurs/mashqlar/27/24-prisma-filtr/ da. 2-mashq yechimi Node 24 da tekshirilgan (bazasiz — faqat where obyektini yasaydi); 3-mashqni biz bazada ishga tushirdik.

1-mashq (oson): qaysi operator?

  • "Ro'yxatdagi id'lardan biri" —
  • "Kamida bitta bolasi shartga mos" —
  • "Katta-kichik harfsiz" — mode: ""
  • Kursor bilan kursor yozuvining o'zini tashlash — skip:
Yechim
  • in — IN (…).
  • some — EXISTS.
  • insensitive — ILIKE.
  • 1 — cursor yozuvni o'zi ham qaytaradi, skip: 1 uni tashlaydi.

2-mashq (o'rta): filtrdan where yasash

shartlar(filtr, egasi) funksiyasini yozing. U massiv qaytarsin (keyin { AND: massiv } bo'ladi): har doim { egasiId, ochirilganVaqt: null }; bajarildi berilsa — shart; har teg uchun — vazifaTeglari: { some: { teg: { nom: "#teg" } } } (takror teg — bir marta); qidiruv — contains + insensitive, \, %, _ ekranlangan. egasi — son (bazasiz tekshirish uchun bigint emas).

Yechim
js
function shartlar(filtr, egasi) {
  const s = [{ egasiId: egasi, ochirilganVaqt: null }];
  if (filtr.bajarildi !== undefined) {
    s.push({ bajarildi: filtr.bajarildi });
  }
  for (const t of new Set(filtr.teglar ?? [])) {
    s.push({ vazifaTeglari: { some: { teg: { nom: `#${t}` } } } });
  }
  if (filtr.qidiruv !== undefined) {
    const soz = filtr.qidiruv.replace(/[\\%_]/g, "\\$&");
    s.push({ matn: { contains: soz, mode: "insensitive" } });
  }
  return s;
}

const w = shartlar({ teglar: ["ish", "ish"], qidiruv: "50%" }, 1);
console.log(w.length); // 3
console.log(JSON.stringify(w[2]));
console.log(shartlar({ bajarildi: false }, 2)[1]);

Konsolda:

text
3
{"matn":{"contains":"50\\%","mode":"insensitive"}}
{ bajarildi: false }

new Set takror tegni olib tashladi — shuning uchun 3 ta shart (asos, bitta teg, qidiruv). JSON'dagi \\% — bitta teskari chiziq va % (JSON teskari chiziqni ikkilab ko'rsatadi). Kanondagi filtrShartlari aynan shunday.

3-mashq (qiyin): NULL'ni yo'qotmaslik

Aziz (egasi 1) uchun "shoshilinch emas" vazifalarni sanang: muddati yo'q yoki muddati 2026-11-01 dan keyin. Natija "shoshilinch" (muddat < sana) bilan birga jami 13 ni bersin.

Yechim
ts
const sana = new Date("2026-11-01T00:00:00Z");

const shoshilmas = await db.vazifa.count({
  where: {
    egasiId: 1n,
    OR: [{ muddat: null }, { muddat: { gte: sana } }],
  },
});
const shoshilinch = await db.vazifa.count({
  where: { egasiId: 1n, muddat: { lt: sana } },
});
const jami = await db.vazifa.count({ where: { egasiId: 1n } });
console.log(shoshilmas, "+", shoshilinch, "=", jami);

Biz ishga tushirdik (PostgreSQL 17.6, Prisma 7.10.1):

text
9 + 4 = 13

9 = 8 ta muddatsiz + 1 ta 1-noyabrdan keyingi. NOT: { muddat: { lt: sana } } bilan esa 1 chiqqan edi — 8 tasi yo'qolgan.

10. Loyiha qadami: PrismaOmbori.royxat

Holat 20 davom etadi — hali commit qilmang. Bu darsda filtr, keyset va ro'yxat.

10.1 Filtr shartlari

Kanondagi filtrShartlari — 2-mashqning bigint va TypeScript turli shakli:

ts
type Shartlar = Prisma.VazifaWhereInput;

function filtrShartlari(filtr: Filtr, egasi: bigint): Shartlar[] {
  const s: Shartlar[] = [{ egasiId: egasi, ochirilganVaqt: null }];
  if (filtr.bajarildi !== undefined) {
    s.push({ bajarildi: filtr.bajarildi });
  }
  // AND: har teg uchun "shunday bog'lanish bor" (EXISTS)
  for (const t of new Set(filtr.teglar ?? [])) {
    s.push({ vazifaTeglari: { some: { teg: { nom: `#${t}` } } } });
  }
  // TOPILMA: Prisma 7 qiymatdagi % va _ ni EKRANLAMAYDI
  if (filtr.qidiruv !== undefined) {
    s.push({
      matn: {
        contains: filtr.qidiruv.replace(/[\\%_]/g, "\\$&"),
        mode: "insensitive",
      },
    });
  }
  if (filtr.yaratildiDan !== undefined) {
    s.push({ yaratilganVaqt: { gte: new Date(filtr.yaratildiDan) } });
  }
  if (filtr.yaratildiGacha !== undefined) {
    const gacha = new Date(filtr.yaratildiGacha);
    s.push({ yaratilganVaqt: { lt: gacha } });
  }
  return s;
}

Prisma.VazifaWhereInput — generatsiya qilingan tur: massivga noto'g'ri maydon qo'shib bo'lmaydi. 26-qismdagi PgOmbori teg filtrini count(*) = n bilan, qidiruvni strpos bilan qilgan edi; Prisma'da natija bir xil, yo'li boshqa — shuning uchun ikkala ombor bitta shartnoma testlaridan o'tishi muhim.

10.2 Keyset sharti

ts
const MAYDON = {
  id: "id", tartib: "tartib", matn: "matn", bajarildi: "bajarildi",
  yaratildi: "yaratilganVaqt", yangilandi: "yangilanganVaqt",
} as const;

/** k1 > x1 OR (k1 = x1 AND k2 > x2) … — Prisma where obyekti */
function keyset(
  k: readonly Saralash[],
  keyin: KursorQiymati,
): Shartlar {
  if (keyin.length !== k.length) {
    throw new Error("kursor kalitlari soni saralashga mos emas");
  }
  const yoki: Shartlar[] = [];
  for (let i = 0; i < k.length; i++) {
    const va: Shartlar[] = [];
    for (let j = 0; j < i; j++) {
      const s = k[j];
      const x = keyin[j];
      if (s === undefined || x === undefined) continue;
      va.push({
        [MAYDON[s.maydon]]: { equals: prismaQiymat(s.maydon, x) },
      });
    }
    const s = k[i];
    const x = keyin[i];
    if (s === undefined || x === undefined) continue;
    va.push({
      [MAYDON[s.maydon]]: {
        [s.kamayish ? "lt" : "gt"]: prismaQiymat(s.maydon, x),
      },
    });
    yoki.push({ AND: va });
  }
  return { OR: yoki };
}

SQL'da bu (a, b) > (x, y) row value bilan qisqa yozilardi (Keyset pagination); Prisma row value'ni bilmaydi, shuning uchun yoyilgan shakl. MAYDON — API nomini Prisma maydoniga bog'laydigan oq ro'yxat. prismaQiymat kursordagi qiymatni turga qaytaradi: id → BigInt, sana → Date.

10.3 royxat va soni

Matnsiz saralash uchun (matn — raw SQL, keyingi darslarda):

ts
const k = kalitlar(sorov.saralash); // oxiriga doim id
const asos = filtrShartlari(sorov.filtr, this.#egasi);
const shart = [...asos];
const { sahifa } = sorov;
if (sahifa.tur === "kursor" && sahifa.keyin !== undefined) {
  shart.push(keyset(k, sahifa.keyin));
}
const qatorlar = await this.#db.vazifa.findMany({
  where: { AND: shart },
  orderBy: k.map((s) => ({
    [MAYDON[s.maydon]]: s.kamayish ? "desc" : "asc",
  })),
  take: sorov.limit + 1,
  skip: sorov.sahifa.tur === "offset" ? sorov.sahifa.offset : 0,
  select: TANLOV,
});
const borKeyingi = qatorlar.length > sorov.limit;
// … vazifalar, keyingi kursor, versiya
if (sorov.jami === true) {
  natija.jami = await this.#db.vazifa.count({ where: { AND: asos } });
}

take: limit + 1 — "yana bormi?" ni bilish uchun bitta ortiqcha qator (25-qismdagi naqsh). jami — asos bilan, kursorsiz: hammasi qancha. soni(filtr) metodi ham shu count ning o'zi.

Kanon o'lchovi (katta bazasi, 500 000 vazifa, egasi 77, mediana 50, PostgreSQL 18.6): ro'yxat 50 — pg 3.65 ms, prisma 4.43 ms; teg filtri 7.33 / 8.36 ms; qidiruv 5.85 / 6.48 ms. Farq — millisekund ulushlari; tanlov tezlik emas, qulaylik va xavfsizlik bo'yicha qilinadi (ORM, query builder va raw SQL).

11. Real ishda

  • Admin panel va API ro'yxatlarining 90 % i — shu dars: filtr, saralash, sahifalash, son.
  • Kod review'da: NULL bo'lishi mumkin ustunda NOT/not, foydalanuvchi matni contains da ekranlanganmi, katta jadvalda distinct/skip bormi.
  • Intervyuda: "every bo'sh ro'yxatda nima qaytaradi?", "offset va cursor farqi?", "Prisma cursor qanday SQL beradi?"

Xulosa

  • where — obyekt: maydon → qiymat yoki operator; AND/OR/NOT — massiv yoki obyekt.
  • NOT NULL qatorlarni yo'qotadi (4 + 1 ≠ 13) — NULL'ni OR bilan aniq qo'shing.
  • some = EXISTS, none = NOT EXISTS, every = "mos kelmagani yo'q" — bo'sh ro'yxatda ham rost.
  • contains %/_ ni ekranlamaydi; distinct xotirada bajariladi.
  • Cursor — >= (SELECT …), o'chirilgan kursorda bo'sh; loyihada keyset qiymatlari bilan.
  • aggregate, groupBy (+having), _count — bitta SQL; relation orqali guruhlash — raw SQL.

Keyingi dars: Prisma: nested write va tranzaksiyalar — vazifa va izohlarni birga yaratish, $transaction, izolyatsiya va atomik increment.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Prisma filtrlash: where, some/every/none, cursor pagination va groupBy — IlmHamroh