MySQL Contando Registros com Múltiplas Condições

SELECT COUNT(id) AS count_total
     , SUM(IF(message = 'content1',1,0)) AS count_message1
     , SUM(CASE 
              WHEN message LIKE 'content1' THEN 1 ELSE 0
           END) AS count_message1a
FROM mytable;

Sempre que precisamos observar e analisar dados podemos fazer uso de comandos mais avançados de SQL.

É claro que este meu exemplo está longe de ser um SQL complexo, mas sempre é bom deixar registrado as possibilidades a partir de um ponto simples também.

Neste caso estou fazendo uma contagem total de registros da tabela com o COUNT(id) simples, como nenhuma condição foi posta no WHERE serão contados todos os registros.

Então quero comparar lado a lado este total de registros com o total de mensagens do tipo 1. Para obter esta contagem parcial precisamos combinar o SUM() com um IF() interno condicionando a contagem apenas para as mensagens com o conteúdo que combinem com o que eu quero.

Opcionalmente ao IF() podemos utilizar a instrução CASE(); o resultado será o mesmo, apenas estamos utilizando uma sintaxe diferente.

É claro que a instrução CASE() vai ter melhor aplicabilidade em determinadas situações, assim como o IF() também tem sua aplicação para funcionalidades de menor complexidade.

Fica a seu critério qual sintaxe aplicar; este é só o ponta-pé inicial na utilização de SQL para análise de dados.

Souriez s’il vous plait!

MySQL Resetar Sequência AutoIncrement Sem Informar Próxima Sequência

-- first, remove auto-increment attribute if it still exists
ALTER TABLE `mytable` MODIFY COLUMN `id` INT(10) UNSIGNED;

-- second, add auto-increment attribute back
ALTER TABLE `mytable` MODIFY COLUMN `id` INT(10) UNSIGNED AUTO_INCREMENT;

Recentemente enfrentei um problema de deixar os cabelos em pé onde uma das tabelas acabou perdendo o atributo auto-increment durante uma operação de movimentação na nuvem.

Bom, o deixar os cabelos em pé foi por que demorei muito para descobrir a ocorrência deste evento de perda do atributo, porém a correção do schema é bastante simples.

Apesar do meu caso se tratar de uma tabela de pouca movimentação eu quis utilizar uma solução que encontrei durante minhas buscas. Achei esta solução bastante interessante pelo fato de não ser preciso atribuir o valor do auto-increment com um HARD SET utilizando o MAX VALUE da coluna id correntemente na tabela.

Ao invés disto, a operação consiste em remover o atributo auto-increment do campo e simplesmente adicioná-lo novamente. Durante esta operação o MySQL realiza um auto-reset atribuindo o valor correto de acordo com os dados existentes na tabela.

No meu caso o atributo auto-increment já tinha sido removido em outra operação, então foi só executar o segundo comando mesmo para adicionar o auto-increment de volta a coluna id.

Caso você queira simplesmente resetar a sequência auto-increment utilizando os comandos mais tradicionais você poderia utilizar, por exemplo, os comandos abaixo.

SELECT MAX(id)+1 INTO @next_id FROM `mytable`;

ALTER TABLE `mytable` AUTO_INCREMENT = @next_id;

Prontinho… Case Closed!

MySQL Restaurar um Arquivo de Backup exportado pelo MySQLDump

mysql -u <my-user> -p <dbname-wanted-to-restore> < file-data-dump.sql
(password will be asked later)

Esse aqui parece simples, só que é muito fácil de esquecer quando não se usa com frequência.

Acontece que o comando para realizar a exportação de dados do MySQL é bem intuitivo, se chama mysqldump.

Só que para restaurar não tem um mysqlrestore não, o que se usa é o mesmo comando local mysql client. E é um saco ter que lembrar disso ou então ter que pesquisar nos longos sites tutoriais ou manuais técnicos.

Agora taí… mysqldump para exportar e mysql para restaurar.

MySQL Montar Script SQL CREATE TABLE a partir de uma Tabela Existente

SHOW CREATE TABLE usuario;

Eu sempre costumo escrever meus scripts SQL para criação de tabelas a partir do zero, normalmente utilizando alguma ferramenta de diagramação como DBDesigner por exemplo.

Mas trabalhando em um projeto recente minha tarefa era transferir algumas tabelas de um determinado banco de dados para outro, assim o SCHEMA a ser criado deveria ser exatamente o mesmo incluíndo os índices para evitar qualquer tipo de impacto na aplicação.

