Interview Handbook by Sayed Elmahdy English

المحور 10 · الداتا وقواعد البيانات

SQL وقواعد البيانات

Joins، و GROUP BY، و Window functions، و Indexes، و Normalization، و ACID، و NoSQL، وتمارين queries.

24 سؤالJunior · 8Mid · 10Senior · 612 اختيارات

المفاهيم

هنفهم إيه؟ هنستخدم لوصف البيانات والعلاقات، ثم نكتب queries تجيب المطلوب من غير نتائج ناقصة أو مضاعفة. المحور ينتقل من keys وconstraints إلى joins وaggregates وindexes وexecution plans، عشان تربط شكل السؤال بتصميم الجداول وطريقة التنفيذ.

قبل ما تبدأ: الجدول يخزن صفوفاً، وكل صف يمثل record، والعمود خاصية مثل السعر. الـ primary key يميز الصف، والـ foreign key يربطه بصف في جدول آخر. لو دي أول مرة، ابدأ بأول كارت ونفذ ذهنياً ما يحدث للصفوف.

المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.

#Keys و constraints وأنواع البياناتKeys, constraints and data types

validation في لوحدها مش بتحمي الداتا من script أو request متزامنة. الـ database constraints هي آخر خط دفاع للعلاقات والقيم الفريدة.

-- SQL Server / T-SQL unless a block explicitly says PostgreSQL.
CREATE TABLE Customers (
    Id int IDENTITY PRIMARY KEY,                         -- (1)
    Name nvarchar(100) NOT NULL,
    Email nvarchar(256) NOT NULL UNIQUE
);
CREATE TABLE Orders (
    Id int IDENTITY PRIMARY KEY,
    CustomerId int NOT NULL REFERENCES Customers(Id),    -- (2)
    CreatedAt datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
    Status varchar(20) NOT NULL,
    Total decimal(18,2) NOT NULL CHECK (Total >= 0)
);
CREATE TABLE OrderLines (
    OrderId int NOT NULL REFERENCES Orders(Id),
    LineNo int NOT NULL,
    ProductId int NOT NULL,
    Quantity int NOT NULL CHECK (Quantity > 0),
    PRIMARY KEY (OrderId, LineNo)                         -- (3)
);
🧾 شرح الكود
  1. (1) primary key واحد للجدول، ممكن مركب، وفريد ومن غير . candidate key أي minimal set يعرّف الصف؛ اللي مش مختارة اسمها alternate key. ممكن فيها أعمدة زيادة.
  2. (2) foreign key بتفرض وجود referenced candidate key سواء PK أو UNIQUE؛ مش بتعمل index تلقائي للعمود التابع في Server. DEFAULT قيمة عند عدم إرسال العمود، و CHECK مش بديل NOT NULL.
  3. (3) composite key من أكتر من عمود. surrogate key زي Id مستقرة؛ natural key ليها معنى زي email وممكن تتغير، فراجع trade-offs.

SQL Server unique single-column constraint يسمح عادةً بـ NULL واحدة؛ PostgreSQL افتراضياً يسمح بأكتر من NULL، وفيه NULLS NOT DISTINCT لتغيير ده. CHAR ثابت الطول، و VARCHAR متغير وبيستخدم encoding حسب collation؛ -8 collations تقدر تخزن Unicode. NVARCHAR بيستخدم UTF-16؛ مش كل character بيستهلك بالضرورة 2 bytes. للفلوس استخدم decimal وحدد precision و rounding policy.

#كل الـ joins على dataset واحدةEvery join on one sample dataset

join بيركب صفوف حسب علاقة، وممكن يكرر صف الشمال لما يلاقي أكتر من match. الداتا دي staging تعليمية من غير : order 13 فيها customer غير موجود؛ الـ FK في التصميم اللي فوق كانت هتمنع ده.

CREATE TABLE #Customers (Id int, Name varchar(20));
CREATE TABLE #Orders (Id int, CustomerId int, Total decimal(10,2));
INSERT INTO #Customers VALUES (1,'Ali'),(2,'Mona'),(3,'Omar');
INSERT INTO #Orders VALUES (10,1,500),(11,1,200),(12,2,300),(13,99,50);

-- (1) Matched rows only.
SELECT c.Name, o.Id FROM #Customers c
INNER JOIN #Orders o ON o.CustomerId = c.Id;
-- Ali 10; Ali 11; Mona 12

-- (2) Preserve the left side.
SELECT c.Name, o.Id FROM #Customers c
LEFT JOIN #Orders o ON o.CustomerId = c.Id;
-- Ali 10; Ali 11; Mona 12; Omar NULL

-- (3) Preserve the right side.
SELECT c.Name, o.Id FROM #Customers c
RIGHT JOIN #Orders o ON o.CustomerId = c.Id;
-- Ali 10; Ali 11; Mona 12; NULL 13

-- (4) Preserve both sides.
SELECT c.Name, o.Id FROM #Customers c
FULL OUTER JOIN #Orders o ON o.CustomerId = c.Id;
-- Ali 10; Ali 11; Mona 12; Omar NULL; NULL 13

-- (5) Every combination: 3 customers x 4 orders = 12 rows.
SELECT c.Id AS CustomerId, o.Id AS OrderId
FROM #Customers c CROSS JOIN #Orders o;

-- (6) Self join: unordered pairs of orders belonging to the same customer.
SELECT a.Id, b.Id FROM #Orders a JOIN #Orders b
ON a.CustomerId = b.CustomerId AND a.Id < b.Id;
-- 10 11

-- (7) Anti-join: customers without any order.
SELECT c.Name FROM #Customers c LEFT JOIN #Orders o
ON o.CustomerId = c.Id WHERE o.Id IS NULL;
-- Omar
🧾 شرح الكود
  1. (1)–(4) الفرق مين بيحتفظ بالـ unmatched rows؛ بتملأ الطرف الغايب. النتائج هنا sets للتوضيح؛ مفيش ترتيب مضمون من غير ORDER BY.
  2. (5) CROSS مفيهاش relationship predicate؛ مفيدة لكل combinations لكن حجمها ممكن ينفجر.
  3. (6) self join مش operator مختلف؛ نفس الجدول بـ aliases. شرط Id يمنع نفس الصف والزوج العكسي.
  4. (7) اختبار NULL على key غير nullable في الداتا الفعلية يكشف غياب match؛ NOT EXISTS غالباً أوضح.
