tuladha-api / sql /match_courses.sql
Fahmi Syahputra
Initial commit for Backend
49bd31a
Raw
History Blame Contribute Delete
3.3 kB
-- =============================================================================
-- Supabase RPC: match_courses
-- Hybrid Recommender Engine — Vector Similarity Search
-- Searches course_chunks embeddings, JOINs to parent courses table.
-- Supports SBERT, E5, and DistilBERT models via model_type parameter.
-- =============================================================================
-- Drop existing function variants (for idempotent re-runs)
DROP FUNCTION IF EXISTS match_courses(vector, float, int, int, text);
DROP FUNCTION IF EXISTS match_courses(vector, float8, int4, int4, text);
CREATE OR REPLACE FUNCTION match_courses(
query_embedding vector,
match_threshold float DEFAULT 0.3,
match_count int DEFAULT 5,
child_age_months int DEFAULT 48,
model_type text DEFAULT 'sbert'
)
RETURNS TABLE (
course_id uuid,
course_title text,
course_description text,
chunk_id uuid,
chunk_title text,
chunk_content text,
similarity float
)
LANGUAGE plpgsql
AS $$
BEGIN
IF model_type = 'e5' THEN
RETURN QUERY
SELECT
c.id AS course_id,
c.title AS course_title,
c.description AS course_description,
cc.id AS chunk_id,
cc.chunk_title,
cc.chunk_content,
(1 - (cc.embedding_e5 <=> query_embedding))::float AS similarity
FROM course_chunks cc
JOIN courses c ON cc.course_id = c.id
WHERE
c.age_min <= child_age_months
AND c.age_max >= child_age_months
AND cc.embedding_e5 IS NOT NULL
AND (1 - (cc.embedding_e5 <=> query_embedding)) > match_threshold
ORDER BY cc.embedding_e5 <=> query_embedding ASC
LIMIT match_count;
ELSIF model_type = 'distilbert' THEN
RETURN QUERY
SELECT
c.id AS course_id,
c.title AS course_title,
c.description AS course_description,
cc.id AS chunk_id,
cc.chunk_title,
cc.chunk_content,
(1 - (cc.embedding_distilbert <=> query_embedding))::float AS similarity
FROM course_chunks cc
JOIN courses c ON cc.course_id = c.id
WHERE
c.age_min <= child_age_months
AND c.age_max >= child_age_months
AND cc.embedding_distilbert IS NOT NULL
AND (1 - (cc.embedding_distilbert <=> query_embedding)) > match_threshold
ORDER BY cc.embedding_distilbert <=> query_embedding ASC
LIMIT match_count;
ELSE
-- Default: SBERT
RETURN QUERY
SELECT
c.id AS course_id,
c.title AS course_title,
c.description AS course_description,
cc.id AS chunk_id,
cc.chunk_title,
cc.chunk_content,
(1 - (cc.embedding_sbert <=> query_embedding))::float AS similarity
FROM course_chunks cc
JOIN courses c ON cc.course_id = c.id
WHERE
c.age_min <= child_age_months
AND c.age_max >= child_age_months
AND cc.embedding_sbert IS NOT NULL
AND (1 - (cc.embedding_sbert <=> query_embedding)) > match_threshold
ORDER BY cc.embedding_sbert <=> query_embedding ASC
LIMIT match_count;
END IF;
END;
$$;