PDA

Ver la Versión Completa : Necesito hacer un INNER JOIN de 3 tablas eficiente


MarianitoSAPO
14/12/10, 10:53:39
Tengo 3 tablas:

LFA1:
- lifnr (Clave)
- land1
- ...

LFB1:
- lifnr (Clave)
- bukrs (Clave)
- pernr
- ...

LFBK:
- lifnr (Clave)
- banks (Clave)
- bankl (Clave)
- bankn (Clave)
- bkont
- ...

lo que necesito es un
INNER JOIN entre LFA1 y LFB1 en lifnr
y ésta un LEFT OUTER JOIN con la LFBK en lifnr


Mi primera solución fue:
select lifnr land1 ....
from LFA1 as a INNER JOIN LFB1 as b on ....
OUTER JOIN ....
pero me dijeron que era ineficiente, que usara FOR ALL ENTRIES


entonces implementé lo siguiente:


SELECT lifnr land1 name1 name2 ktokk kunnr loevm
sperr stcd1 stcd2 vbund stceg profs
FROM lfa1
INTO TABLE i_aux1.

SELECT lifnr bukrs loevm sperr
FROM lfb1
INTO TABLE i_aux2
FOR ALL ENTRIES IN i_aux1
WHERE lifnr = i_aux1-lifnr.

SELECT lifnr banks bankl bankn bvtyp bkont
FROM lfbk
INTO TABLE i_aux3
FOR ALL ENTRIES IN i_aux2
WHERE lifnr = i_aux2-lifnr.

" EXTREMADAMENTE INEFECIENTE
LOOP AT i_aux1 INTO r_aux1.
r_maestros-lifnr = r_aux1-lifnr.
r_maestros-land1 = r_aux1-land1.
r_maestros-name1 = r_aux1-name1.
r_maestros-name2 = r_aux1-name2.
r_maestros-ktokk = r_aux1-ktokk.
r_maestros-kunnr = r_aux1-kunnr.
r_maestros-loevm = r_aux1-loevm.
r_maestros-sperr = r_aux1-sperr.
r_maestros-stcd1 = r_aux1-stcd1.
r_maestros-stcd2 = r_aux1-stcd2.
r_maestros-vbund = r_aux1-vbund.
r_maestros-stceg = r_aux1-stceg.
r_maestros-profs = r_aux1-profs.

"Usamos un loop ya que no accedemos mediante clave
LOOP AT i_aux2 INTO r_aux2 WHERE lifnr = r_aux1-lifnr.
r_maestros-bukrs = r_aux2-bukrs.
r_maestros-so_loevm = r_aux2-so_loevm.
r_maestros-so_sperr = r_aux2-so_sperr.

"Condicion LEFT OUTER JOIN
LOOP AT i_aux3 INTO r_aux3 WHERE lifnr = r_aux2-lifnr.
r_maestros-banks = r_aux3-banks.
r_maestros-bankl = r_aux3-bankl.
r_maestros-bankn = r_aux3-bankn.
r_maestros-bvtyp = r_aux3-bvtyp.
r_maestros-bkont = r_aux3-bkont.

" VER si este append no deberia ir tambien fuera de este loop
" en el caso de que no se ingrese a este LOOP
APPEND r_maestros TO i_maestros.

ENDLOOP.

ENDLOOP.
ENDLOOP.


El gran problema es que el campo lifnr
NO es clave de la tabla LFB1 por lo tanto debo
anidar un loop, y además para la condición
LEFT OUTER JOIN debo anidar otro loop
ya que lifnr tampoco es clave de LFBK.

Como imaginarán, esto tarda un poco mas que una
eternidad.

Existe una solución mas eficiente???, cuando usamos
FOR ALL ENTRIES y los campos que accedemos
no son claves (No podemos usar READ TABLE) y
por lo tanto debemos anidar loops.

Además de la ineficiencia, con la condición
LEFT OUTER JOIN se me plantéo que el APPEND
del loop mas interno debería hacer dentro
del loop si consiguió algún registro,
SINO fuera (con las campos NULL) si no consiguio
ningún registro.
Estaría bien, eso?

Saludos,
Gracias

ppchico
14/12/10, 12:42:27
Lo mejor que puedes hacer, es chequear la eficiencia en SE30. Accede a esa transacción y pulsa en TIPS & TRICKS. Una vez dentro, declara las variables etc. y pon los códigos con los INNER y con el código LOOP y lo comparas... no olvides ejecutarlo varias veces, puesto que cambia en ocasiones dependiendo de la conexión a la BBDD los índices etc..

A ver si te vale eso!
Suerte.
Javier.

MarianitoSAPO
14/12/10, 13:28:00
Gracias por tu ayuda,
calcule el performance, y el resultado fue:
Usando Select .... INNER JOIN .... LEFT JOIN ....:
33 seg, 95%bd resto Abap y sistema

