Dia 5 — Consultar: SELECT

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

Consultar: SELECT

Aula 1

SELECT e a leitura das linhas

SELECT e a leitura das linhas

SELECT lê, INSERT grava. A forma mais simples é SELECT * FROM tarefas, e o resultado não é uma lista: é um objeto com rows, um array vazio quando nada casou com o filtro e as linhas quando casou.

const [resultado] = await banco.executeSql('SELECT * FROM tarefas');
const linhas = resultado.rows;
const quantas = resultado.rows.length;

if (linhas.length === 0) {
  // tela vazia: o app precisa ter um estado para esse caso
}

O nome da propriedade é rows, e dentro dela está a linha. O .length é o número de linhas que casaram, e é ele que decide entre a tela com dado e a tela vazia. Ler é consultar o banco, e consultar é o verbo do trimestre: a mesma palavra que dá nome ao SELECT.

O array vazio é resultado normal, não erro. A consulta que não acha nada rodou com sucesso e por isso não rejeita a promessa — o aplicativo é quem decide o que a tela vazia mostra. Tratar o "não achei" como falha é o erro que faz a tela piscar e sumir.

A distinção entre "não achou" e "deu erro" é a que o try e o catch separam, e é ela que a tela precisa preservar: no primeiro caso o catch não roda, o rows chega vazio e a tela mostra o estado vazio; no segundo o catch roda e a tela mostra a mensagem de falha. Tratar os dois com o mesmo código é a causa mais comum de tela que pisca ao abrir.

Mapear resultado para a tela

O que volta do SELECT * traz o nome de toda coluna, e o que a tela precisa costuma ser um objeto menor. A etapa de mapear resultado é onde o banco vira dado de aplicativo:

const [resultado] = await banco.executeSql('SELECT * FROM tarefas ORDER BY prazo');
const tarefas = resultado.rows.map((linha) => ({
  id: linha.id,
  titulo: linha.titulo,
  prazo: linha.prazo,
  feita: linha.feita === 1,
}));

Duas decisões acontecem nessa linha. A primeira é linha.feita === 1: o banco devolve inteiro e o estado do aplicativo é booleano; sem o === 1, o valor 1 e a string '0' passam pelo filtro da tela com resultado errado. A segunda é nomear as colunas na consulta: SELECT id, titulo, prazo, feita FROM tarefas devolve exatamente o que a tela usa, e a consulta documenta a si mesma.

O exemplo desta página mostra o mapear resultado com a entrada e a saída lado a lado: as três linhas do banco entram no map e saem como três objetos com feita já booleano — pendente, feito, pendente. É essa a transformação que a tela consome, e ela acontece antes do setState, nunca dentro dele.

O mapeamento é também o ponto de trazer para a tela: enquanto o dado é linha de banco, ele pertence ao SQL; depois do map, ele é objeto de aplicativo, e a partir desse ponto nenhuma tela precisa saber que existe um banco no projeto.

Uma linha só

SELECT * FROM tarefas WHERE id = ? com o id da linha devolve uma linha, e resultado.rows[0] é ela — a primeira linha do resultado. Quando a linha não existe, rows é um array vazio e rows[0] é undefined: o acesso direto quebra o aplicativo com erro de propriedade de undefined, e o certo é conferir o tamanho antes de ler linha.

A checagem que resolve é a comparação com 0, e ela é o que separa "não existe" de "existe". O rows[0] de uma consulta que não achou nada é undefined, e ler .titulo de undefined é o erro que o aluno vê como TypeError na tela de edição:

const [resultado] = await banco.executeSql('SELECT * FROM tarefas WHERE id = ?', [idProcurado]);
const linha = resultado.rows.length > 0 ? resultado.rows[0] : null;

Para várias linhas, a forma do aplicativo é sempre a mesma: consulta, mapeia para estado, setTarefas com o array novo. A tela nunca lê do banco sozinha; ela lê do estado, e quem encheu o estado foi a consulta.

Exemplo

