48 - Subconsultas con in

Vimos que una subconsulta puede reemplazar una expresión. Dicha subconsulta debe devolver un valor escalar o una lista de valores de un campo; las subconsultas que retornan una lista de valores reemplazan a una expresión en una cláusula "where" que contiene la palabra clave "in".

El resultado de una subconsulta con "in" (o "not in") es una lista. Luego que la subconsulta retorna resultados, la consulta exterior los usa.

La sintaxis básica es la siguiente:

 ...where EXPRESION in (SUBCONSULTA);

Este ejemplo muestra los nombres de las editoriales que han publicado libros de un determinado autor:

 select nombre
  from editoriales
  where codigo in
   (select codigoeditorial
     from libros
     where autor='Richard Bach');

La subconsulta (consulta interna) retorna una lista de valores de un solo campo (codigo) que la consulta exterior luego emplea al recuperar los datos.

Podemos reemplazar por un "join" la consulta anterior:

 select distinct nombre
  from editoriales as e
  join libros
  on codigoeditorial=e.codigo
  where autor='Richard Bach';

Una combinación (join) siempre puede ser expresada como una subconsulta; pero una subconsulta no siempre puede reemplazarse por una combinación que retorne el mismo resultado. Si es posible, es aconsejable emplear combinaciones en lugar de subconsultas, son más eficientes.

Se recomienda probar las subconsultas antes de incluirlas en una consulta exterior, así puede verificar que retorna lo necesario, porque a veces resulta difícil verlo en consultas anidadas.

También podemos buscar valores No coincidentes con una lista de valores que retorna una subconsulta; por ejemplo, las editoriales que no han publicado libros de un autor específico:

 select nombre
  from editoriales
  where codigo not in
   (select codigoeditorial
     from libros
     where autor='Richard Bach');

Problema resuelto

Trabajamos con las tablas "libros" y "editoriales" de una librería.
Eliminamos las tablas si ya existen:

drop table if exists libros;
drop table if exists editoriales;

Creamos las dos tablas:

create table libros(
	codigo integer primary key,
	titulo text,
	autor text, 
	precio real,
	codigoeditorial integer	
);

create table editoriales(
	codigo integer primary key,
	nombre text
);

Insertamos una serie de filas en cada tabla:

 insert into editoriales(nombre) values('Planeta');
 insert into editoriales(nombre) values('Emece');
 insert into editoriales(nombre) values('Paidos');
 insert into editoriales(nombre) values('Siglo XXI');

 insert into libros(titulo,autor,codigoeditorial) values('Uno','Richard Bach',1);
 insert into libros(titulo,autor,codigoeditorial) values('Ilusiones','Richard Bach',1);
 insert into libros(titulo,autor,codigoeditorial) values('Aprenda PHP','Mario Molina',4);
 insert into libros(titulo,autor,codigoeditorial) values('El aleph','Borges',2);
 insert into libros(titulo,autor,codigoeditorial) values('Puente al infinito','Richard Bach',2);

Queremos conocer el nombre de las editoriales que han publicado libros del autor "Richard Bach":

 select nombre
  from editoriales
  where codigo in
   (select codigoeditorial
     from libros
     where autor='Richard Bach'); 

Probamos la subconsulta separada de la consulta exterior para verificar que retorna una lista de valores de un solo campo:

 select codigoeditorial
  from libros
  where autor='Richard Bach';	 

Podemos reemplazar por un "join" la primera consulta:

 select distinct nombre
  from editoriales as e
  join libros
  on codigoeditorial=e.codigo
  where autor='Richard Bach';

También podemos buscar las editoriales que no han publicado libros de "Richard Bach":

 select nombre
  from editoriales
  where codigo not in
   (select codigoeditorial
     from libros
     where autor='Richard Bach');

Problema propuesto

