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!

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 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.