بعد LEFT JOIN، WHERE o.Total > 100 بيشيل unmatched customers لأن المقارنة مع NULL بتطلع . حط فلتر اليمين في ON لو عايز تحتفظ بكل العملاء.

#Logical query order و WHERE و HAVINGLogical query order, WHERE and HAVING

فهم ترتيب المنطقي يفسّر ليه alias مش متاحة في WHERE. ده ترتيب المعنى، مش ترتيب physical execution؛ optimizer يقدر يعيد ترتيب الشغل مع الحفاظ على النتيجة.

FROM / JOIN / ON -> WHERE -> GROUP BY -> HAVING
-> SELECT -> DISTINCT -> ORDER BY -> paging / limiting
SELECT CustomerId, COUNT(*) AS OrderCount, SUM(Total) AS Revenue
FROM Orders
WHERE Status = 'Paid' AND CreatedAt >= '20260101'         -- (1)
  AND CreatedAt < '20270101'
GROUP BY CustomerId
HAVING SUM(Total) > 1000                                -- (2)
ORDER BY Revenue DESC, CustomerId;                      -- (3)
🧾 شرح الكود
  1. (1) WHERE بتفلتر rows قبل التجميع؛ range نص مفتوح يمنع مشاكل آخر اليوم.
  2. (2) HAVING بتفلتر groups بعد حساب . في SQL Server كل selected non-aggregate column لازم GROUP BY؛ تعبيرات مبنية عليها مسموحة.
  3. (3) Revenue alias اتعرّفت في SELECT فمتاحة هنا. tie-breaker يثبت الترتيب.

COUNT(*) يعد الصفوف، COUNT(column) يعد غير ، و COUNT(DISTINCT column) يعد القيم المختلفة غير NULL. SUM بتتجاهل NULL وبترجع NULL لو مفيش values؛ COALESCE لما business semantics عايزة صفر.

#Subqueries و EXISTS و recursive CTEsSubqueries, EXISTS and recursive CTEs

لما سؤال محتاج نتيجة سؤال تاني، subquery بتعبّر عن الاعتماد. بتسمي خطوة وتوضح القراءة؛ مش جدول temporary متخزّن تلقائي.

SELECT Id, Total FROM Orders
WHERE Total > (SELECT AVG(Total) FROM Orders);           -- (1)

SELECT c.Id FROM Customers c
WHERE NOT EXISTS (
    SELECT 1 FROM Orders o WHERE o.CustomerId = c.Id
);                                                     -- (2)

-- Categories(Id, ParentId, Name); roots have ParentId NULL.
WITH Tree AS (
    SELECT Id, ParentId, Name, 0 AS Depth
    FROM Categories WHERE ParentId IS NULL              -- (3)
    UNION ALL
    SELECT c.Id, c.ParentId, c.Name, t.Depth + 1
    FROM Categories c JOIN Tree t ON c.ParentId = t.Id   -- (4)
)
SELECT Id, Name, Depth FROM Tree OPTION (MAXRECURSION 100);
🧾 شرح الكود
  1. (1) scalar subquery لازم ترجع قيمة واحدة؛ هنا average.
  2. (2) correlated predicate بيربط بالعميل الحالي. EXISTS يسأل عن وجود match؛ IN يسأل عن membership. optimizer ممكن يحوّل الاتنين لنفس semi-join، فمفيش قاعدة إن EXISTS أسرع دايماً.
  3. (3) anchor للـ recursive CTE؛ صالحة للـ statement اللي بعدها بس.
  4. (4) recursive member يجيب أولاد النتيجة السابقة. حد recursion مش cycle detection؛ امنع cycles أو تتبع path، وماتفتحش recursion بلا حدود عشوائي.
لو subquery في NOT IN فيها ، nonmatches بتبقى وتتفلتر. NOT EXISTS أو استبعاد NULL صراحةً حسب المعنى أوضح. correlated query مش لازم تتنفذ physically مرة لكل row؛ شوف execution plan.

#Window functions من غير فقد الصفوفWindow functions without collapsing rows

عايز أحدث order لكل عميل، أو running total، مع الاحتفاظ بكل صف. GROUP BY بتختزل الصفوف؛ window functions بتحسب حوالين الصف الحالي.

SELECT Id, CustomerId, Total,
    ROW_NUMBER() OVER (
        PARTITION BY CustomerId ORDER BY CreatedAt, Id) AS rn, -- (1)
    RANK() OVER (ORDER BY Total DESC) AS rnk,
    DENSE_RANK() OVER (ORDER BY Total DESC) AS dense_rnk,  -- (2)
    SUM(Total) OVER (
        PARTITION BY CustomerId ORDER BY CreatedAt, Id
        ROWS UNBOUNDED PRECEDING) AS RunningTotal,        -- (3)
    LAG(Total) OVER (
        PARTITION BY CustomerId ORDER BY CreatedAt, Id) AS PreviousTotal,
    LEAD(Total) OVER (
        PARTITION BY CustomerId ORDER BY CreatedAt, Id) AS NextTotal,
    100.0 * Total / NULLIF(SUM(Total) OVER (), 0) AS PercentOfTotal
FROM Orders;
🧾 شرح الكود
  1. (1) numbering لكل customer. ORDER BY جوه OVER مش بيضمن ترتيب عرض النتيجة.
  2. (2) totals زي 500 و400 و400 و300: RANK يدي 1 و2 و2 و4، و DENSE_RANK يدي 1 و2 و2 و3. إضافة Id هنا هتلغي ties لو المقصود rank بالقيمة.
  3. (3) ROWS frame بيحدد التراكم row-by-row بترتيب حتمي؛ من غير frame صريحة ممكن peers يدخلوا مع بعض حسب default RANGE.

فلتر rn في outer query أو ؛ مش WHERE في نفس المستوى. الـ sort والـ memory grant ممكن يكونوا غاليين؛ index مناسب يقلل الكلفة.

#UNION و DDL و database programmabilityUNION, DDL and database programmability

اختيار command غلط ممكن يمسح بدل rows، أو يضيف deduplication مش مطلوب.