Una empresa tiene registrados sus clientes en una tabla llamada "clientes", también tiene una tabla "ciudades" donde registra los nombres de las ciudades.

  1. Borrar las tablas "clientes" y "ciudades" si existen.

  2. Crear las tablas con las siguientes estructuras:

     create table ciudades(
      codigo integer primary key,
      nombre text
     );
    
     create table clientes (
      codigo integer primary key,
      nombre text,
      domicilio text,
      codigociudad integer
     );
    
  3. Almacenar las siguientes filas como datos de ejemplo:

     insert into ciudades (nombre) values('Cordoba');
     insert into ciudades (nombre) values('Cruz del Eje');
     insert into ciudades (nombre) values('Carlos Paz');
     insert into ciudades (nombre) values('La Falda');
     insert into ciudades (nombre) values('Villa Maria');
    
     insert into clientes(nombre,domicilio,codigociudad) values ('Lopez Marcos','Colon 111',1);
     insert into clientes(nombre,domicilio,codigociudad) values ('Lopez Hector','San Martin 222',1);
     insert into clientes(nombre,domicilio,codigociudad) values ('Perez Ana','San Martin 333',2);
     insert into clientes(nombre,domicilio,codigociudad) values ('Garcia Juan','Rivadavia 444',3);
     insert into clientes(nombre,domicilio,codigociudad) values ('Perez Luis','Sarmiento 555',3);
     insert into clientes(nombre,domicilio,codigociudad) values ('Gomez Ines','San Martin 666',4);
     insert into clientes(nombre,domicilio,codigociudad) values ('Torres Fabiola','Alem 777',5);
     insert into clientes(nombre,domicilio,codigociudad) values ('Garcia Luis','Sucre 888',5);
    
  4. Necesitamos conocer los nombres de las ciudades de aquellos clientes cuyo domicilio es en calle "San Martin", empleando subconsulta.

  5. Obtenga la misma salida anterior pero empleando join.

  6. Obtenga los nombre de las ciudades de los clientes cuyo apellido no comienza con una letra específica, empleando subconsulta.

  7. Pruebe la subconsulta del punto 5 separada de la consulta exterior para verificar que retorna una lista de valores de un solo campo.

Solución
drop table if exists ciudades;
drop table if exists clientes;

 create table ciudades(
  codigo integer primary key,
  nombre text
 );

 create table clientes (
  codigo integer primary key,
  nombre text,
  domicilio text,
  codigociudad integer
 );
 
 insert into ciudades (nombre) values('Cordoba');
 insert into ciudades (nombre) values('Cruz del Eje');
 insert into ciudades (nombre) values('Carlos Paz');
 insert into ciudades (nombre) values('La Falda');
 insert into ciudades (nombre) values('Villa Maria');

 insert into clientes(nombre,domicilio,codigociudad) values ('Lopez Marcos','Colon 111',1);
 insert into clientes(nombre,domicilio,codigociudad) values ('Lopez Hector','San Martin 222',1);
 insert into clientes(nombre,domicilio,codigociudad) values ('Perez Ana','San Martin 333',2);
 insert into clientes(nombre,domicilio,codigociudad) values ('Garcia Juan','Rivadavia 444',3);
 insert into clientes(nombre,domicilio,codigociudad) values ('Perez Luis','Sarmiento 555',3);
 insert into clientes(nombre,domicilio,codigociudad) values ('Gomez Ines','San Martin 666',4);
 insert into clientes(nombre,domicilio,codigociudad) values ('Torres Fabiola','Alem 777',5);
 insert into clientes(nombre,domicilio,codigociudad) values ('Garcia Luis','Sucre 888',5); 

select nombre
  from ciudades
  where codigo in
   (select codigociudad
     from clientes
     where domicilio like 'San Martin %'); 
	 
 select distinct ci.nombre
  from ciudades as ci
  join clientes as cl
  on codigociudad=ci.codigo
  where domicilio like 'San Martin%';	 
  
 select nombre
  from ciudades
  where codigo not in
   (select codigociudad
     from clientes
     where nombre like 'G%');  
	 
 select codigociudad
  from clientes
  where nombre like 'G%';	 

Ejecución de ejercicios online

Puede ejecutar comandos de SQLite directamente en el sitio sin tener que instalar nada en su computadora.

Resultado.....