Ai que medo de digitar errado!!!

Pesquisando rapidamente encontrei artigos orientando o uso de um comando específico pra isso. Em um só comando o script para criação do SCHEMA sem dados incluíndo chaves primárias, chaves estrangeiras, chaves únicas e índices, e ainda atributos de CHARTSET e outros penduricalhos.

É claro que você consegue chegar ao mesmo resultado por outros meios como utilizando o MYSQLDUMP sem dados de uma única tabela, mas este comando aqui já faz tudo isso sem maiores complicações.

Enjoy Your Day!

MySQL Show e Describe para inspecionar informações de tabelas

# present very basic information, but not custom indexes
DESCRIBE payments;

# basically the same as describe
SHOW COLUMNS FROM payments;
SHOW COLUMNS FROM production.payments;
SHOW COLUMNS FROM payments IN production;

# a more detailed information
SHOW FULL COLUMNS FROM payments;

# wanna see custom indexes?
SHOW INDEX FROM payments;
SHOW INDEX FROM payments FROM production;
SHOW INDEX FROM production.payments;

Sempre que eu quero inspecionar os detalhes de informações de uma tabela no MySQL eu somente consigo me lembrar do comando DESCRIBE, ou nem isso!

O Fato é que o MySQL fornece alguns recursos básicos mas muito eficientes para obter estas informações de forma simples e rápida.

Se você é developer certamente vai precisar de alguns destes recursos listados aí…

Os Databases Scientists e Database Administrators podem querer recorrer a recursos mais avançados como SELECT sobre as tabelas de SCHEMAS, mas isso é assunto para outro artigo.

Happy SQL!

INFORMIX Alterar Múltiplas Linhas e Múltiplas Colunas com SubSelect

UPDATE princing SET
(price, increasepercentage, datenewrate)
=
(SELECT nrpp.increasepercentage * princing.price AS price
      , nrpp.increasepercentage
      , SYSDATE
 FROM newrateperproduct AS nrpp
 WHERE nrpp.productid = pricing.productid
   AND nrpp.increasepercentage IS NOT NULL
)
WHERE EXISTS
(SELECT 1
 FROM newrateperproduct AS nrpp
 WHERE nrpp.productid = pricing.productid
   AND nrpp.increasepercentage IS NOT NULL
);

Minha jornada com bancos de dados iniciou-se com o sensacional INFORMIX, não por que eu queria, mas naquela época tratava-se se um processo de downsizing onde tínhamos que iniciar a desativação de um Mainframe UNISYS. O novo sistema escolhido pela empresa trazia consigo o requisito de utilizar também este banco de dados.

Dentre as inúmeras características, a capacidade de processar update com “subselects” de forma genial com uma sintaxe praticamente ANSII me encheu os olhos e me proporcionou uma evolução fantástica na programação com SQL puro. Cheguei a desenvolver sistemas inteiros eliminando a necessidade de linguagem de programação, somente SHELL para interface com usuário e SQL statements para cálculos e transformações de dados. O update com subselect tomou um importante papel neste caso.

Toda vez que aprendo um banco de dados novo eu acabou invariavelmente procurando saber se ele tem suporte a esta capacidade. Com o MySQL você alcança este resultado com o Update Join, o que não é uma sintaxe tão nativa assim, mas o pessoal da MySQL AB mandou muito bem neste caso, eu até já escrevi um artigo aqui no Blog demonstrando isso.

Explicando um pouco sobre a sintaxe, a grande sacada do INFORMIX é permitir mesmo que sejam postas várias colunas para serem alteradas, quando for mais de uma coluna o requerimento é recobrir as colunas com parênteses e pronto. No subselect então basta fazer o “match” quantitivo das colunas e, é claro o match de tipos. Naturalmente a atribuição das colunas é feita por posicionamento simplesmente.

Outra sacada que você não pode ignorar é que, para não incorrer em erros devemos repetir o subselect na cláusula WHERE do UPDATE para que tenhamos a mesma equivalência de registros tanto subselect quanto no update.

E o que mais? Mais nada!

MySQL Update Join para Alterar Múltiplas Linhas de uma só vez

// explicit inner join
UPDATE product AS p
INNER JOIN product_price AS pp ON (pp.product_id = p.product_id)
SET pp.old_price = p.current_price,
    p.current_price = pp.base_price * 0.8
WHERE p.created_at < '2021-01-01';