الأداةالسلوك في Server
UNION / UNION ALLدمج result sets بنفس عدد أعمدة وأنواع متوافقة؛ UNION يشيل duplicate rows، و ALL يحتفظ بيها
DELETEصفوف بشرط WHERE اختياري؛ row deletions logged، triggers تشتغل، و identity تفضل
TRUNCATEكل الصفوف عادةً بإلغاء تخصيص pages؛ مفيش WHERE ولا DELETE triggers، identity reset، وقيود تمنعه في حالات شائعة؛ self-reference استثناء
DROPإزالة object definition والداتا والـ indexes التابعة
Viewquery محفوظة؛ مش مخزنة افتراضياً. indexed view تخزن نتيجة بقيود وشروط وتكلفة كتابة
Procedurestatements و DML و و output params و result sets؛ تتنده بـ EXEC
Functionscalar أو table result، تتستخدم في queries؛ SQL Server UDF مابتعدّلش database state
Triggerمنطق تلقائي AFTER أو INSTEAD OF؛ لازم set-based لأن inserted و deleted ممكن فيهم صفوف كتير
CREATE OR ALTER VIEW dbo.PaidOrderTotals AS             -- (1)
SELECT CustomerId, SUM(Total) AS Revenue
FROM dbo.Orders WHERE Status = 'Paid'
GROUP BY CustomerId;
-- Run as a separate batch:
SELECT CustomerId, Revenue FROM dbo.PaidOrderTotals;

BEGIN TRANSACTION;
TRUNCATE TABLE dbo.ImportStaging;                       -- (2)
ROLLBACK TRANSACTION;
🧾 شرح الكود
  1. (1) view بتخفي تفاصيل query، لكنها مش cache. simple views ممكن update بشروط؛ aggregated view دي مش update مباشر عادي.
  2. (2) يفترض staging table موجودة ومن غير موانع truncate. في SQL Server TRUNCATE logged بالقدر اللازم للـ rollback، وينفع تتراجع جوه transaction؛ مش unlogged سحري.

stored procedures تقلل round trips وتتيح permissions محددة، بس محتاجة versioning و tests. cached plans مش حكر عليها؛ parameterized SQL كمان. scalar UDF لكل row ممكن تكلف، لكن inlining المدعوم يغير الأداء؛ قيس. triggers مفيدة للـ audit بس تزود hidden behavior و lock duration.

#Normalization من 1NF لـ BCNFNormalization from 1NF to BCNF

تكرار current customer address في كل order بيعمل update anomalies. normalization بتنظم facts حسب dependencies، مش بس عدد الجداول.

Raw: Orders(OrderId, CustomerId, CustomerCity, Products="Mug,Pen")
1NF: Orders + OrderLines(OrderId, ProductId, ProductName, Qty)
2NF: Products(ProductId, ProductName) + OrderLines without ProductName
3NF: Customers(CustomerId, CurrentCity) + Orders without CurrentCity
Snapshot: OrderLines(UnitPriceAtSale) and Orders(ShippingAddressAtPurchase)
  1. : قيم مفردة مناسبة للـ domain ومفيش repeating groups أو list منتجات في field واحدة.
  2. : 1NF ومفيش non-prime attribute بيعتمد على جزء صحيح من candidate key. مثال أوضح: key للسطر (OrderId, ProductId) لو المنتج بيتكرر مرة واحدة؛ ProductName يعتمد على ProductId بس.
  3. : لكل nontrivial dependency X → A، إما X أو A prime attribute. في المثال CustomerId → CurrentCity تنقل لجدول Customers.
  4. : لكل nontrivial dependency، determinant لازم superkey. أقوى من 3NF اللي بتسمح باستثناء prime attributes.

مثال BCNF: ProductSupplier(ProductId, SupplierId, AgentId)، مع (ProductId, SupplierId) → AgentId و AgentId → SupplierId. keys هي (ProductId, SupplierId) و (ProductId, AgentId). الـ AgentId مش superkey، مع إن SupplierId prime؛ العلاقة ممكن تحقق 3NF وتخالف BCNF. فصل AgentSupplier(AgentId, SupplierId) و ProductAgent(ProductId, AgentId) lossless، لكن حفظ كل dependencies ممكن يحتاج check إضافي.

denormalization زي summary table تحسن reads مقابل تكلفة sync. سعر البيع وعنوان الشحن وقت الشراء facts تاريخية مختلفة عن القيم الحالية؛ مش duplicate لازم يتحدّث مع كل تغيير. ماتعملش joins أكتر لمجرد الوصول لأكبر normal form من غير فهم constraints والـ workload.

#ACID و isolation و transactions في .NETACID, isolation and transactions in .NET

order و inventory لازم يتغيروا مع بعض، ومنافسين مايكسرشوا stock . Atomicity كل التغيير أو مفيش؛ Consistency احترام constraints والـ business invariant؛ يحدد التداخل؛ Durability بقاء commit حسب ضمانات storage والـ configuration.

using Microsoft.Data.SqlClient;
using System.Data;

await using var conn = new SqlConnection(connectionString);
await conn.OpenAsync(ct);
await using var tx = (SqlTransaction)await conn.BeginTransactionAsync(ct); // (1)
await using var cmd = conn.CreateCommand();
cmd.Transaction = tx;
cmd.CommandText = "UPDATE Products SET Available = Available - @qty " +
    "WHERE Id = @id AND Available >= @qty";
cmd.Parameters.Add("@id", SqlDbType.Int).Value = productId;
cmd.Parameters.Add("@qty", SqlDbType.Int).Value = quantity;  // (2)
if (quantity <= 0) throw new ArgumentOutOfRangeException(nameof(quantity));
if (await cmd.ExecuteNonQueryAsync(ct) != 1)
    throw new InvalidOperationException("Reservation failed");
// Insert the order using this same connection and transaction.
await tx.CommitAsync(ct);                                 // (3)
🧾 شرح الكود
  1. (1) على connection واحدة؛ أي command لازم تتربط بيها. Dapper بتبعت transaction parameter لكل command. disposal قبل commit بيرجع التغييرات غير المعتمدة.
  2. (2) parameters بأنواع محددة و atomic predicate يمنع overselling. مثال Products هنا يفترض Available موجودة في الفعلية.
  3. (3) commit للـ بس؛ مش بيشمل gateway أو email. SaveChanges واحدة transactional؛ لو عدة عمليات استخدم BeginTransactionAsync ومع retries لف الوحدة في execution strategy. TransactionScope للـ ambient transactions مع async flow، مع مراجعة provider/platform ودعم distributed escalation.
