ADBMS ASSIGNMENT
J032 KHUSHI SHAH
Q1)
Price bands •
RANGE TYPE: price_range AS RANGE (subtype = numeric)
• Table: plans(pid serial pk, name text, band price_range)
• Queries: plans where band @> 450::numeric; overlap with '[300,600]'::price_range; list
lower/upper bounds; order by lower(band); merge overlapping bands (challenge).
SOLUTION)
-- Reset session in case a previous transaction was aborted
ROLLBACK;
-- 1. Drop table and range type if they already exist
DROP TABLE IF EXISTS plans;
DROP TYPE IF EXISTS price_range;
-- 2. Create custom range type for prices
CREATE TYPE price_range AS RANGE (
subtype = numeric
);
-- 3. Create plans table using the range type
CREATE TABLE plans (
pid SERIAL PRIMARY KEY,
name TEXT,
band price_range
);
-- 4. Insert sample plans
INSERT INTO plans (name, band) VALUES
('Basic', '[100,300]'),
('Standard', '[250,500]'),
('Premium', '[450,700]'),
('Elite', '[800,1000]');
-- 5. Plans where price band contains 450
SELECT * FROM plans
WHERE band @> 450::numeric;
-- 6. Plans where price band overlaps with [300,600]
SELECT * FROM plans
WHERE band && '[300,600]'::price_range;
-- 7. List lower and upper bounds of each plan
SELECT
pid,
name,
lower(band) AS lower_price,
upper(band) AS upper_price
FROM plans;
-- 8. Order by lower bound
SELECT * FROM plans
ORDER BY lower(band);
-- 9. Challenge: Merge overlapping or contiguous bands
-- Using range_merge on aggregated bands that overlap
WITH merged AS (
SELECT range_merge(band) AS merged_band
FROM plans
GROUP BY band
)
SELECT merged_band
FROM merged;
Q2)
Booking slots • RANGE TYPE: ts_range AS RANGE (subtype = timestamptz)
• Table: room_bookings(id serial pk, room text, slot ts_range)
• Queries: conflicts for a new slot; all bookings overlapping today; bookings that fully contain
a given interval; free times by room (challenge).
SOLUTION)
-- Create a custom range type for booking slots
CREATE TYPE ts_range AS RANGE (
subtype = timestamptz
);
-- Table to store room bookings
CREATE TABLE room_bookings (
id SERIAL PRIMARY KEY,
room TEXT NOT NULL,
slot ts_range NOT NULL
);
-- Insert sample data
INSERT INTO room_bookings (room, slot) VALUES
('Room A', ts_range('2025-08-14 09:00+05:30', '2025-08-14
11:00+05:30')),
('Room A', ts_range('2025-08-14 13:00+05:30', '2025-08-14
15:00+05:30')),
('Room B', ts_range('2025-08-14 10:00+05:30', '2025-08-14
12:30+05:30')),
('Room B', ts_range('2025-08-14 14:00+05:30', '2025-08-14
16:00+05:30')),
('Room C', ts_range('2025-08-15 09:00+05:30', '2025-08-15
11:00+05:30'));
-- Check if the new slot overlaps with existing bookings
SELECT *
FROM room_bookings
WHERE room = 'Room A'
AND slot && ts_range('2025-08-14 10:30+05:30', '2025-08-14
12:00+05:30');
SELECT *
FROM room_bookings
WHERE slot && ts_range(date_trunc('day', now()), date_trunc('day',
now()) + interval '1 day');
SELECT *
FROM room_bookings
WHERE slot @> ts_range('2025-08-14 09:30+05:30', '2025-08-14
10:30+05:30');
WITH times AS (
SELECT room, slot,
lag(upper(slot)) OVER (PARTITION BY room ORDER BY slot) AS
prev_end
FROM room_bookings
)
SELECT room, ts_range(prev_end, lower(slot)) AS free_slot
FROM times
WHERE prev_end IS NOT NULL;
Q3)
Marks interval • RANGE TYPE: marks_range AS RANGE (subtype = int4)
• Table: grade_rules(grade text pk, interval marks_range)
• Queries: which grade contains 73; overlap with [60,80]; normalize to non-overlapping sets
(challenge); list boundary values.
SOLUTION)
-- Step 1: Create the range type for marks
CREATE TYPE marks_range AS RANGE (
subtype = int4
);
-- Step 2: Create the table for grade rules
CREATE TABLE grade_rules (
grade TEXT PRIMARY KEY,
interval marks_range
);
-- Step 3: Insert sample grade intervals
INSERT INTO grade_rules VALUES
('A', '[80,101)'),
('B', '[60,80)'),
('C', '[40,60)'),
('D', '[0,40)');
SELECT grade
FROM grade_rules
WHERE interval @> 73;
SELECT grade, interval
FROM grade_rules
WHERE interval && '[60,80]'::marks_range;
-- Step 4: Normalize overlapping ranges
SELECT unnest(range_merge(array_agg(interval))) AS
normalized_interval
FROM grade_rules;
SELECT grade,
lower(interval) AS min_marks,
upper(interval) - 1 AS max_marks
FROM grade_rules
ORDER BY min_marks DESC;
Q4)
Salary bands • RANGE TYPE: salary_band AS RANGE (subtype = numeric)
• Table: jobs(id serial pk, title text, band salary_band)
• Queries: jobs covering ₹50,000; employees (another table you create) whose salary falls
within job band (join with @>); raise lower bound by 10% for one job.
SOLUTION)
-- =========================
-- Q1: Price Bands
-- =========================
-- 1. Create range type for prices
CREATE TYPE price_range AS RANGE (subtype = numeric);
-- 2. Table for plans
CREATE TABLE plans (
pid SERIAL PRIMARY KEY,
name TEXT,
band price_range
);
-- 3. Sample data
INSERT INTO plans (name, band) VALUES
('Basic', '[100, 300]'),
('Standard', '[250, 500]'),
('Premium', '[500, 800]'),
('Ultra', '[800, 1200]');
-- 4. Queries
-- Plans where band contains 450
SELECT * FROM plans WHERE band @> 450::numeric;
-- Plans overlapping with [300, 600]
SELECT * FROM plans WHERE band && '[300,600]'::price_range;
-- List lower and upper bounds
SELECT name, lower(band) AS min_price, upper(band) AS max_price
FROM plans;
-- Order by lower bound
SELECT * FROM plans ORDER BY lower(band);
-- Challenge: Merge overlapping bands
WITH merged AS (
SELECT unnest(ranges) AS band
FROM (
SELECT range_merge_agg(band) AS ranges
FROM plans
)x
)
SELECT * FROM merged;
-- =========================
-- Q2: Booking Slots
-- =========================
-- 1. Create range type for booking slots
CREATE TYPE ts_range AS RANGE (subtype = timestamptz);
-- 2. Table for room bookings
CREATE TABLE room_bookings (
id SERIAL PRIMARY KEY,
room TEXT,
slot ts_range
);
-- 3. Sample data
INSERT INTO room_bookings (room, slot) VALUES
('A', '[2025-08-14 09:00, 2025-08-14 11:00)'),
('A', '[2025-08-14 11:00, 2025-08-14 13:00)'),
('B', '[2025-08-14 10:00, 2025-08-14 12:00)');
-- 4. Queries
-- Conflicts for a new slot
SELECT * FROM room_bookings
WHERE room = 'A'
AND slot && '[2025-08-14 10:30, 2025-08-14 12:30)'::ts_range;
-- All bookings overlapping today
SELECT * FROM room_bookings
WHERE slot && tstzrange(date_trunc('day', now()), date_trunc('day',
now()) + interval '1 day');
-- Bookings fully containing a given interval
SELECT * FROM room_bookings
WHERE slot @> '[2025-08-14 10:30, 2025-08-14 11:30)'::ts_range;
-- Challenge: Free times by room
-- (Assuming working hours are [09:00, 17:00))
WITH working_hours AS (
SELECT room, '[2025-08-14 09:00, 2025-08-14 17:00)'::ts_range AS
full_day
FROM room_bookings GROUP BY room
),
gaps AS (
SELECT wh.room, wh.full_day - rb.slot AS free_times
FROM working_hours wh
LEFT JOIN room_bookings rb ON wh.room = rb.room
)
SELECT room, unnest(free_times) AS available_slot FROM gaps;
-- =========================
-- Q3: Marks Interval
-- =========================
-- 1. Create range type
CREATE TYPE marks_range AS RANGE (subtype = int4);
-- 2. Table for grade rules
CREATE TABLE grade_rules (
grade TEXT PRIMARY KEY,
interval marks_range
);
-- 3. Sample data
INSERT INTO grade_rules VALUES
('A', '[85, 100]'),
('B', '[70, 85)'),
('C', '[50, 70)'),
('D', '[0, 50)');
-- 4. Queries
-- Which grade contains 73
SELECT * FROM grade_rules WHERE interval @> 73;
-- Overlap with [60,80]
SELECT * FROM grade_rules WHERE interval &&
'[60,80]'::marks_range;
-- Challenge: Normalize to non-overlapping sets (example)
-- (In this case, already non-overlapping)
-- List boundary values
SELECT grade, lower(interval) AS min_marks, upper(interval) AS
max_marks FROM grade_rules;
-- =========================
Salary Bands
-- =========================
-- 1. Create range type
CREATE TYPE salary_band AS RANGE (subtype = numeric);
-- 2. Table for jobs
CREATE TABLE jobs (
id SERIAL PRIMARY KEY,
title TEXT,
band salary_band
);
-- 3. Table for employees
CREATE TABLE employees (
eid SERIAL PRIMARY KEY,
name TEXT,
salary NUMERIC
);
-- 4. Sample data
INSERT INTO jobs (title, band) VALUES
('Junior Dev', '[30000, 50000)'),
('Mid Dev', '[50000, 80000)'),
('Senior Dev', '[80000, 120000)');
INSERT INTO employees (name, salary) VALUES
('Alice', 45000),
('Bob', 50000),
('Charlie', 90000);
-- 5. Queries
-- Jobs covering ₹50,000
SELECT * FROM jobs WHERE band @> 50000;
-- Employees whose salary falls within job band
SELECT e.name, e.salary, j.title
FROM employees e
JOIN jobs j ON j.band @> e.salary;
-- Raise lower bound by 10% for one job (e.g., Junior Dev)
UPDATE jobs
SET band = numrange(lower(band) * 1.1, upper(band))
WHERE title = 'Junior Dev';
Q5)
Date semesters • RANGE TYPE: semester AS RANGE (subtype = date)
• Table: academic_terms(code text pk, period semester)
• Queries: find term for DATE '2025-01-20'; list terms that overlap with Summer 2025;
ensure no overlaps via EXCLUDE constraint (extra): ALTER TABLE academic_terms ADD
CONSTRAINT no_overlap EXCLUDE USING gist (period WITH &&);
SOLUTION)
-- 1️⃣ Drop range type if already exists (avoid duplicate errors)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'semester')
THEN
DROP TYPE semester;
END IF;
END$$;
-- 2️⃣ Create range type
CREATE TYPE semester AS RANGE (subtype = date);
-- 3️⃣ Create table academic_terms
DROP TABLE IF EXISTS academic_terms CASCADE;
CREATE TABLE academic_terms (
code TEXT PRIMARY KEY,
period semester
);
-- 4️⃣ Insert sample data
INSERT INTO academic_terms (code, period) VALUES
('Spring 2025', '[2025-01-01,2025-05-15)'),
('Summer 2025', '[2025-05-15,2025-08-15)'),
('Fall 2025', '[2025-08-15,2025-12-31)');
-- 5️⃣ Query: Find term for specific date
SELECT code
FROM academic_terms
WHERE period @> DATE '2025-01-20';
-- 6️⃣ Query: List terms that overlap with Summer 2025
SELECT a1.code AS term, a2.code AS overlaps_with
FROM academic_terms a1
JOIN academic_terms a2
ON a1.period && a2.period
WHERE a1.code = 'Summer 2025'
AND a1.code <> a2.code;
-- 7️⃣ Add EXCLUDE constraint to avoid overlaps
ALTER TABLE academic_terms
ADD CONSTRAINT no_overlap
EXCLUDE USING gist (period WITH &&);
-- Test overlap prevention
-- This should fail because it overlaps with Summer 2025
INSERT INTO academic_terms (code, period)
VALUES ('Extra Term', '[2025-06-01,2025-07-01)');