SQL no MySQL e MariaDB

O MySQL é o banco de dados por trás de muitos sites que você usa: lojas virtuais, blogs, sistemas de escola. O MariaDB é o irmão dele, feito pelos mesmos criadores, e é o que o Debian instala quando alguém pede "o MySQL". Nesta aula a Ana instala o MariaDB no notebook dela, cria um usuário, leva a Loja Aurora para lá e descobre o que muda no SQL que ela aprendeu no SQLite: algumas coisas melhoram, outras pegam de surpresa (por exemplo, 'ana' é igual a 'ANA' lá).

Você vai aprender
  • A diferença entre MySQL e MariaDB, e por que o Debian instala o MariaDB.
  • Instalar, criar um banco e um usuário, e entrar com mariadb -u ana -p loja.
  • Guardar a senha com segurança no ~/.my.cnf e rodar um arquivo .sql.
  • Os comandos do terminal: SHOW TABLES, DESCRIBE, \G, SOURCE, quit (os "comandos de ponto" deles).
  • O que muda no SQL: LIMIT, crase nos nomes, AUTO_INCREMENT, CONCAT, datas, booleanos, maiúsculas e acentos, ON DUPLICATE KEY UPDATE e o modo estrito.
  • As diferenças entre o próprio MySQL e o MariaDB, e os erros mais comuns com o número de cada um.
O que é real nesta aula

Todas as sessões de terminal são reais: rodaram num MariaDB 11.8 (a versão do Debian 13) na máquina da Ana, com a Loja Aurora. O MySQL da Oracle nós não rodamos: o que é só dele está marcado e foi conferido no manual oficial do MySQL 8.4 (dev.mysql.com). Os quadros "Resultado" continuam sendo o SQLite do seu navegador, para comparar.


A ideia: dois irmãos

O MySQL nasceu nos anos 1990 e virou um dos bancos mais usados na internet. Hoje ele pertence à Oracle. Os desenvolvedores originais do MySQL criaram o MariaDB, que o site oficial descreve como "feito pelos desenvolvedores originais do MySQL e garantido como código aberto" (licença GPLv2). No começo ele era uma troca direta pelo MySQL; com os anos, cada um ganhou recursos próprios.

Pense em dois irmãos gêmeos que foram morar em cidades diferentes: continuam falando igual, mas cada um pegou algumas gírias novas. Para quem está começando, 90% do que você faz é igual nos dois. Nesta aula, quando algo for diferente entre eles, vai aparecer escrito.

MySQLMariaDB
Quem fazOraclea comunidade MariaDB, com os desenvolvedores originais do MySQL
Programa de terminalmysql, prompt mysql>mariadb, prompt MariaDB [banco]> (no Debian, mysql também funciona: é um atalho para o mariadb)
No Debian 13não vem no Debian; a Oracle oferece um repositório APT própriovem no Debian: pacote mariadb-server, versão 11.8

1. Instalar

Diferente do SQLite, que é só um arquivo, o MySQL e o MariaDB são servidores: um programa fica rodando o tempo todo, guardando os bancos, e você conversa com ele por um programa cliente. Para entender bem essa diferença, veja Os Bancos Famosos.

No Debian ou no Ubuntu, o MariaDB vem do próprio apt (estes comandos são ilustrativos: não instalamos pacotes na máquina da Ana para esta aula, porque ela já tinha o MariaDB):

sudo apt update
sudo apt install mariadb-server

O pacote mariadb-server traz o servidor e, junto, o cliente mariadb. Para o MySQL da Oracle, o manual do MySQL 8.4 diz que o jeito preferido no Debian é o repositório APT do MySQL (um repositório extra que você adiciona ao apt, baixado em dev.mysql.com); depois disso o pacote se chama mysql-server.


2. Criar o banco e o usuário

Num servidor, cada pessoa entra com um usuário e uma senha, e cada usuário só mexe no que tem permissão (aula Usuários e Permissões). Logo depois de instalar, só existe o administrador, o root. No MariaDB do Debian, o root entra pelo próprio usuário do sistema: a documentação do MariaDB diz que a conta 'root'@'localhost' usa por padrão o plugin unix_socket, que confere quem você é no Linux em vez de pedir senha. Então a Ana digitou sudo mariadb (o sudo pediu a senha dela, do Linux) e, já dentro, criou o banco e o usuário:

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 32
Server version: 11.8.6-MariaDB-0+deb13u1 from Debian -- Please help get to 10k stars at https://github.com/MariaDB/Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE DATABASE loja;
Query OK, 1 row affected (0,000 sec)

MariaDB [(none)]> CREATE USER 'ana'@'localhost' IDENTIFIED BY 'Loja#Aurora26';
Query OK, 0 rows affected (0,005 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON loja.* TO 'ana'@'localhost';
Query OK, 0 rows affected (0,002 sec)

MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| loja               |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0,000 sec)