Isolation في SQL Serverالضمان والمقايضة
Read Uncommitteddirty reads ممكنة؛ NOLOCK مش حل correctness
Read Committedيمنع dirty reads؛ إعادة القراءة ممكن تختلف
Repeatable Readيحمي الصفوف المقروءة؛ phantoms ممكنة
Serializableنتيجة مكافئة لترتيب serial؛ contention و retries ممكنين
Read Committed بنسخة على مستوى statement
Snapshotنسخة على مستوى transaction؛ write conflicts و write skew ممكنين

دائرة انتظار؛ SQL Server يختار victim بـ error 1205. خلي ترتيب الموارد ثابت والـ transactions قصيرة وافحص deadlock graph، واعمل bounded retry للوحدة الآمنة. row versioning يقلل reader/writer blocking، لكنه مش بيلغي writer/writer locks أو كل deadlocks. PostgreSQL isolation implementations تختلف؛ التفاصيل الأعمق في topic 12.

#Clustered و covering و filtered indexesClustered, covering and filtered indexes

مثال خطوة بخطوة
  1. المطلوب طلبات مستخدم معين مرتبة بالتاريخ. من غير فهرس مناسب قد تحتاج قاعدة البيانات فحص صفوف كثيرة ثم ترتيب الناتج.
  2. فهرس يبدأ بـ UserId ثم CreatedAt قد يساعد البحث داخل مستخدم وترتيب طلباته؛ الأعمدة المطلوبة في الرد تؤثر في احتياج الرجوع لصفوف الجدول.
  3. الفهرس يشبه دليلًا إضافيًا للبيانات، لكنه يحتاج مساحة وتحديثًا عند الكتابة. اختبر بخطة التنفيذ وحجم بيانات ممثل؛ مش كل عمود يحتاج فهرسًا منفصلًا.

index زي فهرس كتاب: يختصر البحث، لكن كل إضافة بتحتاج صيانة الفهرس. في Server rowstore، clustered index leaf level هي data rows؛ ترتيبها المنطقي بالمفتاح مش وعد إن disk sectors متجاورة أو SELECT هيرجع مرتب.

-- Assumes Orders has a clustered Id key and a StoreId column.
CREATE INDEX IX_Orders_Store_Created
ON Orders (StoreId, CreatedAt DESC, Id DESC)
INCLUDE (Total, Status);                                 -- (1)

SELECT Id, CreatedAt, Total, Status FROM Orders
WHERE StoreId = @store AND CreatedAt >= @from
ORDER BY CreatedAt DESC, Id DESC;

CREATE INDEX IX_Orders_Pending
ON Orders (CreatedAt) WHERE Status = 'Pending';          -- (2)
🧾 شرح الكود
  1. (1) equality prefix وبعده range و order مناسبين للـ query دي؛ INCLUDE يحط payload في leaf ويقلل key lookups. مش قاعدة تحط كل الأعمدة في index.
  2. (2) filtered index لجزء معروف من الداتا؛ optimizer لازم يثبت إن query predicate متوافقة مع الفلتر، والـ parameterization ممكن تعقّد ده.

فيه clustered واحد أو heap من غيره؛ nonclustered كتير، و locator يبقى clustered key أو RID للـ heap. constraint مش لازم clustered. covering وصف علاقة index بـ query، مش نوع مستقل مضمون لكل queries. (StoreId, CreatedAt) مفيد للـ leading prefix؛ تاريخ لوحده غالباً مش efficient direct seek عليه. راجع write amplification والـ storage والـ statistics قبل الإضافة.

#Sargability و execution plans و parameter sniffingSargability, plans and parameter sensitivity

index موجودة مش معناها إنها هتستخدم كويس. predicate وطريقة توزيع البيانات والـ estimates بيحددوا الخطة.

❌ function على العمود
SELECT Id FROM Orders WHERE YEAR(CreatedAt) = 2026;
✅ range على نفس العمود
SELECT Id FROM Orders
WHERE CreatedAt >= '20260101' AND CreatedAt < '20270101';

Sargable يعني predicate مناسبة للـ index access؛ مش guarantee إن optimizer يختار seek. انتبه لـ implicit conversions و leading wildcard زي LIKE '%pen'، والـ collation. computed أو expression indexes ممكن تدعم expressions معينة، فمش كل function تمنع كل index.

  1. حدد query من Query Store أو ، وسجّل duration والـ logical reads والـ rows والـ waits.
  2. اقرأ actual plan: فرق estimates عن actuals، و spills، و memory grants، و lookups كتير، و blocking.
  3. قلّل columns و rows؛ صلّح ؛ عدّل predicates و indexes؛ راجع statistics.
  4. جرّب parameters متنوعة وحجم production. scan ممكن يبقى أحسن لو محتاج نسبة كبيرة من الجدول؛ seek ممكن يقرأ كتير برضه.
  5. قارن قبل وبعد، وراجع تكلفة writes والـ plan stability؛ cache أو summaries لما القياس يبرر.

Parameter sniffing: compile باستخدام قيم parameters حالية بيساعد التقدير، لكن plan مناسبة لمتجر صغير ممكن تسوء مع متجر ضخم. افحص compiled و runtime values. OPTION(RECOMPILE) يزود compile ؛ OPTIMIZE FOR ممكن يدي compromise سيئة؛ Query Store forcing و query variants حلول محتاجة قياس. Server الحديث فيه Parameter Sensitive Plan optimization للـ eligible queries حسب version و compatibility level؛ مش علاج لكل حالة.

#SQL Server و PostgreSQL و حماية tenant dataBonusSQL Server, PostgreSQL and tenant isolation

اختياري بس بيفرق: مش هيتسأل فيه في كل انترفيو، بس معرفته بتعمّق فهمك وبتميّزك عن غيرك.

الاختيار بين محركين relational مش مسابقة feature واحدة؛ بص للـ operations والخبرة والـ licensing والـ query workload.

