cswork / libraryOpen practice workspace

Coding questions

Write SQL to analyze response accuracy and speed

This question evaluates proficiency with SQL-based data manipulation and analytical querying on relational assessment data, including aggregation, grouping, joins, windowed ranking, and use of PostgreSQL statistical functions to analyze accuracy and response-time metrics.

You are given response-level data for an online assessment with sections verbal/design/analytics and verbal subtypes grammar/vocab/tense/other. Using PostgreSQL 14+, answer the SQL tasks below on the following schema and sample rows (illustrative only; real data are larger). Schema: candidates(candidate_id INT PRIMARY KEY, role_applied TEXT, location TEXT); submissions(attempt_id INT PRIMARY KEY, candidate_id INT, started_at TIMESTAMP); questions(question_id INT PRIMARY KEY, section TEXT, subtype TEXT, points INT); responses(attempt_id INT, question_id INT, is_correct INT, response_time_sec INT). Sample rows: candidates: | candidate_id | role_applied | location | -> | 1 | DA | US | | 2 | DA | CN | | 3 | DA | IN |. submissions: | attempt_id | candidate_id | started_at | -> | 1001 | 1 | 2025-08-15 10:00 | | 1002 | 2 | 2025-08-15 10:00 | | 1003 | 1 | 2025-08-16 10:00 |. questions: | question_id | section | subtype | points | -> | 101 | verbal | grammar | 1 | | 102 | verbal | vocab | 1 | | 103 | verbal | tense | 1 | | 104 | verbal | other | 1 | | 105 | verbal | grammar | 1 | | 201 | design | n/a | 2 | | 301 | analytics | n/a | 2 |. responses: | attempt_id | question_id | is_correct | response_time_sec | -> | 1001 | 101 | 1 | 32 | | 1001 | 102 | 0 | 60 | | 1001 | 103 | 1 | 41 | | 1001 | 104 | 0 | 12 | | 1001 | 105 | 1 | 39 | | 1002 | 101 | 0 | 18 | | 1002 | 102 | 1 | 55 | | 1002 | 103 | 1 | 42 | | 1002 | 104 | 0 | 10 | | 1002 | 105 | 0 | 20 | | 1003 | 101 | 1 | 30 | | 1003 | 102 | 1 | 52 | | 1003 | 103 | 1 | 46 | | 1003 | 104 | 1 | 58 | | 1003 | 105 | 1 | 37 |. Tasks: (1) For each attempt with at least 15 verbal responses in the real dataset, compute per-subtype accuracy (avg is_correct) and median response_time_sec; also return total verbal responses and total verbal time per attempt. (2) Flag 'rushing' attempts where over 20% of verbal responses have response_time_sec < 15. Return attempt_id, candidate_id, rushing_rate. (3) Within the verbal section only, find the subtype with the strongest magnitude Pearson correlation between response_time_sec and is_correct (cast to double). Return subtype, corr, n. Use Postgres corr(). (4) For each candidate, compute their latest attempt’s verbal questions per minute and correct per minute; then rank candidates within location by correct per minute, breaking ties by lower time per question.

Attempt-level verbal subtype accuracy + median time (with attempt totals)

You are given response-level data for an online assessment. Each question belongs to a section (verbal/design/analytics). Verbal questions also have a subtype (grammar/vocab/tense/other).

For each attempt that has at least 15 verbal responses, return one row per (attempt_id, verbal subtype) with:

  • accuracy = average of is_correct (treat is_correct as 0/1)
  • median_response_time_sec for that subtype within the attempt
  • total_verbal_responses in the attempt
  • total_verbal_time_sec in the attempt

Output columns: attempt_id, subtype, accuracy, median_response_time_sec, total_verbal_responses, total_verbal_time_sec.

Use PostgreSQL 14+. Median should be computed using percentile_cont(0.5).

Table candidates

columntypeconstraints
candidate_idINTPRIMARY KEY, NOT NULL
role_appliedTEXTNOT NULL
locationTEXTNOT NULL

Sample data:

candidate_idlocationrole_applied
1USDA
2CNDA
3INDA
4USDA

Table submissions

columntypeconstraints
attempt_idINTPRIMARY KEY, NOT NULL
candidate_idINTNOT NULL
started_atTIMESTAMPNOT NULL

Sample data:

attempt_idcandidate_idstarted_at
100112025-08-15 10:00:00
100222025-08-15 10:00:00
100312025-08-16 10:00:00
100432025-08-17 10:00:00
100542025-08-18 10:00:00

Table questions

columntypeconstraints
question_idINTPRIMARY KEY, NOT NULL
sectionTEXTNOT NULL
subtypeTEXTNOT NULL
pointsINTNOT NULL

Sample data:

pointsquestion_idsectionsubtype
1101verbalgrammar
1102verbalvocab
1103verbaltense
1104verbalother
1105verbalgrammar
1106verbalvocab
1107verbaltense
1108verbalother
1109verbalgrammar
1110verbalvocab
1111verbaltense
1112verbalother
1113verbalgrammar
1114verbalvocab
1115verbaltense
1116verbalother
2201designn/a
2301analyticsn/a

Table responses

columntypeconstraints
attempt_idINTNOT NULL
question_idINTNOT NULL
is_correctINTNOT NULL
response_time_secINTNOT NULL

Sample data:

attempt_idis_correctquestion_idresponse_time_sec
1001110132
1001110539
1001010928
1001111335
1001010260
1001110650
1001011055
1001111445
1001110340
1001110740
1001111140
1001011510
1001010412
1001010818
1001111222
1001011614
1002010118
1002010520
1002010914
1002111316
1002110255
1002110648
1002011012
1002011410
1002110340
1002010710
1002011110
1002011510
1002010410
100201089
100201128
100201167
1003110130
1003110537
1003110933
1003111334
1003110252
1003110649
1003111051
1003011447
1003110340
1003110740
1003111140
1003111540
1003110458
1003010840
1003111236
1003111620
1004110125
1004010527
1004110929
1004011331
1004010235
1004110633
1004011037
1004111439
1004110340
1004110740
1004011110
1004011510
1004010415
1004010816
1004111214
1004011613
1005110128
1005110530
1005110932
1005111334
1005110236
1005110638
1005111040
1005011442
1005110340
1005110740
1005111140
1005011510
1005110426
1005110824
1005011222
1005111620

Expected output

One row per (attempt_id, subtype) for attempts with at least 15 verbal responses, including subtype accuracy/median time plus attempt-level verbal totals repeated on each row.

accuracyattempt_idmedian_response_time_secsubtypetotal_verbal_responsestotal_verbal_time_sec
0.75100133.5grammar16540
0.25100116other16540
0.75100140tense16540
0.5100152.5vocab16540
0.25100217grammar16297
010028.5other16297
0.25100210tense16297
0.5100230vocab16297
1100333.5grammar16647
0.75100338other16647
1100340tense16647
0.75100350vocab16647
0.5100428grammar16414
0.25100414.5other16414
0.5100425tense16414
0.5100436vocab16414
1100531grammar16502
0.75100523other16502
0.75100540tense16502
0.75100539vocab16502

Hints

  1. Filter to verbal questions by joining responses to questions and using q.section = 'verbal'.
  2. Use percentile_cont(0.5) WITHIN GROUP (ORDER BY response_time_sec) to compute a median in Postgres.
  3. Compute attempt totals in a separate CTE and join them back to the per-subtype aggregates.

Flag rushing attempts by share of very fast verbal responses

A verbal response is considered "very fast" if response_time_sec < 15.

Flag "rushing" attempts where more than 20% of verbal responses are very fast.

Return attempt_id, candidate_id, and rushing_rate (the fraction of verbal responses with response_time_sec < 15).

Use PostgreSQL 14+.

Table candidates

columntypeconstraints
candidate_idINTPRIMARY KEY, NOT NULL
role_appliedTEXTNOT NULL
locationTEXTNOT NULL

Sample data:

candidate_idlocationrole_applied
1USDA
2CNDA
3INDA
4USDA

Table submissions

columntypeconstraints
attempt_idINTPRIMARY KEY, NOT NULL
candidate_idINTNOT NULL
started_atTIMESTAMPNOT NULL

Sample data:

attempt_idcandidate_idstarted_at
100112025-08-15 10:00:00
100222025-08-15 10:00:00
100312025-08-16 10:00:00
100432025-08-17 10:00:00
100542025-08-18 10:00:00

Table questions

columntypeconstraints
question_idINTPRIMARY KEY, NOT NULL
sectionTEXTNOT NULL
subtypeTEXTNOT NULL
pointsINTNOT NULL

Sample data:

