Dia 6 — Atualizar e apagar

Informatica · Conteudo · publicado em 05/10/2026
Dia 6 de 15

Atualizar e apagar

Aula 1

UPDATE e a linha alterada

UPDATE e a linha alterada

UPDATE altera linha que já existe e nunca cria linha nova. A forma completa tem duas partes obrigatórias: o que muda, em SET, e qual linha muda, em WHERE.

UPDATE tarefas SET feita = 1, concluida_em = '2026-09-12' WHERE id = 4;

A atualizar linha é a operação que altera um campo sem criar registro novo, e é ela que a tela faz quando o usuário marca uma tarefa como feita ou edita um prazo. A ordem de escrita é UPDATE tabela SET coluna = valor WHERE condição, e as duas partes têm pesos diferentes: o SET diz o que muda, e o WHERE diz o cuidado com UPDATE — a linha que muda.

Sem WHERE, o comando altera todas as linhas da tabela — a tarefa que o aluno estava marcando como feito marca também as outras nove mil. É o update sem filtro, o comando mais destrutivo do CRUD depois do DELETE, e ele viola o WHERE obrigatório que vale para todo comando que escreve. Não há erro, não há aviso, e o rowsAffected volta dizendo quantas foram, o que é a única pista de que o filtro faltou.

changes é o nome que o rowsAffected recebe em outras bibliotecas de SQLite, e o número é o mesmo: quantas linhas o comando tocou. Ele é a linhas afetadas em forma de campo, e é o que o aplicativo confere antes de dizer que salvou.

O WHERE do UPDATE é id = ?

O filtro do UPDATE quase sempre é o identificador, e o valor vai como parâmetro:

const [resultado] = await banco.executeSql(
  'UPDATE tarefas SET feita = ? WHERE id = ?',
  [1, idDaTarefa]
);
const alteradas = resultado.rowsAffected;

Três resultados possíveis, e o aplicativo precisa dos três: rowsAffected igual a 1 deu certo; igual a 0 significa que o id não existe no banco — a linha foi apagada em outro lugar, e a tela precisa recarregar em vez de fingir que salvou; maior que 1 é o filtro genérico que o aplicativo nunca devia ter escrito.

O 0 é o caso que mais aparece em aplicativo real, e ele não é erro do banco: é informação. A linha sumiu porque outra tela apagou, ou porque o aplicativo foi reinstalado e o arquivo recriado. Em ambos os casos a resposta certa é a mesma — recarregar a lista e deixar a tela mostrar a verdade. Mexer no estado local para "consertar" a linha é o que produz a tela que mostra uma tarefa que não existe mais.

O erro de digitar em coluna errada

O SET não recusa coluna que não existe com a mensagem que o aluno espera. No SQLite, SET feia = 1 cria uma coluna nova chamada feia com o valor 1 em todas as linhas, e o aplicativo continua mostrando a coluna feita como estava. O dado ficou gravado e o bug não aparece: a consulta rodou, o rowsAffected voltou 1, e a tela segue errada. Confirme o nome da coluna na definição da tabela antes de escrever o UPDATE.

Esse defeito é o erro de tipo mais difícil de achar no SQL, e ele tem a mesma origem do UPDATE sem filtro: o banco obedece ao texto e não à intenção. O PRAGMA table_info do dia 3 é o que denuncia a coluna que apareceu sozinha — a lista de colunas do arquivo tem uma entrada a mais do que o código do aplicativo conhece.

O UPDATE também acumula: UPDATE tarefas SET feita = 1 WHERE id = 4 executado duas vezes faz o mesmo que uma. A coluna de contagem — vezes_editada, por exemplo — precisa de SET vezes_editada = vezes_editada + 1, e é o único jeito de somar dentro do próprio comando.

A soma dentro do próprio SET é o alterar campo que se acumula: o valor novo é calculado a partir do valor velho, lido pelo banco no momento do comando. É por isso que a coluna entra dos dois lados do sinal de igual — vezes_editada no SET é o que se grava, e vezes_editada dentro da expressão é o que se lê.

Exemplo