// `SELECT` devolve um objeto com `rows`: um array de linhas. O array
// vazio e resultado normal de uma consulta que nao achou nada.
function executarSelect(sql, banco) {
  const achado = /SELECT (.+) FROM (\w+)(.*)$/i.exec(sql);
  if (!achado) throw new Error('SQL invalido: ' + sql);
  // o que sobra depois do nome da tabela e o filtro: e o `WHERE` que decide
  // quais linhas entram no resultado
  const [, listaDeColunas, tabela, resto] = achado;
  const nomes = listaDeColunas === '*' ? null : listaDeColunas.split(',').map((c) => c.trim());

  let linhas = [...banco[tabela]];

  const filtro = /WHERE (\w+) = (.+)$/i.exec(resto);
  if (filtro) {
    const [, coluna, valor] = filtro;
    linhas = linhas.filter((linha) => {
      // o valor do `WHERE` chega como texto e a coluna guarda o tipo que
      // o `INSERT` deu a ela: comparar `1` com `'1'` sem converter nunca
      // casa. E o filtro que precisa ser lido: ignorado, `WHERE id = 99`
      // devolve a tabela inteira e a tela inventa tarefa que nao existe.
      const daLinha = linha[coluna];
      const doTexto = typeof daLinha === 'number' ? Number(valor)
        : typeof daLinha === 'boolean' ? valor === '1'
        : String(valor);
      return daLinha === doTexto;
    });
  }

  if (nomes) linhas = linhas.map((linha) => {
    const nova = {};
    for (const nome of nomes) nova[nome] = linha[nome];
    return nova;
  });
  return [{ rows: linhas }];
}

const banco = {
  tarefas: [
    { id: 1, titulo: 'Revisar o WHERE', prazo: '2026-09-10', feita: 0 },
    { id: 2, titulo: 'Ler o capitulo 4', prazo: '2026-09-12', feita: 1 },
    { id: 3, titulo: 'Enviar o relatorio', prazo: '2026-09-08', feita: 0 },
  ],
};

const [resultado] = executarSelect('SELECT * FROM tarefas', banco);
console.log('rows e do tipo', Array.isArray(resultado.rows) ? 'array' : typeof resultado.rows);
console.log('numero de linhas:', resultado.rows.length);
console.log('primeira linha:', resultado.rows[0]);

// a consulta que nao acha nada devolve array vazio, e nao erro
const [vazio] = executarSelect('SELECT * FROM tarefas WHERE id = 99', banco);
console.log('consulta sem resultado:', vazio.rows, '| tamanho', vazio.rows.length);

// mapear a linha do banco para o dado que a tela usa: `feita` chega como
// inteiro e sai do mapeamento como booleano
const tarefas = resultado.rows.map((linha) => ({
  id: linha.id,
  titulo: linha.titulo,
  prazo: linha.prazo,
  feita: linha.feita === 1,
}));
console.log('mapeado:', tarefas.map((t) => t.feita ? 'feito' : 'pendente'));

// uma linha so: `rows[0]` e ela, e `rows[0]` de uma consulta vazia e undefined
console.log('rows[0] da consulta com id 99:', executarSelect('SELECT * FROM tarefas WHERE id = 99', banco)[0].rows[0]);

// nomear as colunas devolve so o que a tela usa
const [curtas] = executarSelect('SELECT id, titulo FROM tarefas', banco);
console.log('colunas nomeadas:', curtas.rows[0]);

Saída real

rows e do tipo array
numero de linhas: 3
primeira linha: { id: 1, titulo: 'Revisar o WHERE', prazo: '2026-09-10', feita: 0 }
consulta sem resultado: [] | tamanho 0
mapeado: [ 'pendente', 'feito', 'pendente' ]
rows[0] da consulta com id 99: undefined
colunas nomeadas: { id: 1, titulo: 'Revisar o WHERE' }
Aula 2

WHERE: filtrar o que vem

WHERE: filtrar o que vem

O WHERE é a cláusula que transforma uma consulta em outra. Fica depois do FROM e recebe uma condição que é avaliada linha por linha: a linha que satisfaz volta, a que não fica de fora.

SELECT * FROM tarefas WHERE feita = 0;

Os operadores de comparação são =, != (e o equivalente <>), >, <, >= e <=. Em conjunto: AND, OR e NOT, com parênteses quando a combinação tem precedência que muda o resultado — WHERE feita = 0 AND prazo < '2026-11-01' e WHERE feita = 0 OR prazo < '2026-11-01' devolvem conjuntos diferentes justamente porque o AND e mais forte.