البند ServerPostgreSQL
اللغة والأدواتT-SQL و Query Store و Microsoft toolingSQL و PL/pgSQL و EXPLAIN ANALYZE و extensions
Index storagerowstore clustered أو heap و nonclusteredheap مع indexes منفصلة؛ CLUSTER إعادة ترتيب مش clustered maintenance دائم
JSON functions؛ SQL Server 2025 يضيف native json حسب edition/service supportjsonb مع indexes زي GIN؛ مش كل query محتاجة GIN
locking Read Committed أو حسب settings؛ Read Committed snapshot لكل statement
التشغيلcommercial editions و licensing حسب الاستخدامopen-source license؛ hosting و support لسه بتكلفة
-- PostgreSQL; app connects as a non-owner role without BYPASSRLS.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY orders_tenant ON orders
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid); -- (1)

BEGIN;
SET LOCAL app.tenant_id = '00000000-0000-0000-0000-000000000001'; -- (2)
SELECT id, total FROM orders;
COMMIT;
🧾 شرح الكود
  1. (1) دفاع إضافي للقراءة والكتابة، مش بديل authentication. owner و superuser وأدوار BYPASSRLS ممكن تتخطاه؛ اختبر بالـ application role.
  2. (2) trusted server يحدد tenant بعد authorization. SET LOCAL جوه يمنع تسريب السياق بين pooled connections؛ ماتقبلش tenant value من client من غير تحقق.

SQL Server عنده RLS بآلية مختلفة. والعائلات والـ partitioning والـ consistency موجودين بتعمق في topic 11؛ relational database برضه تقدر تخزن JSON وتكبر بطرق مختلفة.

12 تمرين SQL كلاسيكي ببيانات متجر

#Queries عملية: السعر والترتيب والتقاريرPractice queries: prices, rankings and reports

التمارين دي بتستخدم تدريب موسّعة: Products(Id, CategoryId, ParentProductId, Price)، و Customers(Id, Email)، و Orders(Id, CustomerId, StoreId, CreatedAt, Status, Total)، و OrderLines(OrderId, ProductId, Quantity)، و AccountEntries(Id, CustomerId, CreatedAt, Amount). اعتبر الأعمدة دي موجودة للتمرين؛ duplicate emails في import staging قبل unique constraint. أنماط الرواتب اتنقلت لأسعار products علشان نفضل في domain واحد.

1 و2: تاني وأعلى سعر رقم N

-- (1) Second DISTINCT highest price; NULL if none exists.
SELECT MAX(Price) AS SecondPrice FROM Products
WHERE Price < (SELECT MAX(Price) FROM Products);

-- (2) Nth distinct price, including all tied products if needed.
WITH Ranked AS (
    SELECT Id, Price, DENSE_RANK() OVER (ORDER BY Price DESC) AS rnk
    FROM Products WHERE Price IS NOT NULL
)
SELECT DISTINCT Price FROM Ranked WHERE rnk = @N;
🧾 شرح الكود
  1. (1) استبعد أعلى قيمة وبعدين خد MAX؛ التعادل مش بياخد مرتبة زيادة.
  2. (2) DENSE_RANK للقيم المختلفة؛ @N موجب. لو مطلوب products نفسها اختار Id و Price من غير DISTINCT. لو N مش موجودة النتيجة فاضية.

3: أغلى products في كل category

WITH Ranked AS (
    SELECT Id, CategoryId, Price,
        RANK() OVER (PARTITION BY CategoryId ORDER BY Price DESC) AS rnk
    FROM Products WHERE Price IS NOT NULL
)
SELECT Id, CategoryId, Price FROM Ranked WHERE rnk = 1;   -- (3)
🧾 شرح الكود
  1. (3) ranking بيبدأ لكل category، وكل التعادلات في المركز الأول بترجع. لو عايز واحد بس استخدم ROW_NUMBER مع Id tie-breaker.

4 و5: اكتشاف وإزالة duplicates

SELECT Email, COUNT(*) AS Copies FROM CustomerImport
WHERE Email IS NOT NULL GROUP BY Email HAVING COUNT(*) > 1; -- (4)

WITH Duplicates AS (
    SELECT Id, Email,
        ROW_NUMBER() OVER (PARTITION BY Email ORDER BY Id) AS rn
    FROM CustomerImport WHERE Email IS NOT NULL
)
DELETE FROM Duplicates WHERE rn > 1;                    -- (5)
🧾 شرح الكود
  1. (4) grouping حسب email والـ collation والـ normalization policy؛ مستبعدة بقرار مقصود.
  2. (5) T- updatable بيسيب أقل Id. preview بنفس CTE مع SELECT الأول، وراجع references والـ merge rules قبل DELETE؛ في live data ماتمسحش customer ليه orders لمجرد نفس email. بعدها امنع التكرار بـ unique index مناسب.

6 و7: الغياب و self join

SELECT c.Id FROM Customers c WHERE NOT EXISTS (
    SELECT 1 FROM Orders o WHERE o.CustomerId = c.Id
);                                                     -- (6)

SELECT variant.Id, variant.Price, parent.Price AS ParentPrice
FROM Products variant JOIN Products parent
ON variant.ParentProductId = parent.Id
WHERE variant.Price > parent.Price;                     -- (7)
🧾 شرح الكود
  1. (6) عملاء عمرهم ما طلبوا؛ إضافة شرط status يغيّر السؤال لغياب orders بالحالة دي بس.
  2. (7) نفس نمط مقارنة صف بالـ parent، زي direct-report interview pattern؛ هنا variant سعرها أعلى من parent product. rows من غير parent مستبعدة.

8: Monthly revenue و month-over-month change

WITH Monthly AS (
    SELECT DATEFROMPARTS(YEAR(CreatedAt), MONTH(CreatedAt), 1) AS MonthStart,
        SUM(Total) AS Revenue
    FROM Orders WHERE Status = 'Paid'
    GROUP BY DATEFROMPARTS(YEAR(CreatedAt), MONTH(CreatedAt), 1)
)
SELECT MonthStart, Revenue,
    Revenue - LAG(Revenue) OVER (ORDER BY MonthStart) AS Change
FROM Monthly ORDER BY MonthStart;                       -- (8)
🧾 شرح الكود
  1. (8) نجمع بالشهر والسنة مع بعض، وبعدها نقارن بالصف السابق. شهر فاضي مش موجود في الناتج؛ لو مطلوب مقارنة calendar months لازم calendar table وتملأ الناقص بصفر. أول change هي NULL.

