Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. where: shartlar obyekti
- 2.1 Taqqoslash operatorlari
- 2.2 AND, OR, NOT
- 2.3 Tuzoq: NOT va NULL
- 3. Matn bo'yicha qidiruv
- 3.1 contains va mode: "insensitive"
- 3.2 Tuzoq: % ekranlanmaydi
- 4. Relation filtrlari
- 4.1 some, none, every
- 4.2 Bitta bog'langan yozuv: is / isNot
- 5. Saralash
- 5.1 Bir nechta kalit va NULL o'rni
- 5.2 Relation soni bo'yicha
- 5.3 Matn bo'yicha saralash va collation
- 6. Sahifalash
- 6.1 Offset: skip va take
- 6.2 Cursor
- 7. Hisob: count, aggregate, groupBy, distinct
- 7.1 count va aggregate
- 7.2 groupBy va having
- 7.3 _count filtri bilan
- 7.4 Tuzoq: distinct xotirada
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi operator?
- 2-mashq (o'rta): filtrdan where yasash
- 3-mashq (qiyin): NULL'ni yo'qotmaslik
- 10. Loyiha qadami: PrismaOmbori.royxat
- 10.1 Filtr shartlari
- 10.2 Keyset sharti
- 10.3 royxat va soni
- 11. Real ishda
- Xulosa
- Manbalar
Prisma filtrlash: where, some/every/none, cursor pagination va groupBy
Qisqacha: Prisma'da filtr —
whereobyekti:{ 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) yokicursor(keyset). Hisob —count,aggregate,groupBy. Biz o'lchagan uchta tuzoq:NOTNULL qatorlarni yo'qotadi,contains%belgisini ekranlamaydi,distinctSQL'da emas, xotirada bajariladi.
Bu darsda
whereoperatorlari vaAND/OR/NOTbilan 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,groupByva_countbilan statistika olasiz.- NULL,
%vadistincttuzoqlarini 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:
await db.vazifa.findMany({
where: { egasiId: { in: [2n, 3n] }, izohlarSoni: { gte: 1 } },
select: { id: true },
orderBy: { id: "asc" },
});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
await db.vazifa.findMany({
where: {
egasiId: 1n,
OR: [
{ ustuvorlik: "yuqori" },
{ matn: { startsWith: "Bozor" } },
],
NOT: [{ bajarildi: true }],
},
select: { id: true },
orderBy: { id: "asc" },
});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 $5Prisma 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:
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 } } },
});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"
await db.vazifa.findMany({
where: {
egasiId: 1n,
matn: { contains: "bozor", mode: "insensitive" },
},
select: { id: true },
});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:
contains '%': 13
contains '\%': 0Prisma 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 yokipg_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:
// 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):
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:
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:
await db.vazifa.count({
where: { egasi: { is: { email: { endsWith: "@example.com" } } } },
});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
await db.vazifa.findMany({
where: { egasiId: 1n },
orderBy: [
{ muddat: { sort: "asc", nulls: "last" } },
{ id: "asc" },
],
take: 3,
select: { id: true, muddat: true },
});[{"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 $3Oxiriga id — barqaror tartib uchun: bir xil muddatli vazifalar har safar bir xil tartibda chiqsin (ORDER BY va LIMIT).
5.2 Relation soni bo'yicha
await db.foydalanuvchi.findMany({
orderBy: { vazifalar: { _count: "desc" } },
take: 3,
select: { ism: true, _count: { select: { vazifalar: true } } },
});[{"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
await db.vazifa.findMany({
where: { egasiId: 1n }, orderBy: { id: "asc" },
skip: 2, take: 3, select: { id: true },
});8,9,17
SQL: … ORDER BY "vazifalar"."id" ASC LIMIT $2 OFFSET $3Manfiy 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
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 },
});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 $4cursor — "shu yozuvdan boshla", skip: 1 — "uning o'zini tashla". SQL'ga qarang: Prisma kursor yozuvini ichki so'rov bilan topadi va >= qiladi.
Ikki cheklov:
- 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. - Prisma kursori noyob maydon (
id) talab qiladi.yaratilganVaqt DESC, id DESCkabi 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:
await db.vazifa.aggregate({
where: { egasiId: 1n },
_count: { _all: true, muddat: true },
_sum: { izohlarSoni: true },
_min: { yaratilganVaqt: true },
});{"_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
await db.vazifa.groupBy({
by: ["egasiId"],
_count: { _all: true },
having: { egasiId: { _count: { gt: 5 } } },
orderBy: { egasiId: "asc" },
});[{"_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 $2groupBy 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
await db.foydalanuvchi.findMany({
where: { id: { lte: 2n } },
select: {
ism: true,
_count: {
select: { vazifalar: { where: { bajarildi: false } } },
},
},
orderBy: { id: "asc" },
});[{"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
await db.vazifa.findMany({
where: { egasiId: 1n },
distinct: ["ustuvorlik"],
select: { ustuvorlik: true },
orderBy: { ustuvorlik: "asc" },
});[{"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 $2Natija 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 —
cursoryozuvni o'zi ham qaytaradi,skip: 1uni 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
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:
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
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):
9 + 4 = 139 = 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:
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
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):
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 matnicontainsda ekranlanganmi, katta jadvaldadistinct/skipbormi. - Intervyuda: "
everybo'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.NOTNULL qatorlarni yo'qotadi (4 + 1 ≠ 13) — NULL'niORbilan aniq qo'shing.some= EXISTS,none= NOT EXISTS,every= "mos kelmagani yo'q" — bo'sh ro'yxatda ham rost.contains%/_ni ekranlamaydi;distinctxotirada 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
- Prisma hujjati — Filtering and sorting: https://www.prisma.io/docs/orm/v7/prisma-client/queries/filtering-and-sorting (2026-10-25)
- Prisma hujjati — Relation queries,
some/every/none: https://www.prisma.io/docs/orm/v7/prisma-client/queries/relation-queries - Prisma hujjati — Pagination: https://www.prisma.io/docs/orm/v7/prisma-client/queries/pagination
- Prisma hujjati — Aggregation, grouping, and summarizing: https://www.prisma.io/docs/orm/v7/prisma-client/queries/aggregation-grouping-summarizing
- Chiqishlar — biz ishga tushirdik: PostgreSQL 17.6, Prisma 7.10.1, Node 24.21.0 (2026-10-25); o'lchov — kanon (PostgreSQL 18.6).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!