A condição é o único texto que separa uma consulta da outra, e é por isso que ela fica no meio: o nome da tabela vem antes, a ordenação vem depois. Quando o WHERE cresce até caber numa tela, ele costuma ser o sinal de que falta uma tabela de ligação ou uma coluna que resolve a pergunta.

Dois operadores que resolvem perguntas que = não resolve: IN recebe a lista, WHERE id IN (1, 3, 7), e LIKE casa padrão de texto com % no lugar de qualquer sequência.

SELECT * FROM tarefas WHERE titulo LIKE 'Revisar%';
SELECT * FROM tarefas WHERE prazo < '2026-11-01' ORDER BY prazo ASC LIMIT 5;

Texto em LIKE e em comparação de data vai entre aspas simples; número, sem. prazo < '2026-11-01' funciona porque o SQLite compara texto ISO — AAAA-MM-DD ordena igual à ordem cronológica, desde que o formato não mude. O dia por extenso quebra isso.

O formato de data é a armadilha mais cara dessa aula, porque a coluna foi criada como TEXT e aceita qualquer coisa. '10/09/2026' é aceito sem erro, '< compara letra por letra, e a ordenação fica em ordem de dia em vez de ordem de mês. A comparação funciona quando o formato é constante, e quebra quando alguém digita no formato do país dele.

ORDER BY, ASC, DESC e LIMIT

ORDER BY ordena o resultado, ASC é o padrão e DESC inverte. LIMIT corta no fim. As três juntas formam a paginação de consulta: LIMIT 20 OFFSET 40 traz a terceira página de vinte linhas, e OFFSET sozinho com ordenação estável dá sempre a mesma página.

SELECT * FROM tarefas
WHERE feita = 0
ORDER BY prazo ASC
LIMIT 20 OFFSET 40;

A ordem das três cláusulas é fixa: WHERE filtra, ORDER BY organiza, LIMIT corta. Inverter produz erro de sintaxe, e o LIMIT antes do ORDER BY produz resultado sem sentido — o corte acontece antes da ordenação, e o que volta é as vinte primeiras linhas na ordem em que o banco as guardou.

Ordenar por coluna indexada é barato; sem índice, o motor compara todas as linhas e depois descarta. É por isso que o ORDER BY entra na lista de colunas que merecem índice, e não só o WHERE.

O exemplo desta página roda os filtros sobre cinco tarefas e imprime o conjunto que cada condição devolve: feita = 0, a comparação de prazo, a condição composta com AND, a lista com IN e o padrão de texto com LIKE. O ORDER BY prazo LIMIT 2 OFFSET 2 fecha a página mostrando o recorte no meio da lista, que é a posição em que a paginação real começa.

Comparar o filtro no código

A mesma condição em JavaScript é um filter com uma função que devolve booleano. A vantagem do WHERE não é a velocidade do laço: é que a condição é escrita uma vez, no texto, e serve para qualquer consulta — inclusive as que rodam sem passar pelo aplicativo. O filter no JavaScript tem o mesmo resultado, com o filtro do lado da tela.

A diferença que aparece no volume: com cinco linhas, as duas formas dão a mesma resposta instantaneamente. Com cinco mil, o WHERE entrega só o que casou e o filter entrega as cinco mil ao JavaScript para ele descartar quase todas. E há um caso em que só o WHERE resolve: quando a consulta não é do aplicativo — um relatório rodado direto no banco, ou outra tela que precisa do mesmo filtro sem passar pelo seu código.

Exemplo

// `WHERE` filtra linha por linha: a linha que satisfaz a condicao volta,
// a que nao satisfaz fica de fora. O mesmo filtro, escrito no texto da
// consulta e escrito em JavaScript, devolve o mesmo conjunto.
function valorDe(texto, linha) {
  const alvo = String(texto).trim();
  if (/^\d+$/.test(alvo)) return Number(alvo);
  if (alvo.startsWith("'")) return alvo.slice(1, -1);
  if (linha && alvo in linha) return linha[alvo];
  return alvo;
}

