Here is the survey design for question 1:
Here is the survey design for question 2:
Here is the survey design for question 3:
I took the survey twice.
On the first submission:
On the second submission:
Here is a sample JSON file, which I saved as option.json:
{ "event_id": "EVENT_ID_1", "event_type": "form_response", "form_response": { "submitted_at": "2022-07-12T22:51:01Z", "token": "TOKEN_1", "calculated": null, "answers": [{ "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_1", "id": "QUESTION_ID_1", "type": "multiple_choice" }, "text": null, "number": null, "choices": { "labels": [{ "value": "red" }, { "value": "blue" }], "other": "yellow" }, "type": "choices", "date": null, "choice": null } }, { "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_2", "id": "QUESTION_ID_2", "type": "multiple_choice" }, "text": null, "number": null, "choices": { "labels": [{ "value": "To seek the holy grail" }], "other": null }, "type": "choices", "date": null, "choice": null } }, { "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_3", "id": "QUESTION_ID_3", "type": "short_text" }, "text": "what is your name", "number": null, "choices": null, "type": "text", "date": null, "choice": null } }], "form_id": "FORM_ID", "variables": [], "definition": { "id": "FORM_ID", "title": "Test Multiple Choice Other", "fields": [{ "value": { "ref": "QUESTION_REF_1", "id": "QUESTION_ID_1", "type": "multiple_choice", "title": "What is your favorite color?", "choices": [{ "value": { "id": "CHOICE_ID_1", "label": "red" } }, { "value": { "id": "CHOICE_ID_2", "label": "blue" } }], "allow_multiple_selections": "true", "allow_other_choice": "true" } }, { "value": { "ref": "QUESTION_REF_2", "id": "QUESTION_ID_2", "type": "multiple_choice", "title": "What is your quest?", "choices": [{ "value": { "id": "CHOICE_ID_3", "label": "To seek the holy grail" } }], "allow_multiple_selections": "true", "allow_other_choice": "true" } }, { "value": { "ref": "QUESTION_REF_3", "id": "QUESTION_ID_3", "type": "short_text", "title": "What else could I ask?", "choices": [], "allow_multiple_selections": null, "allow_other_choice": null } }] }, "hidden": null, "landed_at": "2022-07-12T22:49:34Z" }, "_sdc_received_at": "2022-07-12T22:51:33.248Z", "_sdc_sequence": "1657666262148", "_sdc_batched_at": "2022-07-12T22:57:17.785Z", "_sdc_table_version": "0" }
{ "event_id": "EVENT_ID_2", "event_type": "form_response", "form_response": { "submitted_at": "2022-07-12T22:53:08Z", "token": "TOKEN_2", "calculated": null, "answers": [{ "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_1", "id": "QUESTION_ID_1", "type": "multiple_choice" }, "text": null, "number": null, "choices": { "labels": [{ "value": "red" }], "other": "orange" }, "type": "choices", "date": null, "choice": null } }, { "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_2", "id": "QUESTION_ID_2", "type": "multiple_choice" }, "text": null, "number": null, "choices": { "labels": [], "other": "something else" }, "type": "choices", "date": null, "choice": null } }, { "value": { "boolean": null, "file_url": null, "url": null, "phone_number": null, "email": null, "field": { "ref": "QUESTION_REF_3", "id": "QUESTION_ID_3", "type": "short_text" }, "text": "What... is the air-speed velocity of an unladen swallow?", "number": null, "choices": null, "type": "text", "date": null, "choice": null } }], "form_id": "FORM_ID", "variables": [], "definition": { "id": "FORM_ID", "title": "Test Multiple Choice Other", "fields": [{ "value": { "ref": "QUESTION_REF_1", "id": "QUESTION_ID_1", "type": "multiple_choice", "title": "What is your favorite color?", "choices": [{ "value": { "id": "CHOICE_ID_1", "label": "red" } }, { "value": { "id": "CHOICE_ID_2", "label": "blue" } }], "allow_multiple_selections": "true", "allow_other_choice": "true" } }, { "value": { "ref": "QUESTION_REF_2", "id": "QUESTION_ID_2", "type": "multiple_choice", "title": "What is your quest?", "choices": [{ "value": { "id": "CHOICE_ID_3", "label": "To seek the holy grail" } }], "allow_multiple_selections": "true", "allow_other_choice": "true" } }, { "value": { "ref": "QUESTION_REF_3", "id": "QUESTION_ID_3", "type": "short_text", "title": "What else could I ask?", "choices": [], "allow_multiple_selections": null, "allow_other_choice": null } }] }, "hidden": null, "landed_at": "2022-07-12T22:51:27Z" }, "_sdc_received_at": "2022-07-12T22:53:38.604Z", "_sdc_sequence": "1657666388772", "_sdc_batched_at": "2022-07-12T22:57:17.818Z", "_sdc_table_version": "0" }
I am trying to flatten this in a way that I can easily share it with others.
What I have so far works fine as long as no one selects "optional" in the multiple choice types:
SELECT
tf.event_id,
a.value.field.id AS question_id,
f.value.title AS question_title,
a.value.type AS question_type,
CASE
WHEN a.value.type = 'choices' THEN c.value
WHEN a.value.type = 'text' THEN a.value.text
END AS value,
IF( a.value.type = 'choices' , choices_index+1 , 1 ) AS sequence,
COALESCE( ARRAY_LENGTH( a.value.choices.labels ) , 1 ) AS sequence_end,
FROM `<PROJECT_ID>.<DATASET_ID>.option` AS tf
LEFT JOIN UNNEST( tf.form_response.answers ) AS a
LEFT JOIN UNNEST( a.value.choices.labels ) AS c WITH OFFSET AS choices_index
INNER JOIN UNNEST( form_response.definition.fields ) f
ON a.value.field.id = f.value.id
Result in JSON:
[{
"event_id": "EVENT_ID_1",
"question_id": "QUESTION_ID_1",
"question_title": "What is your favorite color?",
"question_type": "choices",
"value": "red",
"sequence": "1",
"sequence_end": "2"
}, {
"event_id": "EVENT_ID_1",
"question_id": "QUESTION_ID_1",
"question_title": "What is your favorite color?",
"question_type": "choices",
"value": "blue",
"sequence": "2",
"sequence_end": "2"
}, {
"event_id": "EVENT_ID_1",
"question_id": "QUESTION_ID_2",
"question_title": "What is your quest?",
"question_type": "choices",
"value": "To seek the holy grail",
"sequence": "1",
"sequence_end": "1"
}, {
"event_id": "EVENT_ID_1",
"question_id": "QUESTION_ID_3",
"question_title": "What else could I ask?",
"question_type": "text",
"value": "what is your name",
"sequence": "1",
"sequence_end": "1"
}, {
"event_id": "EVENT_ID_2",
"question_id": "QUESTION_ID_1",
"question_title": "What is your favorite color?",
"question_type": "choices",
"value": "red",
"sequence": "1",
"sequence_end": "1"
}, {
"event_id": "EVENT_ID_2",
"question_id": "QUESTION_ID_2",
"question_title": "What is your quest?",
"question_type": "choices",
"value": null,
"sequence": null,
"sequence_end": "0"
}, {
"event_id": "EVENT_ID_2",
"question_id": "QUESTION_ID_3",
"question_title": "What else could I ask?",
"question_type": "text",
"value": "What... is the air-speed velocity of an unladen swallow?",
"sequence": "1",
"sequence_end": "1"
}]
I want the result to append the "optional" responses to the end of the list of the multiple-choice selections, which would mean my total query result should have 9 rows.
Specifically: EVENT_ID_1, QUESTION_ID_1 should have 3 rows:
and EVENT_ID_2, QUESTION_ID_1 should have 2 rows:
and EVENT_ID_3, QUESTION_ID_2 should have 1 non-null row:
I need your brilliance. Could you please help?
Basic SQL approach can be:
Below is an example from your shared data:
with titles as (
select distinct f.value.id question_id,f.value.title as question_title
from `TABLE_NAME` tf
,UNNEST( form_response.definition.fields ) f
)
,response as(
select tf.event_id
,a.value.field.id as question_id
,a.value.type AS question_type
,CASE
WHEN a.value.type = 'choices' THEN c.value
WHEN a.value.type = 'text' THEN a.value.text
END AS value
from `TABLE_NAME` tf
,UNNEST( tf.form_response.answers ) AS a
left join UNNEST( a.value.choices.labels ) AS c
)
,optionals as(
select distinct tf.event_id
,a.value.field.id as question_id
,a.value.type AS question_type
,a.value.choices.other value
from `TABLE_NAME` tf
,UNNEST( tf.form_response.answers ) AS a
where a.value.choices.other is not null
)
select t.question_title
,f.*
,row_number() over (partition by event_id,f.question_id,question_type ) sequence
,count(*) over (partition by event_id,f.question_id,question_type) sequence_end
from (
select * from response where value is not null
union all
select * from optionals
)f
left join titles t on f.question_id = t.question_id
order by event_id,question_id
Above is just an example, your query also works fine all you have to do is get another result set which will contain only optional value and merge both results together.
N.B Use Temp tables instead of CTE.