MariaDB [(none)]> quit
Bye
MariaDB [(none)]>
O prompt. Entre colchetes fica o banco em uso; (none) quer dizer "nenhum banco escolhido ainda".
CREATE DATABASE loja;
Cria um banco vazio chamado loja. Num servidor cabem muitos bancos; no SQLite, cada banco é um arquivo.
CREATE USER 'ana'@'localhost' IDENTIFIED BY '…';
Cria a usuária ana, que só pode entrar desta mesma máquina (localhost), com essa senha. Escolha uma senha sua, forte.
GRANT ALL PRIVILEGES ON loja.* TO …;
"Conceda todos os privilégios em todas as tabelas do banco loja" para a Ana. Ela não ganha acesso aos outros bancos.
SHOW DATABASES;
Lista os bancos. Os outros quatro são do próprio servidor: não mexa neles.
Query OK, 1 row affected (0,000 sec)
"Comando aceito, 1 linha afetada, em 0 segundos." A vírgula decimal aparece porque o sistema da Ana está em português.

3. Entrar como Ana

Agora a Ana entra com o usuário dela. O comando tem três partes importantes:

$ mariadb --version
mariadb from 11.8.6-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using  EditLine wrapper
$ mariadb -u ana -p loja
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 35
Server version: 11.8.6-MariaDB-0+deb13u1 from Debian -- Please help get to 10k stars at https://github.com/MariaDB/Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [loja]> SHOW TABLES;
+----------------+
| Tables_in_loja |
+----------------+
| categorias     |
| clientes       |
| fornecedores   |
| funcionarios   |
| itens_pedido   |
| pedidos        |
| produtos       |
+----------------+
7 rows in set (0,000 sec)

MariaDB [loja]> DESCRIBE clientes;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| nome       | varchar(100) | NO   |     | NULL    |                |
| email      | varchar(100) | YES  |     | NULL    |                |
| cidade     | varchar(60)  | YES  |     | NULL    |                |
| estado     | char(2)      | YES  |     | NULL    |                |
| nascimento | date         | YES  |     | NULL    |                |
| vip        | tinyint(1)   | NO   |     | 0       |                |
+------------+--------------+------+-----+---------+----------------+
7 rows in set (0,001 sec)

MariaDB [loja]> quit
Bye
-u ana
user: entra como a usuária ana do banco (não é o usuário do Linux).
-p
password: pergunte a senha. Ela aparece como Enter password: ("digite a senha") e não aparece na tela enquanto você digita.
loja
O banco que já fica em uso ao entrar: por isso o prompt mostra [loja].
Reading table information…
O cliente leu os nomes das tabelas e colunas para completar com a tecla Tab. Com -A ele pula isso.
Commands end with ; or \g.
"Os comandos terminam com ; ou \g": igual ao sqlite3, sem o ; o comando não roda.

Este banco já tem as tabelas da Loja Aurora: a Ana as importou de um arquivo, como você vai ver na seção 5. No MySQL da Oracle a sessão é igual, só trocando o comando para mysql -u ana -p loja e o prompt para mysql> (assim aparece no manual do MySQL 8.4); para sair, QUIT ou \q.

E se a senha estiver errada? O servidor recusa, com o erro 1045:

$ mariadb -u ana -p loja
Enter password:
ERROR 1045 (28000): Access denied for user 'ana'@'localhost' (using password: YES)

"Acesso negado para a usuária ana em localhost (usando senha: SIM)". O using password: YES só diz que uma senha foi enviada; não diz se o problema é a senha ou o usuário (de propósito, para não ajudar um invasor).


4. Guardar a senha no ~/.my.cnf

