M2M Pivot Table in SQL¶
Here is an example of how to pivot a CRF on a ManyToMany column. M2M uses an interim table to join the main table with the reference table. In our case
the main table is a CRF, the reference table is a List table.
Our model class has two M2M fields:
abnormal_obs_left_foot = models.ManyToManyField(
"edc_mnsi.abnormalfootappearanceobservations",
related_name="+",
verbose_name=f"If NO, check all that apply to LEFT foot?",
blank=True,
)
abnormal_obs_right_foot = models.ManyToManyField(
"edc_mnsi.abnormalfootappearanceobservations",
related_name="+",
verbose_name=f"If NO, check all that apply to RIGHT foot?",
blank=True,
)
An example of the complete model class may be found here: meta_subject_mnsi. See also edc_mnsi.
The tables involved are:
meta_subject_mnsi (CRF)
edc_mnsi_abnormalfootappearanceobservations (List table)
meta_subject_mnsi_abnormal_obs_left_foot (Interim table)
The interim table is created by Django.
The SQL pattern:
SET @sql = NULL;
SELECT GROUP_CONCAT(logic)
INTO @sql
FROM your_table;
SET @sql = CONCAT('select…', @sql, 'from…');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
In the case below, your_table is temp table created from a sub-query.
SET @sql = NULL;
CREATE TEMPORARY TABLE tmp_data
select name as obs_name, subject_identifier, visit_code, visit_code_sequence
from meta_subject_mnsi as CRF
left join meta_subject_subjectvisit as v on crf.subject_visit_id=v.id
left join meta_subject_mnsi_abnormal_obs_left_foot as M2MA on CRF.id = m2ma.mnsi_id
left join edc_mnsi_abnormalfootappearanceobservations as LISTA
on M2MA.abnormalfootappearanceobservations_id = LISTA.id
WHERE LISTA.name is not null
GROUP BY subject_identifier, visit_code, visit_code_sequence, LISTA.name
UNION ALL
select name as obs_name,v.subject_identifier, v.visit_code, v.visit_code_sequence
from meta_subject_mnsi as CRF
left join meta_subject_subjectvisit as v on crf.subject_visit_id=v.id
left join meta_subject_mnsi_abnormal_obs_right_foot as M2MB on crf.id = M2MB.mnsi_id
left join edc_mnsi_abnormalfootappearanceobservations as LISTB
on m2mb.abnormalfootappearanceobservations_id = LISTB.id
WHERE LISTB.name is not null
GROUP BY subject_identifier, visit_code, visit_code_sequence, LISTB.name;
SELECT
GROUP_CONCAT(DISTINCT CONCAT(
'SUM(CASE WHEN obs_name = "', obs_name, '" THEN 1 ELSE 0 END) AS ', obs_name)
)
INTO @sql
FROM tmp_data;
SET @sql = CONCAT('SELECT subject_identifier, visit_code, visit_code_sequence, ', @sql,
' FROM tmp_data GROUP BY subject_identifier, visit_code, visit_code_sequence');
SELECT @sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
DROP table tmp_data;
The result is pivoted on obs_name.
| subject_identifier visit_code visit_code_sequence callous_formation deformity_amputation |
+------------------+-----------+---------------------+------------------+----------------------+
| 105-20-0021-1 | 1000 | 0 | 2 | 1 |
+------------------+-----------+---------------------+------------------+----------------------+
| 105-20-0022-4 | 1000 | 0 | 1 | 0 |
+------------------+-----------+---------------------+------------------+----------------------+
| 105-20-0023-2 | 1000 | 0 | 0 | 0 |
+------------------+-----------+---------------------+------------------+----------------------+
etc ...