Changes between Initial Version and Version 1 of AdvancedReports


Ignore:
Timestamp:
07/07/26 23:17:58 (2 days ago)
Author:
231035
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedReports

    v1 v1  
     1= Advanced Reports
     2== Finding the most active users on the app during a given time period
     3This SQL query wants to find the most active users. Active are users who create listings, write reviews, handle appointments and save favorites.
     4[[BR]]
     5This query:
     6* Counts each type of activity
     7* Combines everything per user
     8* Calculates the final score
     9* Ranks the users from most to least active
     10=== SQL
     11{{{
     12WITH params AS (
     13    SELECT
     14        CAST(:start_ts AS timestamp) AS start_ts,
     15        CAST(:end_ts   AS timestamp) AS end_ts
     16),
     17
     18    listings_by_user AS (
     19         SELECT l.owner_id AS user_id, COUNT(*) AS listings_created
     20         FROM listings l
     21                  JOIN params p ON l.created_at >= p.start_ts AND l.created_at < p.end_ts
     22         GROUP BY l.owner_id
     23     ),
     24     reviews_by_user AS (
     25         SELECT r.reviewer_id AS user_id,
     26                COUNT(*) AS reviews_left,
     27                AVG(r.rating)::numeric(10,2) AS avg_rating_left
     28         FROM reviews r
     29                  JOIN params p ON r.created_at >= p.start_ts AND r.created_at < p.end_ts
     30         GROUP BY r.reviewer_id
     31     ),
     32     appointments_by_user AS (
     33         SELECT a.responsible_owner_id AS user_id,
     34                COUNT(*) AS appointments_total,
     35                COUNT(*) FILTER (WHERE a.status = 'DONE')      AS appointments_done,
     36                COUNT(*) FILTER (WHERE a.status = 'NO_SHOW')   AS appointments_no_show,
     37                COUNT(*) FILTER (WHERE a.status = 'CANCELLED') AS appointments_cancelled
     38         FROM appointments a
     39                  JOIN params p ON a.date_time >= p.start_ts AND a.date_time < p.end_ts
     40         GROUP BY a.responsible_owner_id
     41     ),
     42     favorites_by_user AS (
     43         SELECT f.client_id AS user_id, COUNT(*) AS favorites_saved_all_time
     44         FROM favorite_listings f
     45         GROUP BY f.client_id
     46     )
     47SELECT
     48    u.user_id, u.username, u.email, u.name, u.surname,
     49    COALESCE(l.listings_created, 0) AS listings_created,
     50    COALESCE(rv.reviews_left, 0) AS reviews_left,
     51    COALESCE(rv.avg_rating_left, 0) AS avg_rating_left,
     52    COALESCE(ap.appointments_total, 0) AS appointments_total,
     53    COALESCE(ap.appointments_done, 0) AS appointments_done,
     54    COALESCE(ap.appointments_no_show, 0) AS appointments_no_show,
     55    COALESCE(ap.appointments_cancelled, 0) AS appointments_cancelled,
     56    COALESCE(fv.favorites_saved_all_time, 0) AS favorites_saved_all_time,
     57    (
     58        COALESCE(l.listings_created, 0) * 5
     59            + COALESCE(rv.reviews_left, 0) * 3
     60            + COALESCE(ap.appointments_done, 0) * 2
     61            + COALESCE(fv.favorites_saved_all_time, 0)
     62            - COALESCE(ap.appointments_no_show, 0) * 2
     63        ) AS activity_score,
     64    DENSE_RANK() OVER (
     65        ORDER BY
     66            (
     67                COALESCE(l.listings_created, 0) * 5
     68                    + COALESCE(rv.reviews_left, 0) * 3
     69                    + COALESCE(ap.appointments_done, 0) * 2
     70                    + COALESCE(fv.favorites_saved_all_time, 0)
     71                    - COALESCE(ap.appointments_no_show, 0) * 2
     72                ) DESC,
     73            COALESCE(l.listings_created, 0) DESC,
     74            COALESCE(rv.reviews_left, 0) DESC
     75        ) AS activity_rank
     76FROM users u
     77         LEFT JOIN listings_by_user l ON l.user_id = u.user_id
     78         LEFT JOIN reviews_by_user rv ON rv.user_id = u.user_id
     79         LEFT JOIN appointments_by_user ap ON ap.user_id = u.user_id
     80         LEFT JOIN favorites_by_user fv ON fv.user_id = u.user_id
     81WHERE COALESCE(l.listings_created, 0)
     82          + COALESCE(rv.reviews_left, 0)
     83          + COALESCE(ap.appointments_total, 0)
     84          + COALESCE(fv.favorites_saved_all_time, 0) > 0
     85ORDER BY activity_rank
     86LIMIT 10;
     87}}}
     88=== Relation Algebra
     89{{{
     90Period <-
     91{(start_ts, end_ts)}
     92
     93ListingsByUser <-
     94γ
     95  user_id := l.owner_id;
     96  listings_created := COUNT(*)
     97(
     98  listings l ⨝
     99  (l.created_at ≥ p.start_ts ∧ l.created_at < p.end_ts)
     100  Period p
     101)
     102
     103ReviewsByUser <-
     104γ
     105  user_id := r.reviewer_id;
     106  reviews_left := COUNT(*);
     107  avg_rating_left := AVG(r.rating)
     108(
     109  reviews r ⨝
     110  (r.created_at ≥ p.start_ts ∧ r.created_at < p.end_ts)
     111  Period p
     112)
     113
     114AppointmentsByUser <-
     115γ
     116  user_id := a.responsible_owner_id;
     117  appointments_total := COUNT(*);
     118  appointments_done := COUNT(a.status = 'DONE');
     119  appointments_no_show := COUNT(a.status = 'NO_SHOW');
     120  appointments_cancelled := COUNT(a.status = 'CANCELLED')
     121(
     122  appointments a ⨝
     123  (a.date_time ≥ p.start_ts ∧ a.date_time < p.end_ts)
     124  Period p
     125)
     126
     127FavoritesByUser <-
     128γ
     129  user_id := f.client_id;
     130  favorites_saved_all_time := COUNT(*)
     131(favorite_listings f)
     132
     133UserActivity <-
     134((((
     135     users u
     136     ⟕ (u.user_id = l.user_id) ListingsByUser l
     137    )
     138   ⟕ (u.user_id = r.user_id) ReviewsByUser r
     139   )
     140  ⟕ (u.user_id = a.user_id) AppointmentsByUser a
     141 )
     142 ⟕ (u.user_id = f.user_id) FavoritesByUser f
     143)
     144
     145ActivityWithDefaults <-
     146π
     147  u.user_id,
     148  u.username,
     149  u.email,
     150  u.name,
     151  u.surname,
     152  listings_created := COALESCE(l.listings_created, 0),
     153  reviews_left := COALESCE(r.reviews_left, 0),
     154  avg_rating_left := COALESCE(r.avg_rating_left, 0),
     155  appointments_total := COALESCE(a.appointments_total, 0),
     156  appointments_done := COALESCE(a.appointments_done, 0),
     157  appointments_no_show := COALESCE(a.appointments_no_show, 0),
     158  appointments_cancelled := COALESCE(a.appointments_cancelled, 0),
     159  favorites_saved_all_time := COALESCE(f.favorites_saved_all_time, 0)
     160(UserActivity)
     161
     162ActiveUsers <-
     163σ
     164  listings_created +
     165  reviews_left +
     166  appointments_total +
     167  favorites_saved_all_time > 0
     168(ActivityWithDefaults)
     169
     170ScoredUsers <-
     171π
     172  user_id,
     173  username,
     174  email,
     175  name,
     176  surname,
     177  listings_created,
     178  reviews_left,
     179  avg_rating_left,
     180  appointments_total,
     181  appointments_done,
     182  appointments_no_show,
     183  appointments_cancelled,
     184  favorites_saved_all_time,
     185  activity_score :=
     186    listings_created * 5 +
     187    reviews_left * 3 +
     188    appointments_done * 2 +
     189    favorites_saved_all_time * 1 -
     190    appointments_no_show * 2
     191(ActiveUsers)
     192
     193RankedUsers <-
     194rank_dense
     195  activity_rank :=
     196    ORDER BY
     197      activity_score DESC,
     198      listings_created DESC,
     199      reviews_left DESC
     200(ScoredUsers)
     201
     202Result <-
     203topK_{K := 10}
     204(
     205  τ activity_rank ASC
     206  (
     207    π
     208      user_id,
     209      username,
     210      email,
     211      name,
     212      surname,
     213      listings_created,
     214      reviews_left,
     215      avg_rating_left,
     216      appointments_total,
     217      appointments_done,
     218      appointments_no_show,
     219      appointments_cancelled,
     220      favorites_saved_all_time,
     221      activity_score,
     222      activity_rank
     223    (RankedUsers)
     224  )
     225)
     226
     227}}}
     228
     229== Recommending listings to a user by similar users and liked listings
     230This SQL query wants to find the recommended listings for a user based on similar users and his liked listings.
     231[[BR]]
     232This query:
     233* Gets the recent likes of the user
     234* Gets the similar users based on same liked listings
     235* Gets listings liked by the similar users but NOT by me
     236* Gets listings similar to my liked listings
     237* Combines them both
     238=== SQL
     239{{{
     240WITH
     241    my_likes AS (
     242        SELECT fl.listing_id
     243        FROM favorite_listings fl
     244        WHERE fl.client_id = :user_id
     245    ),
     246
     247    my_recent_likes AS (
     248        SELECT fl.listing_id
     249        FROM favorite_listings fl
     250                 JOIN listings l ON l.listing_id = fl.listing_id
     251        WHERE fl.client_id = :user_id
     252        ORDER BY l.created_at DESC
     253        LIMIT 10
     254    ),
     255
     256    similar_users AS (
     257        SELECT
     258            fl2.client_id AS other_user_id,
     259            COUNT(*)      AS overlap_likes
     260        FROM favorite_listings fl2
     261                 JOIN my_likes ml ON ml.listing_id = fl2.listing_id
     262        WHERE fl2.client_id <> :user_id
     263        GROUP BY fl2.client_id
     264        HAVING COUNT(*) > 0
     265    ),
     266
     267    cf_candidates AS (
     268        SELECT
     269            fl.listing_id,
     270            SUM(su.overlap_likes) AS cf_score,
     271            COUNT(DISTINCT su.other_user_id) AS liked_by_similar_users
     272        FROM similar_users su
     273                 JOIN favorite_listings fl
     274                      ON fl.client_id = su.other_user_id
     275                 LEFT JOIN my_likes ml
     276                           ON ml.listing_id = fl.listing_id
     277        WHERE ml.listing_id IS NULL
     278        GROUP BY fl.listing_id
     279    ),
     280
     281    content_candidates AS (
     282        SELECT
     283            l2.listing_id,
     284            COUNT(*) AS content_score
     285        FROM my_recent_likes r
     286                 JOIN listings l1 ON l1.listing_id = r.listing_id
     287                 JOIN animals a1  ON a1.animal_id = l1.animal_id
     288
     289                 JOIN listings l2 ON l2.listing_id <> l1.listing_id
     290                 JOIN animals a2  ON a2.animal_id = l2.animal_id
     291
     292                 LEFT JOIN my_likes ml ON ml.listing_id = l2.listing_id
     293        WHERE ml.listing_id IS NULL
     294          AND (
     295            a2.species = a1.species
     296                OR a2.breed = a1.breed
     297                OR a2.located_name = a1.located_name
     298            )
     299        GROUP BY l2.listing_id
     300    ),
     301
     302
     303
     304    merged AS (
     305        SELECT
     306            COALESCE(cf.listing_id, cc.listing_id) AS listing_id,
     307            COALESCE(cf.cf_score, 0)              AS cf_score,
     308            COALESCE(cf.liked_by_similar_users, 0) AS liked_by_similar_users,
     309            COALESCE(cc.content_score, 0)         AS content_score
     310        FROM cf_candidates cf
     311                 FULL OUTER JOIN content_candidates cc
     312                                 ON cc.listing_id = cf.listing_id
     313    )
     314
     315SELECT
     316    l.listing_id,
     317    a.name AS title,
     318    a.species,
     319    a.breed,
     320    a.located_name AS location,
     321    l.created_at,
     322
     323
     324    m.cf_score,
     325    m.liked_by_similar_users,
     326    m.content_score,
     327
     328    (m.cf_score * 3 + m.content_score * 2) AS final_score
     329
     330FROM merged m
     331         JOIN listings l ON l.listing_id = m.listing_id
     332         JOIN animals  a ON a.animal_id = l.animal_id
     333WHERE l.status = 'ACTIVE'
     334  AND l.owner_id <> :user_id
     335ORDER BY final_score DESC, l.created_at DESC
     336LIMIT 20;
     337}}}
     338=== Relation Algebra
     339{{{
     340Params <-
     341{(user_id := U, k_recent := 10, top_n := 20)}
     342MyLikes <-
     343π listing_id
     344(
     345  σ fl.client_id = p.user_id
     346  (
     347    favorite_listings fl × Params p
     348  )
     349)
     350MyRecentLikes <-
     351topK_{K := p.k_recent}
     352(
     353  τ l.created_at DESC
     354  (
     355    π fl.listing_id, l.created_at
     356    (
     357      σ fl.client_id = p.user_id
     358      (
     359        (favorite_listings fl ⨝ (fl.listing_id = l.listing_id) listings l)
     360        × Params p
     361      )
     362    )
     363  )
     364)
     365SimilarUsers <-
     366σ other_user_id ≠ p.user_id ∧ overlap_likes > 0
     367(
     368  γ
     369    other_user_id := fl2.client_id;
     370    overlap_likes := COUNT(*)
     371  (
     372    (
     373      favorite_listings fl2 ⨝ (fl2.listing_id = ml.listing_id) MyLikes ml
     374    )
     375    × Params p
     376  )
     377)
     378CFCandidates <-
     379γ
     380  listing_id := fl.listing_id;
     381  cf_score := SUM(su.overlap_likes);
     382  liked_by_similar_users := COUNT_DISTINCT(su.other_user_id)
     383(
     384  σ ml.listing_id IS NULL
     385  (
     386    (
     387      (SimilarUsers su ⨝ (su.other_user_id = fl.client_id) favorite_listings fl)
     388      ⟕ (fl.listing_id = ml.listing_id) MyLikes ml
     389    )
     390  )
     391)
     392
     393ContentCandidates <-
     394γ
     395  listing_id := l2.listing_id;
     396  content_score := COUNT(*)
     397(
     398  σ ml.listing_id IS NULL
     399  (
     400    (
     401      (
     402        (MyRecentLikes r ⨝ (r.listing_id = l1.listing_id) listings l1)
     403        ⨝
     404        (
     405          l2.listing_id ≠ l1.listing_id ∧
     406          (l2.species = l1.species ∨ l2.breed = l1.breed ∨ l2.location = l1.location)
     407        )
     408        listings l2
     409      )
     410      ⟕ (l2.listing_id = ml.listing_id) MyLikes ml
     411    )
     412  )
     413)
     414Merged <-
     415π
     416  listing_id := COALESCE(cf.listing_id, cc.listing_id),
     417  cf_score := COALESCE(cf.cf_score, 0),
     418  liked_by_similar_users := COALESCE(cf.liked_by_similar_users, 0),
     419  content_score := COALESCE(cc.content_score, 0)
     420(CFCandidates cf ⟗ (cf.listing_id = cc.listing_id) ContentCandidates cc)
     421FinalWithListings <-
     422π
     423  l.listing_id,
     424  l.title,
     425  l.species,
     426  l.breed,
     427  l.location,
     428  l.created_at,
     429  m.cf_score,
     430  m.liked_by_similar_users,
     431  m.content_score,
     432  final_score := (m.cf_score * 3 + m.content_score * 2)
     433(
     434  σ (l.status = 'ACTIVE' ∧ l.owner_id ≠ p.user_id)
     435  (
     436    (Merged m ⨝ (m.listing_id = l.listing_id) listings l)
     437    × Params p
     438  )
     439)
     440
     441Result <-
     442topK_{N := p.top_n}
     443(
     444  τ final_score DESC, created_at DESC
     445  (FinalWithListings × Params p)
     446)
     447
     448}}}