25  The shape of a name

How do you pick eight score columns without writing eight names? Naming columns one by one is tedious in a wide table, and the written list goes stale the moment a ninth score arrives. pick where takes a question about a column’s name instead. The survey table is the example: six respondents, eight score columns, and four columns of everything else.

run('
  survey
    then pick where (name starts "q")
')
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4
survey |> take(2)
respondent name region joined q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
1 ana West 2026-01-12 4 2 5 3 4 5 2 3
2 ben East 2026-02-03 5 5 4 3 2 5 4 5
survey |> pick(where(startsWith(name, "q")))
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4
survey >> take(2)
respondent name region joined q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
1 ana West 2026-01-12 4 2 5 3 4 5 2 3
2 ben East 2026-02-03 5 5 4 3 2 5 4 5
survey >> pick(where(name.starts("q")))
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4

survey then pick where (name starts "q")

name is the word for whichever column is being considered. There are three tests of its shape, starts, ends and contains, and they join with and, or and not like any other condition. is still means an exact match, so one named column can sit in the same question:

run('
  survey
    then pick where ((name ends "_score") or (name is "respondent"))
')
respondent q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
1 4 2 5 3 4 5 2 3
2 5 5 4 3 2 5 4 5
3 3 5 2 4 5 3 4 2
4 4 3 5 2 4 3 5 4
5 2 4 3 5 4 2 3 5
6 5 4 4 5 3 4 5 4
survey |> pick(where(endsWith(name, "_score") | name == "respondent"))
respondent q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
1 4 2 5 3 4 5 2 3
2 5 5 4 3 2 5 4 5
3 3 5 2 4 5 3 4 2
4 4 3 5 2 4 3 5 4
5 2 4 3 5 4 2 3 5
6 5 4 4 5 3 4 5 4
survey >> pick(where(name.ends("_score") | (name == "respondent")))
respondent q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
1 4 2 5 3 4 5 2 3
2 5 5 4 3 2 5 4 5
3 3 5 2 4 5 3 4 2
4 4 3 5 2 4 3 5 4
5 2 4 3 5 4 2 3 5
6 5 4 4 5 3 4 5 4

contains is the third test: it matches the quoted text anywhere in the name, not only at the start or the end. In survey it finds the same columns as starts "q", because no other name has a q in it:

run('
  survey
    then pick where (name contains "q")
')
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4
survey |> pick(where(grepl("q", name)))
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4
survey >> pick(where(name.contains("q")))
q1_score q2_score q3_score q4_score q5_score q6_score q7_score q8_score
4 2 5 3 4 5 2 3
5 5 4 3 2 5 4 5
3 5 2 4 5 3 4 2
4 3 5 2 4 3 5 4
2 4 3 5 4 2 3 5
5 4 4 5 3 4 5 4

If no column’s name matches, the pick is refused before anything runs:

collect(survey |> pick(where(startsWith(name, "zzz"))))
Error:
! 
illegal: no column's name matches that, so this would leave the table with no columns. It has: respondent, name, region, joined, q1_score, q2_score, q3_score, q4_score, q5_score, q6_score, q7_score, q8_score
  |
2 |   then pick where (name starts "zzz")
  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
try:
    collect(survey >> pick(where(name.starts("zzz"))))
except GodError as refusal:
    print(refusal)

illegal: no column's name matches that, so this would leave the table with no columns. It has: respondent, name, region, joined, q1_score, q2_score, q3_score, q4_score, q5_score, q6_score, q7_score, q8_score
  |
2 |   then pick where (name starts "zzz")
  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

An empty choice is refused rather than returned, because a table with no columns answers nothing. The message lists what the table does have, and the spelling you meant is usually in that list.

The shape of a name is only the first question this part asks. What a column holds asks the next.