function compara(esquerda, operador, direita) {
  if (operador === '=') return esquerda === direita;
  if (operador === '!=' || operador === '<>') return esquerda !== direita;
  if (operador === '>') return esquerda > direita;
  if (operador === '<') return esquerda < direita;
  if (operador === '>=') return esquerda >= direita;
  if (operador === '<=') return esquerda <= direita;
  if (operador === 'LIKE') {
    return new RegExp('^' + String(direita).replace(/%/g, '.*') + '$').test(String(esquerda));
  }
  if (operador === 'IN') {
    return String(direita).replace(/[()']/g, '').split(',').map((v) => v.trim()).includes(esquerda);
  }
  throw new Error('operador desconhecido: ' + operador);
}

function satisfaz(condicao, linha) {
  return condicao.split(/\s+AND\s+/).every((parte) => {
    const achado = parte.trim().match(/^(\w+)\s*(>=|<=|!=|<>|=|>|<|LIKE|IN)\s*(.+)$/i);
    if (!achado) throw new Error('condicao nao reconhecida: ' + parte);
    const [, coluna, operador, bruto] = achado;
    return compara(valorDe(bruto, linha), operador.toUpperCase(), valorDe(bruto, linha));
  });
}

const banco = {
  tarefas: [
    { id: 1, titulo: 'Revisar o WHERE', prazo: '2026-09-10', feita: 0 },
    { id: 2, titulo: 'Ler o capitulo 4', prazo: '2026-09-12', feita: 1 },
    { id: 3, titulo: 'Enviar o relatorio', prazo: '2026-09-08', feita: 0 },
    { id: 4, titulo: 'Comprar cafe', prazo: '2026-09-07', feita: 0 },
    { id: 5, titulo: 'Revisar o GROUP BY', prazo: '2026-09-11', feita: 0 },
  ],
};

function consultar(consulta) {
  let linhas = [...banco[consulta.from]];
  if (consulta.where) linhas = linhas.filter((linha) => satisfaz(consulta.where, linha));
  if (consulta.orderBy) {
    linhas.sort((a, b) => {
      const x = a[consulta.orderBy], y = b[consulta.orderBy];
      return x === y ? 0 : (x > y ? 1 : -1) * (consulta.desc ? -1 : 1);
    });
  }
  if (consulta.limit) linhas = linhas.slice(consulta.offset || 0, (consulta.offset || 0) + consulta.limit);
  return linhas;
}

const pendentes = consultar({ from: 'tarefas', where: 'feita = 0' });
console.log('WHERE feita = 0 devolveu', pendentes.length, 'de', banco.tarefas.length);

const vencidas = consultar({ from: 'tarefas', where: "prazo <= '2026-09-09'" });
console.log("prazo <= '2026-09-09':", vencidas.map((l) => l.titulo));

const composta = consultar({ from: 'tarefas', where: "feita = 0 AND prazo <= '2026-09-09'" });
console.log('feita = 0 AND prazo <= 09-09:', composta.map((l) => l.titulo));

const conjunto = consultar({ from: 'tarefas', where: 'id IN (1, 3, 5)' });
console.log('id IN (1, 3, 5):', conjunto.map((l) => l.id));

const comLike = consultar({ from: 'tarefas', where: "titulo LIKE 'Revisar%'" });
console.log("titulo LIKE 'Revisar%':", comLike.map((l) => l.titulo));

const primeiraPagina = consultar({ from: 'tarefas', where: 'feita = 0', orderBy: 'prazo', limit: 2, offset: 2 });
console.log('ORDER BY prazo LIMIT 2 OFFSET 2:', primeiraPagina.map((l) => l.prazo));

Saída real

WHERE feita = 0 devolveu 5 de 5
prazo <= '2026-09-09': [
  'Revisar o WHERE',
  'Ler o capitulo 4',
  'Enviar o relatorio',
  'Comprar cafe',
  'Revisar o GROUP BY'
]
feita = 0 AND prazo <= 09-09: [
  'Revisar o WHERE',
  'Ler o capitulo 4',
  'Enviar o relatorio',
  'Comprar cafe',
  'Revisar o GROUP BY'
]
id IN (1, 3, 5): []
titulo LIKE 'Revisar%': [
  'Revisar o WHERE',
  'Ler o capitulo 4',
  'Enviar o relatorio',
  'Comprar cafe',
  'Revisar o GROUP BY'
]
ORDER BY prazo LIMIT 2 OFFSET 2: [ '2026-09-10', '2026-09-11' ]