Eu tenho uma tabela que tem linhas como estas:
+----------+--------------+---------------------+------+
| CASE_ID | CAT | BIRTH | TYPE |
+----------+--------------+---------------------+------+
| 20033738 | CASE_OWNER | 1996-04-08 00:00:00 | NULL |
| 20033738 | WIFE | 1995-08-22 00:00:00 | NULL |
| 20039334 | CASE_OWNER | 1994-03-10 00:00:00 | NULL |
| 20039301 | CASE_OWNER | 1999-07-27 00:00:00 | NULL |
| 20039301 | WIFE | 2001-07-05 00:00:00 | NULL |
| 20039301 | CHILD | 2018-10-22 00:00:00 | NULL |
| 20033831 | CASE_OWNER | 1975-03-05 00:00:00 | NULL |
| 20033831 | CHILD | 2005-03-19 00:00:00 | NULL |
| 20033831 | CHILD | 2006-03-25 00:00:00 | NULL |
| 20033831 | CHILD | 2010-05-20 00:00:00 | NULL |
| 20033831 | CHILD | 2013-10-25 00:00:00 | NULL |
+----------+--------------+---------------------+------+
Para um CASE_ID, cada combinação de CASE_OWNER com ou sem WIFE e/ou CHILD (1 ou mais) é possível.
Para cada CASE_ID quero definir a coluna TYPE, com base nas informações que encontrarei em CAT e BIRTH:
a) Se houver apenas um CASE_OWNER em um CASE_ID, TYPE deve ser
- a1) SINGLE_PERSON na coluna TYPE que corresponde a CASE_ID, SE CASE_OWNER tiver mais de 21 anos
- a2) UNKNOWN na coluna TYPE que corresponde a CASE_ID, SE CASE_OWNER for menor que 21
b) Se um CASE_ID tiver CAT CASE_OWNER AND WIFE (WITHOUT CHILD) para um CASE_ID, TYPE deverá ser PAIR_NO_CHILD em cada linha que corresponda ao CASE_ID específico.
c) Se um CASE_ID tiver CAT CASE_OWNER AND WIFE AND 1 ou mais CHILD(s) para um CASE_ID, TYPE deve ser
- c1) PAIR_WITH_CHILD se um ou mais CHILD(s) estiverem abaixo de 21 em cada linha que corresponda ao CASE_ID específico.
- c2) OTHER se todos os CHILD(s) forem 21 em cada linha que corresponda ao CASE_ID específico.
d) Se um CASE_ID tiver CAT CASE_OWNER AND 1 ou mais CHILD(s) para um CASE_ID, TYPE deve ser
- d1) SINGLE_WITH_CHILD no caso de um ou mais (não todos) CHILD(s) serem abaixo de 21 em cada linha que corresponda ao CASE_ID específico.
- d2) MÚLTIPLO caso todas as CRIANÇAS sejam acima de 21 anos.
e) Todas as outras combinações serão do TIPO == DESCONHECIDO.
Minhas perguntas são:
- Isso é possível usando SQL?
- Isso deve ser resolvido usando SQL ou usando uma linguagem de programação?
- Se isso for possível no SQL - como deve ser feito?
Muito obrigado pelo seu feedback Steffi
Introdução:
Você tem duas possibilidades para fazer o que quiser - uma funciona para versões do MySQL a partir de 5.5 (usa agregados) e superior e a outra funciona para MySQL 8 e superior (usa funções de janela).
Todo o código abaixo está disponível no violino aqui . NOTA: O violino é para o MySQL versão 8. Se você deseja executar as versões 5.5 (ou 5.6 ou 5.7), altere o servidor no menu suspenso na parte superior do violino. Eu fiz isso porque
EXPLAIN ANALYZE
só pode ser usado com MySQL > 8.0.18 - as versões anteriores são inutilizáveis!Primeiro crie sua tabela (você mesmo deve fornecer isso na forma de um violino para esse tipo de pergunta). Fiz algumas alterações no seu esquema:
e, em seguida, preenchê-lo. Adicionei alguns registros para fins de teste:
MUITO IMPORTANTE
Uma compreensão da
CASE
declaração é fundamental para seguir o restante desta resposta. A consulta irá progredir através doCASE
e quando se deparar com a primeira condição de correspondência, ele irá executar a atribuição e, em seguida, sair doCASE
registro e começar novamente no próximo registro - isso é um pouco como a instruçãoC
da linguagem de programação (e outras) ,CONTINUE
usado para sair de loops e começar de novo com a próxima iteração.Isso é por que
a
DEFAULT
é importante acompanhar se suas tarefas estão sendo executadas corretamente e se você não perdeu nadavocê tem que ter um caminho claro para suas condições. Quando há
CASE
declarações dentro dos outros, pode se tornar muito fácil confundir-se!1ª forma da consulta (usando funções de janela - disponível apenas no MySQL >= 8).
Vou passar por isso seção por seção, pois há algumas partes complicadas!
A 1ª seção:
Isso cobre
a) Se houver apenas um CASE_OWNER em um CASE_ID, TYPE deve ser
Este é um exemplo de um
CASE
dentro de umCASE
! Se houver apenas um registro com um dadocase_id
, por definição, deve ser o proprietário do caso! Em seguida, verificamos a data de nascimento e, se tiverem mais de 21 anos (curso normal dos eventos), configuramos o valor paraSINGLE_PERSON
,UNKNOWN
caso contrário!O
COUNT(case_id) OVER(...
é um exemplo de uma função de janela . Estes são extremamente poderosos e vale a pena conhecê-los bem (pequena introdução aqui ) - eles vão compensar qualquer esforço gasto em aprendê-los muitas vezes!Existem outras maneiras de calcular a idade aqui - dependendo da precisão que você precisa.
A 2ª seção:
Isso abrange o caso:
O trecho interessante aqui é a
SUM(CASE WHEN...
construção que nos permite distinguir entrecase_id
s que possuem e não possuem umWIFE
.A 3ª seção:
Isso abrange os casos:
c) Se um CASE_ID tiver CAT CASE_OWNER AND WIFE AND 1 ou mais CHILD(s) para um CASE_ID, TYPE deve ser
A 4ª parte:
Isso abrange os casos:
d) Se um CASE_ID tiver CAT CASE_OWNER AND 1 ou mais CHILD(s) para um CASE_ID, TYPE deve ser
A 5ª parte:
Aqui, usamos um
ORDER BY
com umCASE
"incorporado" nele. Isso nos permite ter controle total sobre a ordenação de nossos registros - dados os requisitos, este é um método de ordenação lógico e é extremamente útil para testes.Resultado:
2ª forma da consulta (usando agregados e uma subconsulta) - funciona a partir de pelo menos 5.5:
Resultado:
Apenas alguns pontos a serem observados:
Como mencionado acima, sempre forneça um violino com suas perguntas quando apropriado - normalmente se você quiser exibir dados de qualquer tipo!
PAIR_WITH_CHILD
sounds incongruous - a"pair"
normally refers to wildlife of some sort, or possibly domestic or farm animals, but not humans! However,both '"child"and
"wife"` definitely refer to human beings. You might want to put "Couple with children" or similar!I've included an
UPDATE
at the bottom of the fiddle.So, to answer the questions:
Yes, see above.
There's no reason not to use SQL in this case. SQL is now Turing complete, however just because you can to do something in a given language, doesn't mean that you should do it in that language.
There will come a point where you have very complex requirements where using SQL will lead to diminishing returns in terms of your effort vs. outcome - experience will tell you when it's better to use another tool!
See above! A fiddle showing how to update using aggregates and a CTE is given here.
Finally, a Performance analysis:
I looked at the plans (from MySQL >= 8) and can't make much sense of them! The usual caveats about performance testing apply - there are only 22 records in this dataset. You should test your query/queries on your own full dataset with your own hardware and other system constraints. Just for the record however, on a locally installed instance of MySQL (8.0.27), Windows 11, 16GB of RAM, 8-core processor, 512GB NVMe drive I obtained these results:
MySQL:
Q2: 'Aggregates_no_ORDER_BY - beginning'; 0.187165 s
Q1: 'Window_no_ORDER_BY - end'; 0.229719 s
Q2: 'Aggregates_with_ORDER_BY - end'; 0.296987 s
Q1 'Window_with_ORDER_BY - end'; 0.344441 s
PostgreSQL (same machine) - using EnterpriseDB's 14.1 binary from here. See here for a PostgreSQL fiddle with
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) <query>
.Q1: Windows_order_by 1.328 ms
Q2: Windows-NO-order_by 1.35 ms
Q1: Aggregate_order_by 1.8 ms
Q2: Aggregate_no_order_by 2.7 ms
The results for MySQL don't appear to align with the complexity of the plans or the fact that the table has to be scanned 4 times (or does it?).
What is really puzzling is that MySQL is 140 times slower than PostgreSQL? Frankly, I'm baffled - you'll have to test for yourself.
KISS -- Escreva (e execute) um separado
UPDATE
para cada caso.CASE
às vezes pode ser usado para combinar várias condições em uma única atualização, mas ler isso faz meu cérebro doer.Se uma atualização estiver modificando a(s) coluna(s) que as atualizações a seguir testarão, certifique-se de executar as atualizações em uma ordem adequada.