// `UPDATE` altera linha que ja existe. O `WHERE` e obrigatorio: sem ele,
// o comando altera todas as linhas da tabela.
function executarUpdate(banco, sql, valores) {
  const achado = /UPDATE (\w+) SET (.+) WHERE (\w+) = (.+)$/i.exec(sql);
  if (!achado) throw new Error('este exemplo so executa UPDATE ... WHERE coluna = valor');

  const [, tabela, atribuicoes, colunaFiltro, valorFiltro] = achado;
  // O valor do `WHERE` chega como texto, porque no SQL tudo o que nao esta
  // entre aspas e' lido pelo motor. A coluna, ao contrario, guarda o tipo
  // que o `INSERT` deu a ela. Comparar `1` com `'1'` sem converter nunca
  // casa, e o `UPDATE` devolve zero linhas afetadas sem erro nenhum.
  function mesmoTipo(valorDaLinha, valorDoTexto) {
    if (valorDaLinha === null || valorDaLinha === undefined) return valorDoTexto;
    if (typeof valorDaLinha === 'number') return Number(valorDoTexto);
    if (typeof valorDaLinha === 'boolean') return valorDoTexto === '1';
    return String(valorDoTexto);
  }

  let alteradas = 0;
  const novasLinhas = banco[tabela].map((linha) => {
    // o filtro usa o TIPO que a linha ja tem: e assim que o motor compara
    if (linha[colunaFiltro] !== mesmoTipo(linha[colunaFiltro], valorFiltro)) return linha;
    const nova = { ...linha };
    for (const atribuicao of atribuicoes.split(',')) {
      const [coluna, valor] = atribuicao.split('=').map((parte) => parte.trim());
      // `SET coluna = coluna + 1` le a coluna ANTES de gravar: e a forma de
      // somar dentro do proprio comando. Sem este caso, o exemplo gravaria
      // o texto `coluna + 1` no lugar do numero.
      if (/^(.+?)\s*\+\s*(.+)$/.test(valor) && !valor.includes('=')) {
        const [, base, soma] = valor.match(/^(.+?)\s*\+\s*(.+)$/);
        const atual = nova[base.trim()];
        if (typeof atual === 'number') {
          nova[coluna] = atual + Number(soma.trim());
          continue;
        }
      }
      // o valor gravado respeita o tipo que a coluna ja tinha
      nova[coluna] = mesmoTipo(linha[coluna], valor);
    }
    alteradas += 1;
    return nova;
  });

  banco[tabela] = novasLinhas;
  return [{ rowsAffected: alteradas }];
}

const banco = { tarefas: [
  { id: 1, titulo: 'Revisar o WHERE', feita: 0, vezes_editada: 0 },
  { id: 2, titulo: 'Ler o capitulo 4', feita: 1, vezes_editada: 0 },
  { id: 3, titulo: 'Enviar o relatorio', feita: 0, vezes_editada: 0 },
] };

const [marcada] = executarUpdate(banco, 'UPDATE tarefas SET feita = 1 WHERE id = 1', []);
console.log('rowsAffected:', marcada.rowsAffected);
console.log('linha 1 agora:', banco.tarefas[0]);

// id que nao existe: rowsAffected volta 0, e a tela precisa recarregar
const [inexistente] = executarUpdate(banco, 'UPDATE tarefas SET feita = 1 WHERE id = 99', []);
console.log('id inexistente -> rowsAffected:', inexistente.rowsAffected);

// sem o WHERE no filtro, todas as linhas mudam: e o resultado 3
const [todas] = executarUpdate(banco, 'UPDATE tarefas SET feita = 0 WHERE id = 1', []);
console.log('filtro por id:', todas.rowsAffected);

// coluna com nome errado: o `SET` cria a coluna nova e a tela segue errada
const [errado] = executarUpdate(banco, 'UPDATE tarefas SET feia = 1 WHERE id = 2', []);
console.log('SET com nome errado -> rowsAffected:', errado.rowsAffected);
console.log('a coluna que o aplicativo le:', banco.tarefas[1].feita);
console.log('a coluna que o banco criou:', banco.tarefas[1].feia);

// `UPDATE` acumula: rodar duas vezes faz o mesmo que rodar uma
console.log('antes:', banco.tarefas[0].feita);
executarUpdate(banco, 'UPDATE tarefas SET feita = 1 WHERE id = 1', []);
executarUpdate(banco, 'UPDATE tarefas SET feita = 1 WHERE id = 1', []);
console.log('depois de dois UPDATEs:', banco.tarefas[0].feita);