9: أعلى 3 products مبيعاً لكل store

WITH Sales AS (
    SELECT o.StoreId, l.ProductId, SUM(l.Quantity) AS Units
    FROM Orders o JOIN OrderLines l ON l.OrderId = o.Id
    WHERE o.Status = 'Paid'
    GROUP BY o.StoreId, l.ProductId
), Ranked AS (
    SELECT StoreId, ProductId, Units,
        ROW_NUMBER() OVER (
            PARTITION BY StoreId ORDER BY Units DESC, ProductId) AS rn
    FROM Sales
)
SELECT StoreId, ProductId, Units FROM Ranked WHERE rn <= 3; -- (9)
🧾 شرح الكود
  1. (9) الأول وبعده rank. ROW_NUMBER يرجّع بحد أقصى 3؛ DENSE_RANK حسب Units لو مطلوب أول 3 قيم مختلفة بكل ties. خصم returns محتاج definition revenue ومبيعات واضحة.

10 و11: running balance و conditional aggregation

SELECT CustomerId, Id, Amount,
    SUM(Amount) OVER (
        PARTITION BY CustomerId ORDER BY CreatedAt, Id
        ROWS UNBOUNDED PRECEDING) AS Balance
FROM AccountEntries;                                   -- (10)

SELECT COUNT(CASE WHEN Status = 'Paid' THEN 1 END) AS Paid,
    COUNT(CASE WHEN Status = 'Pending' THEN 1 END) AS Pending,
    COUNT(CASE WHEN Status = 'Cancelled' THEN 1 END) AS Cancelled
FROM Orders;                                           -- (11)
🧾 شرح الكود
  1. (10) Amount signed: إضافة موجبة وخصم سالب؛ partition لكل عميل. لو فلترت التاريخ قبل الـ window، لازم opening balance وإلا الحساب ناقص.
  2. (11) COUNT بيتجاهل NULL اللي CASE بترجعها لغير المطابق؛ النتيجة صفر حتى لو الجدول فاضي. ده pivot لعدد حالات ثابت من غير dynamic SQL.

12: أيام orders المتتالية — gaps and islands

WITH Days AS (
    SELECT DISTINCT CAST(CreatedAt AS date) AS OrderDay FROM Orders
), Islands AS (
    SELECT OrderDay,
        DATEADD(day, -CAST(ROW_NUMBER() OVER (ORDER BY OrderDay) AS int),
            OrderDay) AS IslandKey
    FROM Days
)
SELECT MIN(OrderDay) AS StartDay, MAX(OrderDay) AS EndDay, COUNT(*) AS DayCount
FROM Islands GROUP BY IslandKey ORDER BY StartDay;       -- (12)
🧾 شرح الكود
  1. (12) يوم واحد لكل تاريخ؛ نطرح sequence من التاريخ فالأيام المتتالية يبقى ليها key واحدة، وكل gap يغيّرها. ده لكل المتجر؛ لكل customer ضيفه في DISTINCT والـ PARTITION والـ GROUP BY. حدد business timezone قبل تحويل لـ date.

مراجع للمراجعة: SQL Server index design، و Parameter Sensitive Plan optimization، و TRUNCATE وقيوده، و PostgreSQL row security. كل block موضح لهجته وافتراضاته؛ نفّذ تمارين التعديل على database تدريب، وراجع النتائج قبل تطبيقها على data حقيقية.

أسئلة الانترفيو

راجع فهمك

ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟

10.1How do primary, candidate, unique and foreign keys differ?Junior

candidate minimal identifier؛ واحدة والباقي alternate. لمرجع فريد، و composite معناها أكتر من عمود.

الإجابة النموذجية (English)

I distinguish a candidate key, which minimally identifies a row, from the chosen primary key. Other candidate keys can be enforced with unique constraints. A primary key cannot be null and can contain several columns. A foreign key enforces a reference to an eligible unique key, not necessarily the primary key. I use stable identifiers while preserving business uniqueness separately, and I check provider-specific behavior and indexing on the referencing columns.

10.2Explain inner and outer joins using customers and orders.Junior

INNER matches؛ LEFT كل customers؛ RIGHT كل orders؛ FULL الكل. تكرار customer مع orders كتير متوقع.

الإجابة النموذجية (English)

I use an inner join when I need only matching customers and orders. A left join retains all customers and fills order columns with when none match. A right join preserves all orders, while a full outer join retains unmatched rows from both sides. Multiple matching orders repeat the customer columns, which is expected. I am careful with right-side filters after a left join, because a WHERE predicate can remove the unmatched rows.

10.3What are cross joins, self joins and anti-joins?Junior

CROSS كل التوافيق؛ self نفس الجدول aliases؛ anti غياب match غالباً NOT EXISTS.

الإجابة النموذجية (English)

I use a cross join to generate every combination, such as all product sizes and colors, while watching the resulting row count. A self join uses two aliases of the same table to compare related rows, such as a product variant and its parent. An anti-join asks which rows have no match. I usually express that with NOT EXISTS because it clearly states the question and avoids the pitfalls of NOT IN.

10.4What is the difference between WHERE and HAVING?Junior

WHERE rows قبل grouping؛ HAVING groups بعدها. condition مكانها HAVING.

الإجابة النموذجية (English)

I use WHERE to filter individual rows before grouping and HAVING to filter groups. For example, WHERE selects paid orders in a date range, and HAVING keeps customers whose total spending exceeds a threshold. I put conditions that do not need aggregates in WHERE when that matches the intended meaning. Logical processing also explains why a SELECT alias is normally unavailable in WHERE but can be used in ORDER BY.

10.5How do COUNT(*) and COUNT(column) handle NULL?Junior

الأول كل rows؛ التاني values مش . NULL comparison بـ IS NULL مش = NULL.

الإجابة النموذجية (English)

I use COUNT(*) to count rows and COUNT(column) to count only rows with a non-null value in that column. COUNT DISTINCT counts distinct non-null values. After a left join I count the right table's non-nullable key when I want zero for a customer without orders; COUNT(*) would count the preserved customer row. I also use IS rather than equality, because comparisons involving NULL usually evaluate to rather than true.

10.6How do UNION and UNION ALL differ?Junior