pointsquestion_idsectionsubtype
1101verbalgrammar
1102verbalvocab
1103verbaltense
1104verbalother
1105verbalgrammar
1106verbalvocab
1107verbaltense
1108verbalother
1109verbalgrammar
1110verbalvocab
1111verbaltense
1112verbalother
1113verbalgrammar
1114verbalvocab
1115verbaltense
1116verbalother

Table responses

columntypeconstraints
attempt_idINTNOT NULL
question_idINTNOT NULL
is_correctINTNOT NULL
response_time_secINTNOT NULL

Sample data:

attempt_idis_correctquestion_idresponse_time_sec
1001110132
1001110539
1001010928
1001111335
1001010260
1001110650
1001011055
1001111445
1001110340
1001110740
1001111140
1001011510
1001010412
1001010818
1001111222
1001011614
1002010118
1002010520
1002010914
1002111316
1002110255
1002110648
1002011012
1002011410
1002110340
1002010710
1002011110
1002011510
1002010410
100201089
100201128
100201167
1003110130
1003110537
1003110933
1003111334
1003110252
1003110649
1003111051
1003011447
1003110340
1003110740
1003111140
1003111540
1003110458
1003010840
1003111236
1003111620
1004110125
1004010527
1004110929
1004011331
1004010235
1004110633
1004011037
1004111439
1004110340
1004110740
1004011110
1004011510
1004010415
1004010816
1004111214
1004011613
1005110128
1005110530
1005110932
1005111334
1005110236
1005110638
1005111040
1005011442
1005110340
1005110740
1005111140
1005011510
1005110426
1005110824
1005011222
1005111620

Expected output

One row per rushing attempt (attempts where rushing_rate > 0.20), including the owning candidate_id.

attempt_idcandidate_idrushing_rate
100220.625
100430.25

Hints

  1. Filter to verbal responses first by joining to questions.
  2. Compute rushing_rate as AVG((response_time_sec < 15)::int).
  3. Filter attempts with rushing_rate > 0.20.

Find the verbal subtype with strongest time-vs-correct correlation

Within the verbal section only, compute the Pearson correlation between response_time_sec and is_correct for each verbal subtype, using PostgreSQL's corr() function.

Return the subtype with the strongest magnitude correlation (i.e., maximum ABS(corr)). Also return:

  • corr (the correlation value)
  • n (number of responses used)

Notes:

  • Cast is_correct to double precision.
  • If a subtype's correlation is NULL (e.g., no variance), it should not win over non-NULL correlations.

Output columns: subtype, corr, n.

Table questions

columntypeconstraints
question_idINTPRIMARY KEY, NOT NULL
sectionTEXTNOT NULL
subtypeTEXTNOT NULL
pointsINTNOT NULL

Sample data:

pointsquestion_idsectionsubtype
1101verbalgrammar
1102verbalvocab
1103verbaltense
1104verbalother
1105verbalgrammar
1106verbalvocab
1107verbaltense
1108verbalother
1109verbalgrammar
1110verbalvocab
1111verbaltense
1112verbalother
1113verbalgrammar
1114verbalvocab
1115verbaltense
1116verbalother

Table responses

columntypeconstraints
attempt_idINTNOT NULL
question_idINTNOT NULL
is_correctINTNOT NULL
response_time_secINTNOT NULL

Sample data:

attempt_idis_correctquestion_idresponse_time_sec
1001110132
1001110539
1001010928
1001111335
1001010260
1001110650
1001011055
1001111445
1001110340
1001110740
1001111140
1001011510
1001010412
1001010818
1001111222
1001011614
1002010118
1002010520
1002010914
1002111316
1002110255
1002110648
1002011012
1002011410
1002110340
1002010710
1002011110
1002011510
1002010410
100201089
100201128
100201167
1003110130
1003110537
1003110933
1003111334
1003110252
1003110649
1003111051
1003011447
1003110340
1003110740
1003111140
1003111540
1003110458
1003010840
1003111236
1003111620
1004110125
1004010527
1004110929
1004011331
1004010235
1004110633
1004011037
1004111439
1004110340
1004110740
1004011110
1004011510
1004010415
1004010816
1004111214
1004011613
1005110128
1005110530
1005110932
1005111334
1005110236
1005110638
1005111040
1005011442
1005110340
1005110740
1005111140
1005011510
1005110426
1005110824
1005011222
1005111620

Expected output

