Ir para conteúdo
arthursanno

MySQL pegar o primeiro registro sem 2 filhoa (parent)

Recommended Posts

Possuo a seguinte tabela

CREATE TABLE IF NOT EXISTS `pool` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `idPai` int(11) NOT NULL,
  `nome` varchar(100) NOT NULL,
  PRIMARY KEY (`idPool`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

INSERT INTO `pool` (`id`, `idPai`, `nome`) VALUES
(1, 0, 'José'),
(2, 1, 'Maria'),
(3, 1, 'Carlos'),
(4, 2, 'Renta'),
(5, 2, 'Pedro'),
(6, 3, 'Gustavo');

No caso desse diagrama:

1
:: 2
:::: 4
:::: 5
:: 3
:::: 6

Quero fazer uma consulta SQL nessa tabela cujo me retorne o primeiro registros que NÃO possui 2 filhos. (no caso do diagrama seria o registro 3... e ao acrescentar mais um filho de 3, a consulta ia retornar o registro 4 agora)

 

Não sei se tenho que fazer consultas dentro de consultas. Preciso de orientação pra continuar o caminho do estudo.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá!

 

Caso você precise consultar quando não possui nenhum filho, no caso os IDS 4, 5 e 6, você poderia fazer assim:

SELECT id
FROM pool p1
WHERE NOT EXISTS (
  SELECT idPai FROM pool p2 WHERE p1.id = p2.idPai
)

Indo nessa lógica, para restringirmos para os registros que não possuem 2 filhos para casar com o ID 3, poderíamos fazer assim:

SELECT id
FROM pool p1
WHERE (
  SELECT COUNT(1) FROM pool p2 WHERE p1.id = p2.idPai
) <= 1
LIMIT 1

 

Mas eu não acho que resolver esse tipo de questão diretamente no banco seja a melhor solução. Depende muito do que você precisa fazer.

 

Outro detalhe é que do jeito que seu banco está estruturado nesse momento, problemas de performance futuros seriam inevitáveis. Para contornar isso você deve criar um índice/chave na coluna idPai. Há um exemplo de SQL aqui:

https://forum.imasters.com.br/topic/562468-resolvido-estrutura-de-um-projeto-de-marketing-multinível-com-php-e-mysql/

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá Matheus. Obrigado por me responder.

 

Eu havia construido essa consulta, verifique:

SELECT *
FROM pool a
WHERE a.id NOT IN (select idPai from pool GROUP BY idPai ORDER BY idPai ASC)
OR a.id IN (SELECT idPai
				FROM pool
				WHERE id <> 1
				GROUP BY idPai
				HAVING COUNT(*) < 2);

O resultado seria a lista dos pais sem filhos e do pai com 1 filho.

Mas como a inserção é automática, ou seja, ele já faz a inserção no primeiro pai livre, então mudei para.

SELECT *
FROM pool a
WHERE a.id > (select idPai from pool ORDER BY idPai DESC LIMIT 1)
OR a.id IN (SELECT idPai
				FROM pool
				WHERE id <> 1
				GROUP BY idPai
				HAVING COUNT(*) < 2)
ORDER BY id ASC LIMIT 1;

Me sugere algo?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pelo que entendi você está solicitando ao banco os pais com menos de 2 filhos (através da sua segunda subquery) e caso não existam, o último pai criado no sistema (sua primeira subquery), é isso?

Dessa forma você teoricamente teria o pai apropriado para a inserção do próximo filho na árvore.

 

Mas você ainda não está livre de cair na situação de todos os pais terem 2 filhos, inclusive o último pai (sua primeira subquery). Caso sua intenção seja de não permitir mais que 2 filhos, aqui haveria uma exceção, que provavelmente seria criar um pai virtual, por exemplo.

 

Além disso, não tenho certeza, mas acredito que dessa forma sua consulta seria mais performática:

SELECT *
FROM pool p1
WHERE
  (SELECT COUNT(1) FROM pool p2 WHERE p1.id = p2.idPai) <= 1
  OR
  id > (select idPai from pool ORDER BY idPai DESC LIMIT 1)

ORDER BY id ASC
LIMIT 1

 

Outra coisa que poderia ser feita é manter em cache a lista dos pais que ainda não possuem 2 filhos. Imagine essa sua segunda SUBQUERY:

SELECT idPai
FROM pool
WHERE id <> 1
GROUP BY idPai
HAVING COUNT(*) < 2

Basicamente ela faz isso: para todos os pais que não possuam ID 1, agrupe-os e remova os que possuirem 2 filhos ou mais.

Perceba que essa consulta será repetida múltiplas vezes, sempre que você precisar consultar o banco.

 

Sua consulta ficaria assim, se estes registros estivessem em cache:

SELECT *
FROM pool a
WHERE a.id > (select idPai from pool ORDER BY idPai DESC LIMIT 1)
OR a.id IN ( 3 )
ORDER BY id ASC LIMIT 1;

Eu apenas copiei sua consulta e substitui a segunda SUBQUERY pelo id 3, que é o resultado da consulta quando executada.

 

Isso você poderia fazer com MATERIALIZED VIEW ou cache a nível de aplicação (APCu por exemplo, se estivermos falando de PHP).

 

De resto, acho que está tudo ok, mas iria sugerir de você popular melhor sua tabela para testar melhor sua consulta. Algo como uns 20 a 30 registros, pelo menos. Isso iria prover uma maior confiabilidade no desenvolvimento.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Citar

Pelo que entendi você está solicitando ao banco os pais com menos de 2 filhos (através da sua segunda subquery) e caso não existam, o último pai criado no sistema (sua primeira subquery), é isso?

Dessa forma você teoricamente teria o pai apropriado para a inserção do próximo filho na árvore.

A inserção do novo registros será sempre no primeiro pai disponível.

 

Ex:

INSERT INTO `pool` (`id`, `idPai`, `nome`) VALUES
(1, 0, 'José'),
(2, 1, 'Maria'),
(3, 1, 'Carlos'),
(4, 2, 'Renta'),
(5, 2, 'Pedro'),
(6, 3, 'Gustavo');

Nesse caso o primeiro pai disponível é o 3 (Carlos), porque ele não possui 2 filhos (somente 1). Ao acrescentar mais um filho nele, o primeiro pai disponível é o 4 (Renata), cujo não possui nenhum filho, ao acrescentar mais um filho, Renata ainda será o primeiro pai disponível pois ainda não tem 2 filhos...

INSERT INTO `pool` (`id`, `idPai`, `nome`) VALUES
(1, 0, 'José'),
(2, 1, 'Maria'),
(3, 1, 'Carlos'),
(4, 2, 'Renta'),
(5, 2, 'Pedro'),
(6, 3, 'Gustavo'),
// Acrescentando novo filho em 3 pq ele só tem 1 filho é o primeiro pai disponível
(7, 3, 'Igor'),
// Acrescentando novo filho agora em 4, pq 3 já possui 3 filhos e 4 nenhum filho 
(8, 4, 'Jerry'),
// Acrescentando novo filho, ainda em 4 pq 4 não possui 2 filhos
(9, 4, 'Bárbara'),
// Acrescentando novo filho, agora em 5 pq 4 já possui 2 filhos
(10, 5, 'Rebeca');
...

 

 

Nessa query que montei, ele me retorna sempre o PRIMEIRO PAI DISPONÍVEL

SELECT *
FROM pool a
WHERE a.id > (select idPai from pool ORDER BY idPai DESC LIMIT 1)
OR a.id IN (SELECT idPai
				FROM pool
				WHERE id <> 1
				GROUP BY idPai
				HAVING COUNT(*) < 2)
ORDER BY id ASC LIMIT 1;

Uso esse para pegar o ID do primeiro pai livre e adiciono automaticamente o novo registro nesse pai.

 

http://www.sqlfiddle.com/#!9/67fdf2/1

 

 

Sim, estarei usando PHP.

Compartilhar este post


Link para o post
Compartilhar em outros sites
37 minutos atrás, arthursanno disse:

Nessa query que montei, ele me retorna sempre o PRIMEIRO PAI DISPONÍVEL 

Aí que tá. Na primeira query que postei é exatamente isso que é feito também.

 

Então, ao menos que essa query não lhe atenda por alguma razão ou esteja funcionando incorretamente, acredito que ela seja a melhor opção para você, levando em consideração que possui menos código e performa um pouco melhor (no seu fiddle dá para notar a diferença em ms na execução):

SELECT *
FROM pool p1
WHERE (
  SELECT COUNT(1) FROM pool p2 WHERE p1.id = p2.idPai
) <= 1
LIMIT 1

Aqui retorna: #5 | 2 | "Pedro", assim como as demais consultas.

 

 

O mesmo serve para a consulta que você elaborou. Ela é uma boa opção se retirarmos a primeira condição do WHERE, que eu acredito que esteja ali sem necessidade.

Nesse caso teríamos isso, que é muito parecido com o que eu fiz, mas utilizando o IN/GROUP/HAVING como alternativas ao depenent subquery:

SELECT *
FROM pool a
WHERE a.id IN (
    SELECT idPai
    FROM pool
    WHERE id <> 1
    GROUP BY idPai
    HAVING COUNT(*) < 2)
ORDER BY id ASC LIMIT 1;

Essa consulta gera o mesmo resultado também. Tudo certo.

 

Entende onde eu quero chegar? Ao menos que eu não tenha entendido alguma coisa, acredito que essas consultas fazem o que você precisa, mas esse pequeno trecho é desnecessário e pode ser removido. Estou me referindo a isso aqui:

[...] a.id > (select idPai from pool ORDER BY idPai DESC LIMIT 1) OR [...]

 

Mas que bom que está resolvido. Você mesmo solucionou a questão :)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora

  • Conteúdo Similar

    • Por ILR master
      Fala pessoal.
       
      Seguinte:
       
      Quero selecionar duas tabelas e mostrar com resultados intercalados. Abaixo segue um código explicando para vcs terem uma ideia.
       
      $consulta = "SELECT A.*, B.* FROM tabela1 A, tabela2 B'";
      $resultado = mysqli_query($conexao, $consulta) or die ("erro");
      while($busca = mysqli_fetch_array($resultado)){
       
      print $busca['cod_evento']; --> traz o código da tabela1 
      print $busca['titulo_evento']; -->  traz o titulo da tabela1
      print $busca['cod_noticia']; --> traz o código da tabela2
      print $busca['titulo_noticia']; --> traz o tituloda tabela2
       
      }
       
      Espero que entendam. Grato
       
    • Por gersonab
      Bom dia
      estou com uma dúvida de como proceder, tenho uma tabela de categoria e uma de subcategoria, a categoria pode ter várias subs, até ai tranquilo, quando faço update de uma sub altero normalmente a quantidade deste, até aí normal, porém me deparei com uma situação um pouco diferente, vou tentar ser o mais claro possível, de certa forma algumas subs são comuns só mudando o nome praticamente, aí eu preciso que ao efetuar o update em umas destas este ocorra nas demais, tipo:
       
      Se o id da sub for 5 , eu preciso fazer o mesmo update nos ids 6 e 7 ;
       
      Se o id da sub for 9 , eu preciso fazer o mesmo update no id 10
       
      se for 2 , fazer o update somente neste
       
      ainda não estou conseguindo ver uma lógica para isso, e ou uma nova coluna para cadastrar em comum nestes casos.
       
      qual seria a melhor opção
    • Por JoaoSilva75
      oi pessoal
       
      se eu entrar no link dos desenvolvedores php pesquisasar quem é programador e enviar uma mesma mensagem para uns 6 ou 8 membros do forum serei advertido   ou é spam ???????
       
      procuro um programador php para me fazer algo 
       
      mas não tem como postar aqui nessa sessão
       
    • Por clovis.sardinha
      Tenho uma consulta de autocomplete no bd que funciona no servidor local e não roda no servidor da web. 
      Ao enviar a consulta no servidor local  aparece no console :Fetch terminou o carregamento: GET ".../Cidade?cidade=sao%20paulo". A pesquisa é feita normalmente.
      Quando mando a mesma pesquisa para o servidor web(locaweb) aparece no console: Fetch terminou o carregamento: GET "..../Cidade?cidade=sao%2520paul".
      O número 25 aparece só no servidor web. Pelo que pesquisei 25 significa %, ou seja, está duplicando o caractere %. 
      Não consegui utilizar nenhuma função para evitar que isto ocorra. Alguém sabe se há alguma configuração no servidor web que possa ser alterada para evitar essa duplicação?
       
    • Por gersonab
      Boa tarde, estou quebrando a cabeça aqui para somar os dados agrupados de vários itens distintos de uma tabela e inserir em outra. tipo :
      $consulta31 = $pdo->query("SELECT SUM(valoror) AS val1, SUM(metros) AS metr, orc, idcatc, idmate FROM orcamencli WHERE orc=$orc GROUP BY idcatc, idmate"); $user331 = $consulta31->fetch(PDO::FETCH_ASSOC); $orcx = $user331['orc']; $idcatcx = $user331['idcatc']; $val1x = $user331['val1']; $metrx = $user331['metr']; $idmatex = $user331['idmate']; o resultado de cada item agrupado eu faria um outro calculo separado para inserir em outra tabela, tipo ...
      a tabela acima eu teria algo assim :
       
      orcx = 01 - 01 - 01 - 01 - 01 - 01 - 01 - 01 - 01
      idcatcx = 11 - 11 - 11 - 11 - 11 - 11 - 11 - 11 - 11
      val1x = 1,00 - 1,00 - 3,00 - 3,00 - 3,00 - 1,00 - 3,00 - 1,00 - 2,00
      metrx =  01 - 01 - 03 - 03 - 03 - 01 - 03 - 01 - 02
      idmatex =  11 - 11 - 21 - 21 - 21 - 31 - 31 - 31 - 31
       
      preciso inserir  na outra tabela o seguinte :
      orc = 01 - 01 - 01
      idcatc = 11 - 11 - 11
      val1 = 2,00 - 9,00 - 7,00 ( aqui não é só a soma dos valores , existe um outro cálculo )
      metr =  02 - 09 - 07
      idmate =  11 -  21 -  31
       
       a dúvida é --> como inserir estes valores separados de uma única vez em outra tabela.
×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.