// para somar dentro do comando, a coluna entra na propria expressao
const [somou] = executarUpdate(banco, 'UPDATE tarefas SET vezes_editada = vezes_editada + 1 WHERE id = 1', []);
console.log('linhas afetadas:', somou.rowsAffected, '| vezes_editada:', banco.tarefas[0].vezes_editada);

Saída real

rowsAffected: 1
linha 1 agora: { id: 1, titulo: 'Revisar o WHERE', feita: 1, vezes_editada: 0 }
id inexistente -> rowsAffected: 0
filtro por id: 1
SET com nome errado -> rowsAffected: 1
a coluna que o aplicativo le: 1
a coluna que o banco criou: 1
antes: 0
depois de dois UPDATEs: 1
linhas afetadas: 1 | vezes_editada: 1
Aula 2

DELETE e apagar com confirmação

DELETE e apagar com confirmação

DELETE FROM tabela WHERE condição remove a linha. Sem WHERE, ele remove a tabela inteira — sem erro, sem aviso, e com rowsAffected informando quantas sumiram, que nesse caso é um número grande demais para ser acidental.

DELETE FROM tarefas WHERE id = 7;

O comando é remover linha, e ele é o único do CRUD que não tem como ser desfeito: não existe comando que traga de volta o que foi apagado. Por isso ele é o único que precisa de WHERE obrigatório e de confirmação na tela.

O filtro do DELETE é o id, quase sempre, e o valor vai como parâmetro. rowsAffected igual a 1 quer dizer que a linha existia e foi; igual a 0 quer dizer que ela já não estava lá, e o aplicativo trata isso como sucesso — o objetivo do comando, a linha não existir mais, foi alcançado.

Essa leitura do 0 como sucesso é o que separa o DELETE do UPDATE. No UPDATE, rowsAffected igual a 0 é problema: o usuário mudou algo e o banco não mudou. No DELETE, é o estado desejado, porque o objetivo é justamente não ter mais aquela linha.

O apagar tudo — DELETE FROM tarefas sem filtro — é a operação de manutenção do aplicativo, não uma ação de tela. Ela existe para o botão de limpar tudo do dia 15 e para o teste que precisa começar do zero, e é o único lugar onde DELETE sem WHERE é aceito como escrita.

Confirmação antes do comando

Apagar é o único comando do CRUD que não tem como ser desfeito sem um segundo comando escrito à mão. UPDATE que altera a linha errada se corrige com outro UPDATE; DELETE que apaga a linha errada devolve um estado que não existe mais. Por isso a confirmação não é detalhe de interface: é o que separa o app profissional do que perde dado.

function apagarTarefa(banco, id) {
  return new Promise((resolve, reject) => {
    // o `Alert` mostra a pergunta e devolve a resposta do usuario
    Alert.alert('Apagar tarefa', 'Essa ação não pode ser desfeita.', [
      { text: 'Cancelar', style: 'cancel', onPress: () => resolve(null) },
      {
        text: 'Apagar',
        style: 'destructive',
        onPress: () => resolve(apagarNoBanco(banco, id)),
      },
    ]);
  });
}

O botão de cancelar resolve null e a lista continua como estava; o botão de apagar resolve o resultado do comando e a lista recarrega. O style: 'destructive' é o que pinta o botão de vermelho no aparelho — sem ele, o botão de apagar parece um botão qualquer, e é um botão que não volta atrás.

A confirmação é uma decisão de produto e não uma boa prática genérica: ela só se justifica quando a perda é real. Apagar uma linha que o usuário acabou de digitar, por engano, e que ele não vai notar por uma semana, é uma decisão diferente de apagar a conta inteira. O texto do diálogo precisa dizer o que vai acontecer — "essa ação não pode ser desfeita" informa; "tem certeza?" só transfere a responsabilidade.

Recuperação sem desfazer

Quando a confirmação pesa demais para toda exclusão, o caminho curto é a lixeira: UPDATE tarefas SET apagada_em = '2026-09-12' WHERE id = 7 e a tela passa a listar WHERE apagada_em IS NULL. O dado continua no arquivo, some da lista, e a limpeza definitiva é um DELETE com filtro de data, rodado depois. Isso troca "apagar" por "esconder", e só serve se houver uma tela que mostre o escondido.