Usando For All Entries:
797 seg(24 veces mas lento)
y aproxmadamente 98% Abap y resto sistema y BD.

Es increible la diferencia,
mi duda es
cuando es mas performante un INNER JOIN que un FOR ALL ENTRIES?

Mi hipotesis:
Calculo que es mejor un FOR ALL ENTRIES cuando accedemos
por claves a las tablas (Y podemos usar READ TABLE),
el cuál no es mi caso, y por lo tanto debo anidar 3 loops.

ppchico
14/12/10, 13:33:57
Esto depende mucho también de las tablas etc. Por ejemplo, las tablas de documentos de material son tablas muy pesadas, y la selección aun accediendo por clave, si la tabla que usas para el FOR ALL ENTRIES es muy pesada, va a dar también un tiempo de respuesta demasiado alto... Lo mejor para optimizarlo es en primer lugar, traer solo los datos que necesitas... el SELECT * en tablas con bastantes datos puede hacer lento el acceso a BBDD, mientras que igual solo necesitas 20 o 30 campos de esa tabla... por otro lado, a mi personalmente me gusta hacer el acceso a base de datos de una vez, y luego tratar los datos recuperados. En tu caso, yo creo que podría ser factible una mezcla de ambas cosas... hacer el LEFT OUTER JOIN por un lado (Para evitar anidad dos bucles) y recuperar el resto de la información por otro lado... y una vez que lo tengas en ambas tablas, montar la tabla final... pero esto es cuestión también de probarlo!!

Suerte pues!! Y ojalá encuentres la solución más optima!

Un Saludo,
Javier.

VLozano
15/12/10, 10:39:57
¿Compruebas que el primer SELECT devuelva datos antes de ejecutar el FOR ALL ENTRIES? En caso de no hacerlo, si no hay datos el programa baja TODA LA TABLA, ignorando totalmente el WHERE... a partir de ahí, todo se va al carajo.

La elección entre JOIN, FOR ALL ENTRIES y subqueries (LOOP-SELECT) debe hacerse con cuidado, y teniendo en cuenta muchos factores, como el tamaño de las tablas, los índices utilizables, la velocidad y la carga del servidor de datos...

En tu caso yo haría una tercera prueba: INNER JOIN + FOR ALL ENTRIES.

cubanito
15/12/10, 17:16:18
Hola MarianitoSAPO...
LIFNR si es llave en las tres tablas... y a menos que tengan activos índices diferentes, si puedes usar ese campo como llave y hasta me atrevería a decirte que sería mucho mas rápido un INNER JOIN.
Por otro lado, es REGLA INVIOLABLE asegurarse SIEMPRE antes de hacer un FOR ALL ENTRIES que la tabla usada para ello no venga VACÍA!!!

Y por último, el código está muy mal hecho!!! No hay ninguna necesidad de usar esos LOOPS anidados!!! No necesitas usar ese AUX2 para hacerle ese LOOP a AUX3 ya que el LIFNR es el mismo en todas desde AUX1.
por lo tanto podrías dejarlo así:
LOOP AUX1.
LOOP AUX2 WHERE LIFNR = AUX1-LIFNR
ENDLOOP.

LOOP AUX3 WHERE LIFNR = AUX1-LIFNR
ENDLOOP.
ENDLOOP.

Además, podrías hacerle un SORT a todas tus tablas por el campo LIFNR antes de hacer los LOOP's e ir borrando los registros ya leídos para ir reduciendo el tamaño de las tablas internas:

SORT: AUX1, AUX2, AUX3 by LIFNR.
LOOP a AUX1.
LOOP a AUX2 WHERE LIFNR = AUX1-LIFNR.
<Realiza operaciones>
DELETE AUX2.
ENDLOOP.

LOOP a AUX3 WHERE LIFNR = AUX1-LIFNR
<Realiza operaciones>
DELETE AUX3.
ENDLOOP.
ENDLOOP.

Y si aún quieres mas velocidad, entónces mete tus tablas y tus work areas en FIELD-SYMBOLS y has los LOOPS con ellos:

FIELD-SYMBOLS: <AUX1> like aux1,
<W_AUX1> like line of aux1,
<AUX2> like aux2,
<W_AUX2> like line of aux2,
<AUX3> like aux3,
<W_AUX3> like line of aux3.

ASSIGN: aux1 TO <AUX1>,
aux2 TO <AUX2>,
aux3 TO <AUX3>.

LOOP AT <AUX1> ASSIGNING <W_AUX1>.
LOOP AT <AUX2> ASSIGNING <W_AUX2>.
<Realiza operaciones>
DELETE <AUX2>.
ENDLOOP.

LOOP AT <AUX3> ASSIGNING <W_AUX3>.
<Realiza operaciones>
DELETE <AUX3>.
ENDLOOP.
ENDLOOP.

Saludos!!!