UNION تشيل duplicate rows؛ ALL تحتفظ. نفس عدد columns وأنواع متوافقة؛ ORDER BY نهائي لو محتاج ترتيب.

الإجابة النموذجية (English)

I use both to combine result sets with the same number of columns and compatible types. UNION removes duplicate rows across the complete selected shape, which can require extra sorting or hashing. UNION ALL keeps duplicates and avoids that deduplication work. I choose ALL when duplicates are acceptable or already impossible, rather than using UNION to hide a mistaken join. Neither guarantees output order without a final ORDER BY clause.

10.7How do DELETE, TRUNCATE and DROP differ in SQL Server?Junior

صفوف بشرط، أو كل الصفوف مع reset identity، أو object نفسه. TRUNCATE ينفع rollback في Server.

الإجابة النموذجية (English)

I use DELETE to remove selected rows, with row deletion logging and trigger behavior. TRUNCATE removes all rows through operations, resets identity and does not fire DELETE triggers, but has eligibility restrictions such as referencing foreign keys. It is still logged sufficiently for rollback inside a Server . DROP removes the table definition and its data. I review permissions, dependencies and recovery before choosing any destructive command.

10.8Why normalize a relational model?Junior

علشان anomalies؛ قيم مفردة، مفيش partial dependency، تنظم transitive dependencies، أشد.

الإجابة النموذجية (English)

I normalize to reduce update, insert and delete anomalies by storing each current fact with the key it depends on. First normal form removes repeating groups. Second normal form removes partial dependencies of non-prime attributes on candidate keys. Third normal form addresses non-key dependency problems, while requires every nontrivial determinant to be a . I balance the design with real constraints and workloads, and distinguish historical snapshots from accidental duplication.

10.9When is NOT EXISTS safer than NOT IN?Mid

NOT IN مع في subquery بتخلي nonmatches . performance حسب plan مش اسم operator.

الإجابة النموذجية (English)

I prefer NOT EXISTS when expressing absence of a related row, especially if the subquery can contain . With NOT IN, a NULL can make otherwise nonmatching comparisons and remove rows unexpectedly. EXISTS tests whether a matching row exists without comparing against a NULL list member. I still inspect the execution plan for performance, because the optimizer can transform both constructs into similar joins. There is no universal rule that one spelling always runs faster.

10.10What does a CTE provide, and is it materialized?Mid

اسم لنتيجة داخل statement؛ مش cache. recursive فيها anchor و recursive member مع منع cycles.

الإجابة النموذجية (English)

I use a to name a query step and make a complex statement easier to understand. In Server it is not a promise of materialization or caching; the optimizer chooses the plan, and repeated references may repeat work. Recursive CTEs combine an anchor with a recursive member for hierarchies such as categories. I guard against cycles and unreasonable depth. If I need reuse across statements or intermediate indexes, a temporary table may be more suitable.

10.11How do ROW_NUMBER, RANK and DENSE_RANK differ?Mid

ROW_NUMBER كل row رقم؛ RANK ties مع gaps؛ DENSE_RANK ties من غير gaps. وضّح المطلوب من top N.

الإجابة النموذجية (English)

I use ROW_NUMBER when I need one sequential position per row, with a unique tie-breaker for deterministic selection. RANK gives equal values the same rank and leaves gaps afterwards. DENSE_RANK also preserves ties but does not leave gaps. For the Nth distinct price I use DENSE_RANK. For exactly three products per category I use ROW_NUMBER, while including all ties needs a different ranking choice and an explicit business definition.

10.12How do you find the latest order per customer?Mid

ROW_NUMBER partition customer order time desc و Id desc؛ outer query rn=1. join على MAX(time) ممكن يرجّع ties.

الإجابة النموذجية (English)

I assign ROW_NUMBER partitioned by CustomerId and ordered by CreatedAt descending plus a unique identifier descending. An outer query filters the number to one. The identifier makes selection deterministic when timestamps tie. If the requirement is to return every order tied for the latest time, I use ranking without that tie-breaker instead. I also consider an index beginning with CustomerId and the ordering columns, then verify the actual plan on representative data.

10.13When would you use a view, procedure, function or trigger?Mid

view شكل query؛ procedure process؛ function composable result؛ trigger تلقائي ولازم multi-row safe.

الإجابة النموذجية (English)

I use a view to expose a reusable query shape or restricted columns, and a procedure for a controlled data operation with statements and logic. A Server function returns a scalar or table usable within queries, subject to side-effect restrictions. A trigger runs automatically on changes, which can help enforce auditing but adds hidden behavior. I keep triggers set-based for multi-row statements and version and test all database code alongside application changes.

10.14What are ACID properties, and do they enforce business rules automatically?Mid

atomicity و consistency و و durability. مش هتخترع stock لو الكود والـ constraints مش فارضينه.

الإجابة النموذجية (English)

I describe atomicity as all-or-nothing changes, consistency as preserving valid states, as controlling interference between , and durability as retaining committed results under the configured guarantees. A transaction does not invent business rules: constraints and correct operations still need to enforce them. For stock, I may use a conditional update or suitable concurrency control. I also keep remote payments outside the assumption that a local database transaction can roll everything back.

10.15How do you use transactions in .NET with EF Core or Dapper?Mid

SaveChanges واحدة transactional؛ لو أكتر افتح صريحة. Dapper shared connection و لكل command.

الإجابة النموذجية (English)

I rely on a single SaveChanges when it covers the operation. For multiple saves or direct statements I create an explicit transaction, inside the execution strategy when retries apply. With Dapper or ADO.NET I open one connection, begin a transaction and pass it to every command. I keep the unit short, select for the and avoid remote calls inside it. Retryable operations also need safe handling of uncertain commit outcomes.

10.16What is the difference between clustered and nonclustered indexes?Mid

Server rowstore clustered leaves هي data؛ nonclustered منفصلة و locator للـ row. مش لازم clustered.

الإجابة النموذجية (English)

In Server rowstore, I can organize a table as a clustered index whose leaf level contains the data rows, or as a heap. A nonclustered index is a separate structure with keys and a row locator, using the clustered key or a heap RID. A primary key need not be clustered. I choose index keys for access patterns and consider included columns for coverage, while remembering that output order still requires ORDER BY.