Exactly one row: the verbal subtype with the maximum absolute Pearson correlation between response_time_sec and is_correct, along with the correlation value and number of rows used.

corrnsubtype
120tense

Hints

  1. Compute corr(response_time_sec::double precision, is_correct::double precision) grouped by subtype.
  2. Use ORDER BY ABS(corr) DESC NULLS LAST to pick the strongest magnitude correlation.
  3. Return COUNT(*) as n so you can validate the sample size.

Latest attempt throughput and location ranking

For each candidate, consider ONLY their latest attempt (based on submissions.started_at).

Using only verbal responses from that latest attempt, compute:

  • verbal_questions_per_minute = (number of verbal responses) / (total verbal time in minutes)
  • verbal_correct_per_minute = (number of correct verbal responses) / (total verbal time in minutes)
  • avg_time_per_question_sec = average verbal response_time_sec

Then rank candidates within each location by:

  1. higher verbal_correct_per_minute
  2. if tied, lower avg_time_per_question_sec

Return: candidate_id, location, latest_attempt_id, verbal_questions_per_minute, verbal_correct_per_minute, avg_time_per_question_sec, rank_in_location.

Use PostgreSQL 14+.

Table candidates

columntypeconstraints
candidate_idINTPRIMARY KEY, NOT NULL
role_appliedTEXTNOT NULL
locationTEXTNOT NULL

Sample data:

candidate_idlocationrole_applied
1USDA
2CNDA
3INDA
4USDA

Table submissions

columntypeconstraints
attempt_idINTPRIMARY KEY, NOT NULL
candidate_idINTNOT NULL
started_atTIMESTAMPNOT NULL

Sample data:

attempt_idcandidate_idstarted_at
100112025-08-15 10:00:00
100222025-08-15 10:00:00
100312025-08-16 10:00:00
100432025-08-17 10:00:00
100542025-08-18 10:00:00

Table questions

columntypeconstraints
question_idINTPRIMARY KEY, NOT NULL
sectionTEXTNOT NULL
subtypeTEXTNOT NULL
pointsINTNOT NULL

Sample data:

pointsquestion_idsectionsubtype
1101verbalgrammar
1102verbalvocab
1103verbaltense
1104verbalother
1105verbalgrammar
1106verbalvocab
1107verbaltense
1108verbalother
1109verbalgrammar
1110verbalvocab
1111verbaltense
1112verbalother
1113verbalgrammar
1114verbalvocab
1115verbaltense
1116verbalother

Table responses

columntypeconstraints
attempt_idINTNOT NULL
question_idINTNOT NULL
is_correctINTNOT NULL
response_time_secINTNOT NULL

Sample data:

attempt_idis_correctquestion_idresponse_time_sec
1001110132
1001110539
1001010928
1001111335
1001010260
1001110650
1001011055
1001111445
1001110340
1001110740
1001111140
1001011510
1001010412
1001010818
1001111222
1001011614
1002010118
1002010520
1002010914
1002111316
1002110255
1002110648
1002011012
1002011410
1002110340
1002010710
1002011110
1002011510
1002010410
100201089
100201128
100201167
1003110130
1003110537
1003110933
1003111334
1003110252
1003110649
1003111051
1003011447
1003110340
1003110740
1003111140
1003111540
1003110458
1003010840
1003111236
1003111620
1004110125
1004010527
1004110929
1004011331
1004010235
1004110633
1004011037
1004111439
1004110340
1004110740
1004011110
1004011510
1004010415
1004010816
1004111214
1004011613
1005110128
1005110530
1005110932
1005111334
1005110236
1005110638
1005111040
1005011442
1005110340
1005110740
1005111140
1005011510
1005110426
1005110824
1005011222
1005111620

Expected output

One row per candidate showing metrics computed from that candidate's latest attempt, plus a rank within their location (ordered by correct per minute desc, then avg time per question asc).

avg_time_per_question_seccandidate_idlatest_attempt_idlocationrank_in_locationverbal_correct_per_minuteverbal_questions_per_minute
18.5621002CN10.80813.2323
25.8831004IN11.01452.3188
31.3841005US11.55381.9124
40.4411003US21.29831.4838

Hints

  1. Use ROW_NUMBER() to find each candidate's latest attempt by started_at.
  2. Compute per-minute rates by dividing counts by (sum_time_sec / 60.0).
  3. Use RANK() OVER (PARTITION BY location ORDER BY correct_per_min DESC, avg_time_per_question ASC).