// implicit inner join
UPDATE product AS p, product_price AS pp
SET pp.old_price = p.current_price,
    p.current_price = pp.base_price * 0.8
WHERE pp.product_id = p.product_id
p.created_at < '2021-01-01';

// explicit left join, only left side is updatable
UPDATE product AS p
LEFT JOIN product_price AS pp ON (pp.product_id = p.product_id)
SET p.deleted = 1
WHERE p.created_at < '2021-01-01';

Toda vez que eu estudo um banco de dados novo uma das coisas que sempre busco em todos eles é entender como escrever a sintaxe de um update em massa apenas relacionando tabelas sem a necessidade de laços for ou foreach bastante comuns em Stored Procedures. Esta preferência é porque a performance de uma instrução SQL é amplamente superior a execução de um laço de programação.

Eu peguei esta mania por que quando iniciei na linguagem SQL com banco de dados Informix, este banco de dados possuía uma sintaxe fora de serie para fazer update com dados entre tabelas. A sintaxe era bastante comum e intuitiva que permitia a alteração de diversas colunas e diversas linhas num mesmo comando.

Como o pessoal da MySQL AB também era fora de série eles implementaram uma funcionalidade chamada de Cross Table Update, que nada mais é que um update que permite join com outras tabelas. Resumindo, esta funcionalidade do MySQL preenche com perfeição o comportamento que eu conseguia executar com o Informix.

Outra forma de se fazer update em massa é utilizando-se sub-selects, porém as instruções do update com join são infinitamente mais simples de serem escritas e também a performance com o update join é bem melhor.

E para que não finquem dúvidas sobre a autoria deste recurso, esta funcionalidade foi disponibilizada desde a versão 4.0 do MySQL.

Uma salva de palma pra eles… genial!

PHP Flex Search Encontrando Registros Por Múltiplos Argumentos Numa só Tacada

$inputTextString = 'cano pvc 3mm rosca azul tigre pc';
$baseQuery = 'SELECT * FROM product WHERE 1=1 %s';

$flexQuery = ' %1$s (product.code LIKE \'%%%2$s%%\' OR product.long_desc LIKE \'%%%2$s%%\' OR product.short_desc LIKE \'%%%2$s%%\' OR product.brand LIKE \'%%%2$s%%\' OR product.measure_unit LIKE \'%%%2$s%%\')';
            
$flexWhere = '';
$flexArgs = explode(' ',$inputTextString);
foreach($flexArgs as $flexArg) {
  $flexAnd = empty($flexWhere) ? 'AND(' : 'AND';
  $flexWhere .= vsprintf($flexQuery,[$flexAnd, $flexArg]); 
}

$flexWhere .= empty($flexWhere) ? '' : ')';
$flexSearch = sprintf($baseQuery, $flexWhere);

$conn = mysqli_connect('localhost', 'username', 'password', 'database');
$result = mysqli_query($conn, $flexSearch);

Sabe quando você vai àquele material de construção e o vendedor não consegue achar o produto que você pediu dentro do sistema de vendas? Pois é, que falta de sorte né!

No meu caso eu acho que dei bastante sorte quando iniciei na programação WEB no início dos anos 2000… trabalhando com sistemas eCommerce uma das jóias do nosso aplicativo PHP era a busca de produtos simplesmente informando todas as características para pesquisa num único campo string.

Todas as informações como partes do nome, marca, modelo, dimensões, unidade de medida eram informados num único campo TYPE TEXT simplesmente separando as partes por espaço em branco, o separador universal mais rápido de ser digitado.

Como a técnica não tinha sido nomeada até então eu acabei dando o nome de FLEX SEARCH por que é exatamente isto que este conceito de busca faz, flexibiliza a pesquisa de registros para o operador.. o desenvolvedor que se vire pra fazer o código funcionar.

Observe no código que a busca é flexibilizada ao máximo, pois tentamos encontrar os fragmentos de pesquisa informados não somente na descrição do produto, mas também em outros campos na tabela, e esta busca é feita de forma não obstrutiva, ou seja, a busca tentar encontrar os fragmentos onde for possível mas não requer obrigatoriedade de existência em todos os pontos.

Se você imprimir o conteúdo em $flexSearch você vai encontrar uma query como o SELECT logo abaixo. Então basta simplesmente executar a instrução SQL para obter os registros desejados.