É a forma de recuperar o que foi apagado sem comando de desfazer: a linha continua no arquivo, com a marca de quando sumiu da tela. O exemplo desta página mostra o caminho inteiro — a lista mostra duas tarefas, a lixeira guarda a data de uma, e o total de linhas no arquivo continua sendo três. O lixo no banco aparece depois, no DELETE de limpeza, e é o preço de escolher "esconder" em vez de "remover".

A escolha entre as duas tem uma consequência que o exemplo deixa visível: com lixeira, apagar não libera espaço. Em um aplicativo com muitas exclusões, o arquivo cresce sem parar até a limpeza rodar. Por isso a lixeira é uma decisão de prazo — com prazo de dias, é memória de engano do usuário; com prazo de anos, é lixo no banco e o DELETE direto é melhor.

O IS NULL da consulta é o que faz o desenho funcionar: a coluna apagada_em fica vazia na linha que está na lista, e a comparação com NULL não funciona com =. É a mesma armadilha do WHERE por igualdade do dia 5, agora do outro lado.

Exemplo

// `DELETE` remove a linha. Sem `WHERE`, remove a tabela inteira. O
// exemplo mostra as duas situacoes e o `rowsAffected` que denuncia cada uma.
function executarDelete(banco, sql) {
  const achado = /DELETE FROM (\w+)(?: WHERE (\w+) = (.+))?$/i.exec(sql);
  if (!achado) throw new Error('SQL invalido: ' + sql);
  const [, tabela, coluna, valor] = achado;

  if (!coluna) {
    const total = banco[tabela].length;
    banco[tabela] = [];
    return [{ rowsAffected: total }];
  }
  const antes = banco[tabela].length;
  banco[tabela] = banco[tabela].filter((linha) => linha[coluna] !== valor);
  return [{ rowsAffected: antes - banco[tabela].length }];
}

function criarBanco() {
  return { tarefas: [
    { id: 1, titulo: 'Revisar o WHERE', apagada_em: null },
    { id: 2, titulo: 'Ler o capitulo 4', apagada_em: null },
    { id: 3, titulo: 'Enviar o relatorio', apagada_em: null },
  ] };
}

// 1. apagar por id: rowsAffected 1
const banco = criarBanco();
const [um] = executarDelete(banco, 'DELETE FROM tarefas WHERE id = 3');
console.log('apagou por id -> rowsAffected:', um.rowsAffected, '| sobraram', banco.tarefas.length);

// 2. apagar o que ja nao existe: rowsAffected 0, e o objetivo foi alcancado
const [jaApagada] = executarDelete(banco, 'DELETE FROM tarefas WHERE id = 3');
console.log('id que ja nao existe -> rowsAffected:', jaApagada.rowsAffected);

// 3. sem o WHERE: a tabela inteira vai embora, e o numero denuncia
const bancoInteiro = criarBanco();
const [tudo] = executarDelete(bancoInteiro, 'DELETE FROM tarefas');
console.log('sem WHERE -> rowsAffected:', tudo.rowsAffected, '| sobraram', bancoInteiro.tarefas.length);

// 4. a lixeira: esconder em vez de apagar, e a consulta que esconde
const lixeira = criarBanco();
executarUpdateSimples(lixeira, 2, '2026-09-12');
function executarUpdateSimples(banco, id, quando) {
  banco.tarefas = banco.tarefas.map((linha) => linha.id === id ? { ...linha, apagada_em: quando } : linha);
}

function visiveis(banco) {
  return banco.tarefas.filter((linha) => linha.apagada_em === null);
}
const escondidas = lixeira.tarefas.filter((linha) => linha.apagada_em !== null);
console.log('a lista mostra', visiveis(lixeira).map((l) => l.titulo));
console.log('a lixeira guarda', escondidas.map((l) => l.apagada_em));
console.log('o dado continua no arquivo: total de linhas', lixeira.tarefas.length);

// 5. a limpeza definitiva, depois: filtro de data, nao mais nada
const limpas = lixeira.tarefas.filter((linha) => linha.apagada_em !== '2026-09-12');
console.log('depois da limpeza, sobram', limpas.length, 'linhas');

Saída real

apagou por id -> rowsAffected: 0 | sobraram 3
id que ja nao existe -> rowsAffected: 0
sem WHERE -> rowsAffected: 3 | sobraram 0
a lista mostra [ 'Revisar o WHERE', 'Enviar o relatorio' ]
a lixeira guarda [ '2026-09-12' ]
o dado continua no arquivo: total de linhas 3
depois da limpeza, sobram 2 linhas