Digitar a senha toda vez cansa. Escrever a senha no próprio comando (-pLoja#Aurora26) é pior: ela fica no histórico do terminal e aparece para outros programas. O manual do MySQL recomenda guardá-la num arquivo de opções na sua pasta pessoal, o ~/.my.cnf, que o MySQL e o MariaDB leem sozinhos:

[client]
user=ana
password="Loja#Aurora26"
[client]
O grupo de opções que todos os programas clientes leem (mariadb, mysql, mariadb-dump…), mas não o servidor.
user=ana
Equivale a -u ana.
password="…"
A senha. As aspas são necessárias aqui porque a senha tem #, e num arquivo de opções o # começa um comentário: sem as aspas, a senha lida seria só Loja e o servidor responderia Access denied.

Depois, deixe o arquivo legível só por você (o manual pede o modo 400 ou 600), e entre sem -u nem -p:

chmod 600 ~/.my.cnf
mariadb loja

Daqui em diante, as sessões da aula usam mariadb loja.


5. Levar a Loja Aurora para o MariaDB

O arquivo aurora.sql do guia foi escrito para o SQLite. Para o MariaDB, a Ana fez o loja-mariadb.sql: os mesmos INSERT (eles funcionam igual), mas com o CREATE TABLE nos tipos do MariaDB. A tabela de clientes ficou assim:

CREATE TABLE clientes (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    nome       VARCHAR(100) NOT NULL,
    email      VARCHAR(100),
    cidade     VARCHAR(60),
    estado     CHAR(2),
    nascimento DATE,
    vip        BOOLEAN NOT NULL DEFAULT FALSE
);

Compare com a do SQLite (aula O Banco da Loja Aurora): TEXT virou VARCHAR(100) (texto de até 100 caracteres), a data virou DATE, o vip virou BOOLEAN e o id ganhou AUTO_INCREMENT. Para rodar um arquivo inteiro de fora do cliente, use o < do terminal; para um comando só, a opção -e (execute):

$ ls *.sql
estoque-baixo.sql  loja-mariadb.sql
$ mariadb loja < loja-mariadb.sql
$ mariadb loja -e "SELECT COUNT(*) AS clientes FROM clientes"
+----------+
| clientes |
+----------+
|       30 |
+----------+

O < loja-mariadb.sql não mostra nada quando dá certo. Os 30 clientes chegaram.


6. Explorando o banco

Os "comandos de ponto" do sqlite3 (aula Comandos de Ponto) não existem aqui. No MySQL e no MariaDB, quase tudo é SQL mesmo (SHOW, DESCRIBE), mais alguns comandos do cliente que começam com \:

$ mariadb loja
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 43
Server version: 11.8.6-MariaDB-0+deb13u1 from Debian -- Please help get to 10k stars at https://github.com/MariaDB/Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [loja]> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| loja       |
+------------+
1 row in set (0,000 sec)

MariaDB [loja]> SELECT * FROM clientes WHERE id = 1\G
*************************** 1. row ***************************
        id: 1
      nome: José Ribamar Silva
     email: jose.ribamar@exemplo.com.br
    cidade: Teresina
    estado: PI
nascimento: 1985-03-12
       vip: 1
1 row in set (0,000 sec)

MariaDB [loja]> SOURCE estoque-baixo.sql
+-----------------------------------+---------+
| nome                              | estoque |
+-----------------------------------+---------+
| Jogo de Panelas 5 peças           |      12 |
| Tesoura de Poda                   |      17 |
| Caixa de Som Portátil             |      18 |
| Tapete de Yoga                    |      19 |
| Vaso de Cerâmica Médio            |      20 |
| Jogo de Tabuleiro Trilha Mística  |      21 |
| Faqueiro Inox 24 peças            |      22 |
| Par de Halteres 2kg               |      24 |
| Teclado Compacto                  |      25 |
| Atlas Geográfico                  |      26 |
| Boneca de Pano Maria Flor         |      27 |
| Guia de Receitas do Nordeste      |      29 |
+-----------------------------------+---------+
12 rows in set (0,000 sec)
SELECT DATABASE();
Mostra o banco em uso. Para trocar: USE outro_banco;.
\G
No lugar do ;, mostra cada linha "em pé": uma coluna por linha. Ótimo para tabelas largas.
SOURCE estoque-baixo.sql
Roda os comandos de um arquivo, como o .read do sqlite3. O arquivo tem um SELECT … WHERE estoque < 30 AND descontinuado = FALSE ORDER BY estoque;.

Se você esquecer o ;, o cliente espera mais texto e mostra ->, como o ...> do sqlite3. Basta digitar o ;:

MariaDB [loja]> SELECT nome FROM clientes WHERE estado = 'PI'
    -> ;
+-----------------------+
| nome                  |
+-----------------------+
| José Ribamar Silva    |
| Maria Clara Mendes    |
| Conceição Prado       |
| Luís Otávio Brito     |
| Antônio Carlos Neves  |
| Tiago Fontes          |
| Camila Duarte         |
| Larissa Macêdo        |
| Ana Carvalho Lima     |
| Sebastião Moura       |
| Henrique Sales        |
| Otávio Bezerra        |
| Paulo Sérgio Andrade  |
| Elias Furtado         |
+-----------------------+
14 rows in set (0,000 sec)
No sqlite3No mariadb / mysqlO que faz
sqlite3 loja.dbmariadb -u ana -p lojaabre o banco
.databasesSHOW DATABASES;lista os bancos
.tablesSHOW TABLES;lista as tabelas
.schema clientesDESCRIBE clientes; ou SHOW CREATE TABLE clientes;mostra a estrutura da tabela
.mode lineterminar com \Guma coluna por linha
.read arquivo.sqlSOURCE arquivo.sqlroda um arquivo
sqlite3 loja.db "SELECT …"mariadb loja -e "SELECT …"um comando só, sem entrar
.quitquit, exit ou \qsai

7. O que muda no SQL

A Ana rodou as consultas que conhece. Quase tudo funcionou igual. As diferenças estão abaixo, cada uma comparada com o SQLite.

7.1 LIMIT: as duas formas

O MySQL e o MariaDB aceitam LIMIT 3 OFFSET 3 e a forma curta LIMIT 3, 3 (primeiro o deslocamento, depois a quantidade; o manual do MySQL explica que a forma com OFFSET existe "por compatibilidade com o PostgreSQL"). O MariaDB, desde a versão 10.6, também aceita a forma do padrão, com OFFSET … ROWS FETCH …; a sintaxe do SELECT do MySQL 8.4 só tem o LIMIT:

MariaDB [loja]> SELECT nome, preco FROM produtos ORDER BY preco DESC LIMIT 3, 3;
+-------------------------+--------+
| nome                    | preco  |
+-------------------------+--------+
| Faqueiro Inox 24 peças  | 119.40 |
| Teclado Compacto        |  99.00 |
| Mochila Escolar Azul    |  89.99 |
+-------------------------+--------+
3 rows in set (0,000 sec)

MariaDB [loja]> SELECT nome, preco FROM produtos ORDER BY preco DESC OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
+-------------------------+--------+
| nome                    | preco  |
+-------------------------+--------+
| Faqueiro Inox 24 peças  | 119.40 |
| Teclado Compacto        |  99.00 |
| Mochila Escolar Azul    |  89.99 |
+-------------------------+--------+
3 rows in set (0,000 sec)

O SQLite também entende LIMIT 3, 3. A diferença que salta aos olhos é o preço: 119.40 lá, 119.4 aqui, porque a coluna no MariaDB é DECIMAL(10,2), que guarda sempre duas casas:

SELECT nome, preco
FROM produtos
ORDER BY preco DESC
LIMIT 3, 3;

7.2 Crase nos nomes; aspas duplas são texto

Nomes de tabela e coluna que precisam de aspas vão entre crases: `nome`. As aspas duplas são texto, iguais às simples (a não ser que o modo ANSI_QUOTES esteja ligado). Por isso SELECT "nome" FROM clientes devolve a palavra "nome" em todas as linhas, sem erro. Isso foi mostrado em As Diferenças entre os Bancos. Outra pegadinha: no Linux, o nome da tabela diferencia maiúsculas (CLIENTES não é clientes), mas o da coluna não.

7.3 AUTO_INCREMENT, LAST_INSERT_ID e RETURNING

Com AUTO_INCREMENT, quem dá o número é o servidor. Para saber que número ele deu, use LAST_INSERT_ID() ("o último id inserido", na sua conexão). O MariaDB também tem o RETURNING, que devolve a linha inserida na hora, como no SQLite; o MySQL 8.4 não tem RETURNING no INSERT (o manual indica o LAST_INSERT_ID()):

MariaDB [loja]> INSERT INTO categorias (nome, descricao) VALUES ('Pet Shop', 'Rações e acessórios');
Query OK, 1 row affected (0,001 sec)

MariaDB [loja]> SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
|                9 |
+------------------+
1 row in set (0,000 sec)

MariaDB [loja]> INSERT INTO categorias (nome) VALUES ('Material de Construção') RETURNING id, nome;
+----+--------------------------+
| id | nome                     |
+----+--------------------------+
| 10 | Material de Construção   |
+----+--------------------------+
1 row in set (0,001 sec)

No SQLite, o equivalente do LAST_INSERT_ID() é last_insert_rowid():

INSERT INTO categorias (nome, descricao) VALUES ('Pet Shop', 'Rações e acessórios');
SELECT last_insert_rowid();

7.4 Juntar textos: CONCAT, não ||

No MySQL e no MariaDB o || é "ou" lógico (o manual do MySQL 8.4 avisa que esse uso está descontinuado). Use CONCAT(a, b, …) ou CONCAT_WS(separador, a, b, …) (with separator, "com separador"). O CONCAT_WS pula os NULL; o CONCAT fica NULL se um pedaço for NULL:

MariaDB [loja]> SELECT CONCAT_WS(' - ', nome, cidade, estado) AS cliente FROM clientes WHERE id IN (1, 7);
+--------------------------------------+
| cliente                              |
+--------------------------------------+
| José Ribamar Silva - Teresina - PI   |
| Luís Otávio Brito - Parnaíba - PI    |
+--------------------------------------+
2 rows in set (0,000 sec)

O SQLite 3.50 tem as mesmas duas funções (desde a 3.44), então este SQL roda nos dois:

SELECT concat_ws(' - ', nome, cidade, estado) AS cliente
FROM clientes
WHERE id IN (1, 7);

7.5 Datas

No MySQL e no MariaDB a data é um tipo de verdade (DATE), com funções próprias: CURDATE() (hoje), NOW() (agora), DATE_ADD(d, INTERVAL 7 DAY), DATEDIFF(fim, início), YEAR(d), MONTH(d), DAY(d) e DATE_FORMAT(d, formato):

MariaDB [loja]> SELECT CURDATE() AS hoje;
+------------+
| hoje       |
+------------+
| 2026-10-18 |
+------------+
1 row in set (0,000 sec)

MariaDB [loja]> SELECT id, DATE_FORMAT(data, '%d/%m/%Y') AS data_br, YEAR(data) AS ano FROM pedidos WHERE id IN (1, 90);
+----+------------+------+
| id | data_br    | ano  |
+----+------------+------+
|  1 | 05/01/2024 | 2024 |
| 90 | 14/10/2026 | 2026 |
+----+------------+------+
2 rows in set (0,000 sec)

MariaDB [loja]> SELECT YEAR(data) AS ano, COUNT(*) AS pedidos FROM pedidos GROUP BY ano ORDER BY ano;
+------+---------+
| ano  | pedidos |
+------+---------+
| 2024 |      32 |
| 2025 |      32 |
| 2026 |      26 |
+------+---------+
3 rows in set (0,001 sec)

Os códigos do DATE_FORMAT (%d, %m, %Y) são quase os mesmos do strftime do SQLite. A tabela completa de equivalências está em As Diferenças entre os Bancos.

Experimente: esta é a consulta do MariaDB acima, sem o ano. No SQLite ela dá erro, porque DATE_FORMAT não existe aqui. Troque por strftime com os mesmos códigos, mantendo o apelido data_br.

SELECT id, DATE_FORMAT(data, '%d/%m/%Y') AS data_br
FROM pedidos
WHERE id IN (1, 90);
SELECT id, strftime('%d/%m/%Y', data) AS data_br
FROM pedidos
WHERE id IN (1, 90);

7.6 Booleanos e divisão

BOOLEAN no MySQL/MariaDB é só outro nome para TINYINT(1), um número pequeno: o DESCRIBE da seção 3 mostrou vip | tinyint(1), e TRUE vale 1. Já a divisão é diferente do SQLite: 7 / 2 dá 3.5000; para a divisão inteira existe o DIV:

MariaDB [loja]> SELECT TRUE, FALSE, 7 / 2 AS divisao, 7 DIV 2 AS inteira, 7 % 2 AS resto;
+------+-------+---------+---------+-------+
| TRUE | FALSE | divisao | inteira | resto |
+------+-------+---------+---------+-------+
|    1 |     0 |  3.5000 |       3 |     1 |
+------+-------+---------+---------+-------+
1 row in set (0,000 sec)

No SQLite, inteiro dividido por inteiro dá inteiro (aula Funções Numéricas):

SELECT 7 / 2 AS divisao, 7 / 2.0 AS decimal, 7 % 2 AS resto;

7.7 Maiúsculas e acentos: 'ana' = 'ANA'

Esta é a diferença que mais surpreende quem vem do SQLite. Cada texto no MySQL/MariaDB tem uma collation (a regra de comparação e ordenação). A padrão do MySQL 8.4 é utf8mb4_0900_ai_ci; a do MariaDB 11.8 que a Ana usa é utf8mb4_uca1400_ai_ci. O final _ai_ci quer dizer, segundo o manual do MySQL, accent-insensitive e case-insensitive: ignora acentos e maiúsculas:

MariaDB [loja]> SELECT 'ana' = 'ANA' AS maiusculas, 'Jose' = 'José' AS acento;
+------------+--------+
| maiusculas | acento |
+------------+--------+
|          1 |      1 |
+------------+--------+
1 row in set (0,000 sec)

MariaDB [loja]> SELECT @@character_set_server, @@collation_server;
+------------------------+-----------------------+
| @@character_set_server | @@collation_server    |
+------------------------+-----------------------+
| utf8mb4                | utf8mb4_uca1400_ai_ci |
+------------------------+-----------------------+
1 row in set (0,000 sec)

MariaDB [loja]> SELECT nome FROM clientes WHERE nome = 'jose ribamar silva';
+---------------------+
| nome                |
+---------------------+
| José Ribamar Silva  |
+---------------------+
1 row in set (0,001 sec)

Uma busca por "jose ribamar silva", tudo minúsculo e sem acento, achou o "José Ribamar Silva". Para um campo de busca isso é ótimo; para um login, pode ser um problema. No SQLite, o = compara letra por letra; para ignorar maiúsculas (só nas letras sem acento) existe o COLLATE NOCASE:

SELECT 'ana' = 'ANA' AS maiusculas,
       'ana' = 'ANA' COLLATE NOCASE AS com_nocase,
       'Jose' = 'José' AS acento;

7.8 Conversões silenciosas e o modo estrito

Quando o MySQL/MariaDB não consegue converter um valor, às vezes ele não dá erro: troca o valor e deixa um aviso. 'dez' + 5 vira 5 (o texto vira 0):

MariaDB [loja]> SELECT 'dez' + 5 AS conta;
+-------+
| conta |
+-------+
|     5 |
+-------+
1 row in set, 1 warning (0,000 sec)

MariaDB [loja]> SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level   | Code | Message                                 |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'dez' |
+---------+------+-----------------------------------------+
1 row in set (0,000 sec)

Em gravações, porém, o modo estrito (STRICT_TRANS_TABLES, ligado por padrão no MySQL 8.4 e no MariaDB) transforma o problema em erro. A coluna estado é CHAR(2), e a Ana tentou gravar "Piauí":

MariaDB [loja]> INSERT INTO clientes (nome, estado) VALUES ('Cliente Teste', 'Piauí');
ERROR 1406 (22001): Data too long for column 'estado' at row 1
MariaDB [loja]> SELECT @@sql_mode;
+-------------------------------------------------------------------------------------------+
| @@sql_mode                                                                                |
+-------------------------------------------------------------------------------------------+
| STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------+
1 row in set (0,000 sec)

"Dado longo demais para a coluna estado, na linha 1". O SQLite, nesse caso, gravaria "Piauí" sem reclamar, porque não confere o tamanho (aula Os Tipos no SQLite). O @@sql_mode mostra os modos ligados; o manual do MySQL 8.4 explica que, sem o modo estrito, o valor seria cortado com um aviso.

7.9 Inserir ou atualizar: ON DUPLICATE KEY UPDATE

O UPSERT do MySQL/MariaDB é o ON DUPLICATE KEY UPDATE ("em chave duplicada, atualize"). A Ana quer renomear a categoria 1:

MariaDB [loja]> INSERT INTO categorias (id, nome) VALUES (1, 'Papelaria e Escritório');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
MariaDB [loja]> INSERT INTO categorias (id, nome) VALUES (1, 'Papelaria e Escritório') ON DUPLICATE KEY UPDATE nome = VALUE(nome);
Query OK, 2 rows affected (0,001 sec)

MariaDB [loja]> INSERT INTO categorias (id, nome) VALUES (1, 'Papelaria e Escritório') ON DUPLICATE KEY UPDATE nome = VALUE(nome);
Query OK, 0 rows affected (0,000 sec)
ERROR 1062 … Duplicate entry '1' for key 'PRIMARY'
Sem o UPSERT, o INSERT falha: "entrada duplicada '1' na chave PRIMARY" (a chave primária).
ON DUPLICATE KEY UPDATE
Se a chave já existe, em vez de falhar, atualize aquela linha.
nome = VALUE(nome)
VALUE(nome) é "o nome que eu tentei inserir". No MariaDB a função se chama VALUE() (o VALUES() antigo também funciona nesse lugar).
2 rows affected / 0 rows affected
A convenção do MySQL, segundo o manual: 1 = inseriu uma linha nova, 2 = atualizou uma existente, 0 = a linha já tinha esses valores. Na segunda vez nada mudou, por isso 0.

No MySQL 8.4, o manual marca o VALUES() desse lugar como descontinuado e recomenda dar um apelido à linha nova (ilustrativo, sintaxe do manual):

INSERT INTO categorias (id, nome) VALUES (1, 'Papelaria e Escritório') AS nova
ON DUPLICATE KEY UPDATE nome = nova.nome;

O MariaDB 11.8 não aceita esse apelido; é uma das diferenças entre os irmãos:

MariaDB [loja]> INSERT INTO categorias (id, nome) VALUES (8, 'Jardim e Quintal') AS nova ON DUPLICATE KEY UPDATE nome = nova.nome;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'AS nova ON DUPLICATE KEY UPDATE nome = nova.nome' at line 1

7.10 GROUP BY com coluna solta

No MariaDB, esta consulta roda: ela agrupa por estado, mas pede também o nome, que não está no GROUP BY nem numa função agregada. O banco escolhe um nome qualquer do grupo:

MariaDB [loja]> SELECT estado, COUNT(*) AS clientes, nome FROM clientes GROUP BY estado;
+--------+----------+----------------------+
| estado | clientes | nome                 |
+--------+----------+----------------------+
| CE     |        7 | Carlos Eduardo Lima  |
| PE     |        2 | Juliana Paes Barbosa |
| PI     |       14 | José Ribamar Silva   |
| SP     |        7 | Ana Beatriz Sousa    |
+--------+----------+----------------------+
4 rows in set (0,001 sec)

O SQLite também aceita (veja o quadro abaixo). O MySQL 8.4, não: o modo ONLY_FULL_GROUP_BY está ligado por padrão lá e, segundo o manual, rejeita consultas que pedem colunas "não agregadas" fora do GROUP BY (erro 1055). O @@sql_mode do MariaDB, na seção 7.8, não tem esse modo. Escreva sempre como o MySQL exige: toda coluna do SELECT ou está no GROUP BY, ou dentro de uma função como MIN(nome) (aula GROUP BY).

SELECT estado, COUNT(*) AS clientes, nome
FROM clientes
GROUP BY estado;

Formas diferentes: inserir sem duplicar

Quando a linha pode já existir, há três jeitos de escrever o INSERT no MariaDB, com equivalentes no SQLite:

MariaDB [loja]> INSERT IGNORE INTO categorias (id, nome) VALUES (2, 'Eletrônicos e Informática');
Query OK, 0 rows affected, 1 warning (0,000 sec)

MariaDB [loja]> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level   | Code | Message                               |
+---------+------+---------------------------------------+
| Warning | 1062 | Duplicate entry '2' for key 'PRIMARY' |
+---------+------+---------------------------------------+
1 row in set (0,000 sec)

O INSERT IGNORE transformou o erro 1062 num aviso e não mudou nada. No SQLite, o mesmo se escreve INSERT OR IGNORE:

INSERT OR IGNORE INTO categorias (id, nome) VALUES (2, 'Eletrônicos e Informática');
SELECT id, nome FROM categorias WHERE id = 2;
Você quer…MySQL / MariaDBSQLite
falhar se já existe (o normal)INSERT … → erro 1062INSERT … → UNIQUE constraint failed
ignorar se já existeINSERT IGNORE …INSERT OR IGNORE … ou … ON CONFLICT DO NOTHING
atualizar se já existe… ON DUPLICATE KEY UPDATE col = VALUE(col) (MySQL: AS nova … nova.col)… ON CONFLICT (id) DO UPDATE SET col = excluded.col

Qual usar? Atualizar quando o dado novo deve valer (um preço novo). Ignorar só quando você tem certeza de que a linha antiga está certa: o IGNORE engole o erro, e erros engolidos escondem problemas.


MySQL × MariaDB: o que é diferente entre os irmãos

AssuntoMySQL 8.4 (manual)MariaDB 11.8 (rodado)
Clientemysql, prompt mysql>mariadb, prompt MariaDB [loja]>
OFFSET … FETCH …não (só LIMIT)sim (desde a 10.6)
INSERT … RETURNINGnão (use LAST_INSERT_ID())sim
UPSERTVALUES (…) AS nova ON DUPLICATE KEY UPDATE c = nova.cON DUPLICATE KEY UPDATE c = VALUE(c)
ONLY_FULL_GROUP_BY por padrãosimnão
Collation padrãoutf8mb4_0900_ai_ciutf8mb4_uca1400_ai_ci

Quando dá errado

Os erros do MySQL e do MariaDB têm sempre o mesmo formato: ERROR número (SQLSTATE): mensagem. O número é o mesmo nos dois bancos e é ótimo para pesquisar; o SQLSTATE, entre parênteses, é um código de 5 caracteres definido pelo padrão SQL. As mensagens saem em inglês.

Tabela com nome errado: ERROR 1146
MariaDB [loja]> SELECT nome FROM cliente;
ERROR 1146 (42S02): Table 'loja.cliente' doesn't exist

"A tabela loja.cliente não existe": faltou o s. O banco mostra o nome completo, banco.tabela. No SQLite a mesma falha é no such table: cliente.

Como consertar: SHOW TABLES; para ver os nomes certos. Lembre que no Linux Clientes e clientes são tabelas diferentes.

Coluna com nome errado: ERROR 1054
MariaDB [loja]> SELECT nome, cidad FROM clientes;
ERROR 1054 (42S22): Unknown column 'cidad' in 'SELECT'

"Coluna desconhecida 'cidad' no SELECT". O MySQL escreve o fim de outro jeito (o manual registra a mensagem como Unknown column '%s' in '%s', e o segundo pedaço varia), mas o número 1054 é o mesmo.

Como consertar: DESCRIBE clientes; e corrija para cidade.

Erro de sintaxe: ERROR 1064
MariaDB [loja]> SELECT TOP 3 nome FROM produtos;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '3 nome FROM produtos' at line 1

"Você tem um erro na sintaxe SQL; confira no manual da sua versão a sintaxe certa para usar perto de '3 nome FROM produtos', na linha 1." O trecho depois do near começa exatamente onde o banco se perdeu: aqui, no 3 depois do TOP (que é do SQL Server). Leia o que vem logo antes desse trecho.

Como consertar: SELECT nome FROM produtos ORDER BY preco DESC LIMIT 3;.

Chave repetida: ERROR 1062

Visto na seção 7.9: ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'. Você tentou inserir um id que já existe. Como consertar: não informe o id (deixe o AUTO_INCREMENT escolher) ou use ON DUPLICATE KEY UPDATE.

Texto grande demais: ERROR 1406

Visto na seção 7.8: ERROR 1406 (22001): Data too long for column 'estado' at row 1. Como consertar: grave o valor no tamanho da coluna ('PI') ou aumente a coluna com ALTER TABLE (aula ALTER TABLE).

Senha ou usuário: ERROR 1045

Visto na seção 3: ERROR 1045 (28000): Access denied for user 'ana'@'localhost' (using password: YES). Confira a senha; se ela está no ~/.my.cnf e tem #, ponha-a entre aspas. Se aparecer using password: NO, nenhuma senha foi enviada: faltou o -p.

Sem erro, mas errado: aspas duplas e ||

Os dois mais traiçoeiros não dão erro nenhum: WHERE "estado" = 'PI' compara o texto "estado" com "PI" (volta vazio), e nome || cidade devolve 0 com avisos. Ambos estão em As Diferenças entre os Bancos. Se aparecer warnings no fim do resultado, rode SHOW WARNINGS;.


Referência rápida

Comando / SQLO que faz
sudo apt install mariadb-serverinstala o MariaDB no Debian/Ubuntu
sudo mariadbentra como administrador (root)
CREATE DATABASE loja;cria um banco
CREATE USER 'ana'@'localhost' IDENTIFIED BY '…';cria um usuário
GRANT ALL PRIVILEGES ON loja.* TO 'ana'@'localhost';dá permissão no banco
mariadb -u ana -p lojaentra (pede a senha)
mariadb loja < arquivo.sql / -e "SQL"roda um arquivo / um comando, sem entrar
SHOW DATABASES; SHOW TABLES; DESCRIBE t;lista bancos, tabelas, colunas
\G · SOURCE f.sql · quituma coluna por linha · roda arquivo · sai
SHOW WARNINGS;lê os avisos do último comando
`nome`aspas de nome (identificador)
AUTO_INCREMENT · LAST_INSERT_ID()id automático · último id gerado
CONCAT() · CONCAT_WS()juntar textos
CURDATE() · NOW() · DATE_ADD(d, INTERVAL n DAY) · DATEDIFF(fim, início) · DATE_FORMAT(d, '%d/%m/%Y') · YEAR(d)datas
7 DIV 2divisão inteira
INSERT IGNORE · ON DUPLICATE KEY UPDATEignorar / atualizar se a chave já existe

Exercícios

1. Entrar pedindo a senha

Complete o comando para a Ana entrar no banco loja com a usuária ana, pedindo a senha:

mariadb -u ana loja

2. Listar as tabelas

Qual comando, dentro do mariadb, faz o mesmo que o .tables do sqlite3?

3. No MariaDB, a Ana rodou SELECT nome FROM clientes WHERE nome = 'ana beatriz sousa';. O que acontece?

4. O que quer dizer Query OK, 2 rows affected depois de um INSERT … ON DUPLICATE KEY UPDATE de uma linha só?

5. A senha da Ana no ~/.my.cnf é Sol#2026. Como ela deve escrever a linha?

6. Qual destes só funciona no MariaDB, e não no MySQL 8.4?

Missão final: o relatório por ano

Missão: esta consulta conta os pedidos por ano no MariaDB (você viu o resultado na seção 7.5: 32, 32 e 26). No SQLite, YEAR() não existe. Reescreva para o SQLite, mantendo os apelidos ano e pedidos.

SELECT YEAR(data) AS ano, COUNT(*) AS pedidos
FROM pedidos
GROUP BY ano
ORDER BY ano;
SELECT strftime('%Y', data) AS ano, COUNT(*) AS pedidos
FROM pedidos
GROUP BY ano
ORDER BY ano;

Resumo

  • MySQL (Oracle) e MariaDB (dos criadores originais do MySQL) são irmãos: o SQL do dia a dia é igual. O Debian instala o MariaDB (sudo apt install mariadb-server).
  • São servidores: crie o banco e o usuário como root (sudo mariadb, depois CREATE DATABASE, CREATE USER, GRANT) e entre com mariadb -u ana -p loja. A senha pode ficar no ~/.my.cnf, com chmod 600 (e aspas se tiver #).
  • No lugar dos comandos de ponto: SHOW TABLES, DESCRIBE, \G, SOURCE, quit.
  • No SQL: crase para nomes (aspas duplas são texto), AUTO_INCREMENT, CONCAT (não ||), datas com DATE_ADD/DATEDIFF/DATE_FORMAT/YEAR, BOOLEAN = TINYINT(1), 7 / 2 = 3.5000.
  • A collation padrão ignora maiúsculas e acentos: 'ana' = 'ANA' e 'Jose' = 'José' são verdadeiros.
  • Leituras convertem em silêncio (com warnings); gravações, com o modo estrito, dão erro (1406). UPSERT: ON DUPLICATE KEY UPDATE.
  • Erros têm número: 1045 (acesso), 1054 (coluna), 1062 (duplicado), 1064 (sintaxe), 1146 (tabela), 1406 (grande demais).