| | 241 | WITH |
| | 242 | my_likes AS ( |
| | 243 | SELECT fl.listing_id |
| | 244 | FROM favorite_listings fl |
| | 245 | WHERE fl.client_id = :user_id |
| | 246 | ), |
| | 247 | |
| | 248 | my_recent_likes AS ( |
| | 249 | SELECT fl.listing_id |
| | 250 | FROM favorite_listings fl |
| | 251 | JOIN listings l ON l.listing_id = fl.listing_id |
| | 252 | WHERE fl.client_id = :user_id |
| | 253 | ORDER BY l.created_at DESC |
| | 254 | LIMIT :10 |
| | 255 | ), |
| | 256 | |
| | 257 | similar_users AS ( |
| | 258 | SELECT |
| | 259 | fl2.client_id AS other_user_id, |
| | 260 | COUNT(*) AS overlap_likes |
| | 261 | FROM favorite_listings fl2 |
| | 262 | JOIN my_likes ml ON ml.listing_id = fl2.listing_id |
| | 263 | WHERE fl2.client_id <> :user_id |
| | 264 | GROUP BY fl2.client_id |
| | 265 | HAVING COUNT(*) > 0 |
| | 266 | ), |
| | 267 | |
| | 268 | cf_candidates AS ( |
| | 269 | SELECT |
| | 270 | fl.listing_id, |
| | 271 | SUM(su.overlap_likes) AS cf_score, |
| | 272 | COUNT(DISTINCT su.other_user_id) AS liked_by_similar_users |
| | 273 | FROM similar_users su |
| | 274 | JOIN favorite_listings fl |
| | 275 | ON fl.client_id = su.other_user_id |
| | 276 | LEFT JOIN my_likes ml |
| | 277 | ON ml.listing_id = fl.listing_id |
| | 278 | WHERE ml.listing_id IS NULL |
| | 279 | GROUP BY fl.listing_id |
| | 280 | ), |
| | 281 | |
| | 282 | content_candidates AS ( |
| | 283 | SELECT |
| | 284 | l2.listing_id, |
| | 285 | COUNT(*) AS content_score |
| | 286 | FROM my_recent_likes r |
| | 287 | JOIN listings l1 ON l1.listing_id = r.listing_id |
| | 288 | JOIN listings l2 ON |
| | 289 | l2.listing_id <> l1.listing_id |
| | 290 | AND ( |
| | 291 | (l2.species = l1.species) |
| | 292 | OR (l2.breed = l1.breed) |
| | 293 | OR (l2.location = l1.location) |
| | 294 | ) |
| | 295 | LEFT JOIN my_likes ml ON ml.listing_id = l2.listing_id |
| | 296 | WHERE ml.listing_id IS NULL |
| | 297 | GROUP BY l2.listing_id |
| | 298 | ), |
| | 299 | |
| | 300 | |
| | 301 | merged AS ( |
| | 302 | SELECT |
| | 303 | COALESCE(cf.listing_id, cc.listing_id) AS listing_id, |
| | 304 | COALESCE(cf.cf_score, 0) AS cf_score, |
| | 305 | COALESCE(cf.liked_by_similar_users, 0) AS liked_by_similar_users, |
| | 306 | COALESCE(cc.content_score, 0) AS content_score |
| | 307 | FROM cf_candidates cf |
| | 308 | FULL OUTER JOIN content_candidates cc |
| | 309 | ON cc.listing_id = cf.listing_id |
| | 310 | ) |
| | 311 | |
| | 312 | SELECT |
| | 313 | l.listing_id, |
| | 314 | l.title, |
| | 315 | l.species, |
| | 316 | l.breed, |
| | 317 | l.location, |
| | 318 | l.created_at, |
| | 319 | |
| | 320 | m.cf_score, |
| | 321 | m.liked_by_similar_users, |
| | 322 | m.content_score, |
| | 323 | |
| | 324 | (m.cf_score * 3 + m.content_score * 2) AS final_score |
| | 325 | |
| | 326 | FROM merged m |
| | 327 | JOIN listings l ON l.listing_id = m.listing_id |
| | 328 | WHERE l.status = 'ACTIVE' |
| | 329 | ORDER BY final_score DESC, l.created_at DESC |
| | 330 | LIMIT 20; |
| | 334 | Params <- |
| | 335 | {(user_id := U, k_recent := 10, top_n := 20)} |
| | 336 | MyLikes <- |
| | 337 | π listing_id |
| | 338 | ( |
| | 339 | σ fl.client_id = p.user_id |
| | 340 | ( |
| | 341 | favorite_listings fl × Params p |
| | 342 | ) |
| | 343 | ) |
| | 344 | MyRecentLikes <- |
| | 345 | topK_{K := p.k_recent} |
| | 346 | ( |
| | 347 | τ l.created_at DESC |
| | 348 | ( |
| | 349 | π fl.listing_id, l.created_at |
| | 350 | ( |
| | 351 | σ fl.client_id = p.user_id |
| | 352 | ( |
| | 353 | (favorite_listings fl ⨝ (fl.listing_id = l.listing_id) listings l) |
| | 354 | × Params p |
| | 355 | ) |
| | 356 | ) |
| | 357 | ) |
| | 358 | ) |
| | 359 | SimilarUsers <- |
| | 360 | σ other_user_id ≠ p.user_id ∧ overlap_likes > 0 |
| | 361 | ( |
| | 362 | γ |
| | 363 | other_user_id := fl2.client_id; |
| | 364 | overlap_likes := COUNT(*) |
| | 365 | ( |
| | 366 | ( |
| | 367 | favorite_listings fl2 ⨝ (fl2.listing_id = ml.listing_id) MyLikes ml |
| | 368 | ) |
| | 369 | × Params p |
| | 370 | ) |
| | 371 | ) |
| | 372 | CFCandidates <- |
| | 373 | γ |
| | 374 | listing_id := fl.listing_id; |
| | 375 | cf_score := SUM(su.overlap_likes); |
| | 376 | liked_by_similar_users := COUNT_DISTINCT(su.other_user_id) |
| | 377 | ( |
| | 378 | σ ml.listing_id IS NULL |
| | 379 | ( |
| | 380 | ( |
| | 381 | (SimilarUsers su ⨝ (su.other_user_id = fl.client_id) favorite_listings fl) |
| | 382 | ⟕ (fl.listing_id = ml.listing_id) MyLikes ml |
| | 383 | ) |
| | 384 | ) |
| | 385 | ) |
| | 386 | |
| | 387 | ContentCandidates <- |
| | 388 | γ |
| | 389 | listing_id := l2.listing_id; |
| | 390 | content_score := COUNT(*) |
| | 391 | ( |
| | 392 | σ ml.listing_id IS NULL |
| | 393 | ( |
| | 394 | ( |
| | 395 | ( |
| | 396 | (MyRecentLikes r ⨝ (r.listing_id = l1.listing_id) listings l1) |
| | 397 | ⨝ |
| | 398 | ( |
| | 399 | l2.listing_id ≠ l1.listing_id ∧ |
| | 400 | (l2.species = l1.species ∨ l2.breed = l1.breed ∨ l2.location = l1.location) |
| | 401 | ) |
| | 402 | listings l2 |
| | 403 | ) |
| | 404 | ⟕ (l2.listing_id = ml.listing_id) MyLikes ml |
| | 405 | ) |
| | 406 | ) |
| | 407 | ) |
| | 408 | Merged <- |
| | 409 | π |
| | 410 | listing_id := COALESCE(cf.listing_id, cc.listing_id), |
| | 411 | cf_score := COALESCE(cf.cf_score, 0), |
| | 412 | liked_by_similar_users := COALESCE(cf.liked_by_similar_users, 0), |
| | 413 | content_score := COALESCE(cc.content_score, 0) |
| | 414 | (CFCandidates cf ⟗ (cf.listing_id = cc.listing_id) ContentCandidates cc) |
| | 415 | FinalWithListings <- |
| | 416 | π |
| | 417 | l.listing_id, |
| | 418 | l.title, |
| | 419 | l.species, |
| | 420 | l.breed, |
| | 421 | l.location, |
| | 422 | l.created_at, |
| | 423 | m.cf_score, |
| | 424 | m.liked_by_similar_users, |
| | 425 | m.content_score, |
| | 426 | final_score := (m.cf_score * 3 + m.content_score * 2) |
| | 427 | ( |
| | 428 | σ l.status = 'ACTIVE' |
| | 429 | ((Merged m ⨝ (m.listing_id = l.listing_id) listings l)) |
| | 430 | ) |
| | 431 | Result <- |
| | 432 | topK_{N := p.top_n} |
| | 433 | ( |
| | 434 | τ final_score DESC, created_at DESC |
| | 435 | (FinalWithListings × Params p) |
| | 436 | ) |