SELECT * FROM product WHERE 1=1  AND( (product.code LIKE '%cano%' OR product.long_desc LIKE '%cano%' OR product.short_desc LIKE '%cano%' OR product.brand LIKE '%cano%' OR product.measure_unit LIKE '%cano%') AND (product.code LIKE '%pvc%' OR product.long_desc LIKE '%pvc%' OR product.short_desc LIKE '%pvc%' OR product.brand LIKE '%pvc%' OR product.measure_unit LIKE '%pvc%') AND (product.code LIKE '%3mm%' OR product.long_desc LIKE '%3mm%' OR product.short_desc LIKE '%3mm%' OR product.brand LIKE '%3mm%' OR product.measure_unit LIKE '%3mm%') AND (product.code LIKE '%rosca%' OR product.long_desc LIKE '%rosca%' OR product.short_desc LIKE '%rosca%' OR product.brand LIKE '%rosca%' OR product.measure_unit LIKE '%rosca%') AND (product.code LIKE '%azul%' OR product.long_desc LIKE '%azul%' OR product.short_desc LIKE '%azul%' OR product.brand LIKE '%azul%' OR product.measure_unit LIKE '%azul%') AND (product.code LIKE '%tigre%' OR product.long_desc LIKE '%tigre%' OR product.short_desc LIKE '%tigre%' OR product.brand LIKE '%tigre%' OR product.measure_unit LIKE '%tigre%') AND (product.code LIKE '%pc%' OR product.long_desc LIKE '%pc%' OR product.short_desc LIKE '%pc%' OR product.brand LIKE '%pc%' OR product.measure_unit LIKE '%pc%'))

Resumindo, a busca só vai falhar se algum fragmento de pesquisa informado não existir em nenhuma das fontes possíveis. O PHP ajuda muito na construção de queries dinâmicas, e este SQL é bastante padrão ANSI e deveria rodar fácil em pelo menos uma dúzia de diferentes bancos de dados.

Da próxima vez que for ao comércio e se deparar com um vendedor em dificuldades, você vai lembrar… vai aí um Flex Search?!!!

MySQL Criando banco de dados e liberando acesso para usuário

cd /mariadb/bin
mysql -u root -p

CREATE DATABASE mydb;
GRANT ALL ON mydb.* TO myuser@localhost IDENTIFIED BY 'mypassword';
FLUSH PRIVILEGES;

quit

Atualmente é muito comum que os desenvolvedores necessitem lidar com uma infinidade de tecnologias dentre elas instalação e configuração de banco de dados.

Até aqui tudo bem, só que não… o mais corriqueiro é que depois de instalarmos o MySQL passemos a utilizar a conexão com o usuário root e pior, sem senha.

E quem já não tentou liberar o acesso para usuário com o PHPMYADMIN e acabou se frustando por que não funcionou corretamente?

Ao criar um novo banco de dados podemos imediatamente liberar o acesso para um usuário específico evitando assim o acesso com o infame root user.

Os comandos acima são praticamente intuitivos, vou apenas reforçar que a liberação foi feita para o usuário myuser provenientes do ambiente localhost, ou seja conexão interna.

Para liberar acesso mais genérico poderíamos utilizar um asterisco (*) no lugar de localhost, assim estaríamos liberando o acesso remoto a partir de outros hosts.

Agora você já sabe, nada de usar o usuário root sem senha.

MySQL Comparando colunas Case Insensitive diferenciando maiúsculas e minúsculas

SELECT *
FROM tabela_nome
WHERE (coluna_nome COLLATE latin1_general_cs) = 'TexTo';

O MySQL tem um recurso que acho muito show que é com muita facilidade configurar o recurso COLLATE CI (case insensitive) nas colunas texto, o que facilita muito a busca de informações pois não precisamos nos preocupar com maiúsculas e minúsculas no momento das pesquisas.

Mas se em algum momento por alguma razão precisamos fazer uma pesquisa fazendo distinção de maiúsculas e minúsculas?

Neste caso podemos utilizar o recurso COLLATE associando dinamicamente a coluna que queremos fazer a comparação do conteúdo.

Neste exemplo estamos claramente solicitando que o banco de dados faça a consulta comparando o conteúdo da coluna coluna_nome epsis litteris a string “TexTo” discriminando exatamente as maiúsculas e minúsculas.

Eu entendo que muitos profissionais preferem que o banco de dados tenha o comportamento CASE SENSITIVE nativamente como no caso do ORACLE e PostGre, mas o mundo web tem destas facilidades que o MySQL incorporou na gente.