Separar participação de resposta sem chave entre as duas ainda deixa a ordem de inserção como ponte. Se responded_at fica na tabela de participação e created_at na de resposta, dá pra casar as duas pelo horário, e id sequencial (ou UUIDv7, que carrega o timestamp) faz a mesma coisa pela ordem. Eu guardaria só a data no created_at, ou nem guardaria, e usaria UUID aleatório.
Sobre a pergunta do fim: o ataque por diferença que você descreveu é o que os institutos de estatística tratam com supressão complementar. Quando uma célula fica abaixo do mínimo, esconde também outra da mesma linha ou coluna, pra conta de subtração não fechar. No produto eu iria pelo caminho de limitar, com segmentos fixos definidos antes da pesquisa no lugar de filtros que combinam à vontade, porque aí dá pra checar todas as combinações antes de mostrar qualquer coisa.