10.17How do covering and filtered indexes help?Mid

covering بالنسبة لـ query بعينها؛ filtered subset. تكلفة writes و storage والـ query eligibility مهمة.

الإجابة النموذجية (English)

I call an index covering when it contains everything a particular query needs, allowing the engine to avoid extra base-row lookups. Included columns can provide payload without enlarging the search key in the same way. A filtered index stores a subset, such as pending orders, reducing size and maintenance for irrelevant rows. The optimizer must prove the query fits the filter. I evaluate storage, write costs, parameterization and actual usage rather than creating broad indexes speculatively.

10.18What is a SQL deadlock, and how do you handle it?Mid

دائرة انتظار مش مجرد blocking. graph للتشخيص؛ ترتيب موارد ثابت و قصيرة؛ retry كامل للوحدة الآمنة.

الإجابة النموذجية (English)

I distinguish blocking, where one operation waits, from a , where a cycle prevents progress. Server chooses a victim and returns error 1205. I inspect the deadlock graph to find resource order and access paths, then reduce risk with consistent ordering, short and appropriate indexes. Row versioning can reduce some reader-writer conflicts but does not eliminate deadlocks. I retry the complete aborted unit with a bounded policy only when its side effects are safe.

10.19How do RCSI and Snapshot isolation differ?Senior

statement snapshot، Snapshot snapshot. write skew و writer locks لسه ممكنين.

الإجابة النموذجية (English)

In Server I use to give Read Committed statements versioned reads, with each statement seeing its own committed snapshot. Snapshot provides a -level view instead. Both can reduce reader-writer blocking, but writers still take locks and snapshot transactions can encounter update conflicts. A stable snapshot also does not imply serializability; write skew across different rows can remain possible. I choose isolation from the business and account for version-store overhead and retry behavior.

10.20How do you optimize a slow query without guessing?Senior

actual plan و reads و waits و parameters؛ scan مش دايماً غلط و seek مش دايماً رخيص.

الإجابة النموذجية (English)

I capture the query, parameters, duration, logical reads and waits, then inspect its actual execution plan. I compare estimated and actual rows, look for spills, expensive lookups, large transfers and blocking, and check statistics. I reduce unnecessary data and round trips, rewrite predicates or adjust indexes according to evidence. I test different parameter sizes and compare before and after, including write impact. A scan is not automatically bad, and a seek is not automatically cheap.

10.21What makes a predicate sargable?Senior

تسمح index access مناسب؛ date range بدل YEAR. functions أحياناً ليها computed indexes؛ optimizer يقرر.

الإجابة النموذجية (English)

I call a predicate sargable when its form permits efficient index search for the available indexing strategy. For example, a half-open date range is usually more useful than applying YEAR to every timestamp. I also check implicit conversions, collation and leading wildcards. This is not a guarantee of an index seek, because selectivity and cost still drive the optimizer. Computed or expression indexes can support particular expressions, so I avoid absolute claims about every function.

10.22What is parameter sniffing, and how do you address harmful sensitivity?Senior

قيم compile بتشكل plan؛ reuse مع skew ممكن يضر. PSP و recompilation و Query Store أدوات بقياس، مش default hints.

الإجابة النموذجية (English)

I view parameter sniffing as using current parameter values during compilation to improve estimates, which is normally useful. Harmful sensitivity appears when one cached plan performs poorly for very different distributions. I compare compiled and runtime values and test representative parameters. Depending on evidence, I consider recompilation, query variants or Query Store controls, and check eligibility for Parameter Sensitive Plan optimization in modern Server. Every remedy has costs, so I do not disable sniffing globally by default.

10.23When would you denormalize, and how is BCNF stronger than 3NF?Senior

تسمح RHS prime؛ كل determinant . denormalize بقياس ومع sync owner، والـ historical facts مختلفة.

الإجابة النموذجية (English)

I use normalization to reason about dependencies and prevent anomalies. is stricter than because every nontrivial determinant must be a , whereas 3NF permits a prime attribute on the dependent side. I may denormalize a read model or summary when measured query cost justifies synchronization complexity. I define who updates it, its acceptable staleness and how it can be rebuilt. Historical sale prices are separate facts, so I do not normalize them into current catalog prices.

10.24How do you safely remove duplicate customer records?Senior

تعريف duplicate والـ normalization الأول؛ merge references قبل delete؛ ROW_NUMBER على staging أسهل من live entities.

الإجابة النموذجية (English)

I first define duplicate identity, including email normalization, case rules and policy. I inspect candidate groups and choose a survivor deterministically, often using ROW_NUMBER in staging data. For live customers I merge or reassign related records and preserve audit requirements before deleting anything. I use a controlled or batch strategy, preview affected rows and plan recovery. Finally, I add an appropriate uniqueness constraint so the application cannot immediately recreate the same problem.

اختبر نفسك

إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.

النتيجة: 0 / 0

LEFT JOIN followed by WHERE right.Total > 100 does what to unmatched rows?

مقارنة رقم بتطلع ؛ الفلتر في ON لو عايز تحفظ الشمال.

Which clause filters grouped SUM(Total)?

HAVING بعد grouping منطقياً.

For values 500, 400, 400, 300, what is the DENSE_RANK of 300 descending?

التعادل مش بيعمل gap في DENSE_RANK.

What can NULL inside a NOT IN subquery cause?

WHERE بتحتفظ بـ TRUE بس؛ NOT EXISTS أوضح لغياب العلاقة.

TRUNCATE inside a SQL Server transaction...

changes ليها logging كفاية للـ rollback.

A SQL Server index on (StoreId, CreatedAt) best fits...

الـ leading equality prefix وبعده range مناسبين.

Which date predicate is usually more sargable on CreatedAt?

range من غير function على column؛ الخطة لسه تعتمد على cost.

Which SQL Server option provides statement-level versioned Read Committed reads?

statement snapshot؛ Snapshot -level مختلفة.

A CTE in SQL Server guarantees...

الـ optimizer يحدد physical plan؛ مش cache promise.

A monthly LAG query omits months without orders. It compares against...

املأ missing months لو المقارنة calendar شهر بشهر.

Which statement about plans is correct?

الـ operator name لوحده مش تشخيص.

What should follow deduplication once business identity is defined?

التنضيف من غير prevention بيتكرر.