Wednesday, October 27, 2010
My first official LEFT JOIN
Tuesday, October 26, 2010
Add new parameter to Stored Procedure without impacting existing call
Add another parameter to my SP to handle the transaction or not, but If I do this the signature (DDL) of my SP is going to change!
There is one way to do this, something like adding polymorphism, on this case overload SP.
This is my original SP: On my iBatis side I'm calling this SP with this number of Params (5), If I add a new param, I'll have to add a new one to my DDL, right?
Not necessarily, because If you add another parameter but you don't declare it as IN neither OUT, it will be optional!!!
Then I can do this: And nobody will notice that I change it, because you can call it with 5 params (The 6th param will be given by default in the signature: DEFAULT 1) or with 6 (assigning 0 for example to the 6th parameter)
Inside the body of my SP I'm doing something like this:
Set Timeout when accessing Oracle with iBatis
There is a call which takes too much time, we don't want to wait until it's done, so in this case we can timeout our transaction and kill it on the Oracle side (well we don't kill/stop anything, we just abandon the process without any adverse impact);
Then we'll receive something like this from the DB:
ORA-01013: user requested cancel of current operationHow do you do that??
According to iBatis documentation, we can do it globally or for each statement/call we make by changing the value of defaultStatementTimeout
defaultStatementTimeout This setting is an integer value that will be applied as the JDBC query timeout for all statements. This value can be overridden with the “statement” attribute of any mapped statement. If not specified, no query timeout will be set unless specified on the “statement” attribute of a mapped statement. The specified value is the number of seconds the driver will wait for a statement to finish. Note that not all drivers support this setting.
Or we can do it Specifically for each statement element by setting the attribute timeout, for example:
Thursday, October 14, 2010
Create List of STRUCT based on a Query
In Oracle to create a new instance of this kind of object, the default Contructor is given by definition of the object, for example in this case to create a new instance of CUST_ACCNT_MTN_PRE_ORDER, my contructor will be something like this
And this is my Collection/List/Table of Objects
My SP is something like this (As you'll see I'm using the same objects I defined in that entry)
And my solution is something like this
The secret is in this line: SELECT CUST_ACCNT_MTN_PRE_ORDER(CUST_ID, ACCT_NUM, NPA || NXX || TLN, -1, 'X', MTN_ORDER_TYPE, SYSDATE, NVL(EO_ALT_NPA||EO_ALT_NXX||EO_ALT_TLN,0)) BULK COLLECT INTO p_list
I'm creating for each row an object of type CUST_ACCNT_MTN_PRE_ORDER and then the ResultSet is set INTO p_list variable using BULK COLLECT, which fetch all rows once
IF you want to see my Stored Proc, this is what I did
CREATE OR REPLACE PROCEDURE record_ship_temp_orders( out_orders_processed OUT NUMBER ) IS my_cust_id CUST_ACCT_ORDER.cust_id%TYPE; my_acct_num CUST_ACCT_ORDER.acct_num%TYPE; my_pos_order_id CUST_ACCT_ORDER.pos_order_id%TYPE; my_pos_pre_order_id CUST_ACCT_ORDER.pos_pre_order_id%TYPE; my_pos_loc_cd CUST_ACCT_ORDER.pos_loc_cd%TYPE; my_total_order_lines CUST_ACCT_ORDER.total_order_lines%TYPE; my_ord_add_lines_cnt CUST_ACCT_ORDER.add_lines_cnt%TYPE; my_ord_upg_lines_cnt CUST_ACCT_ORDER.upg_lines_cnt%TYPE; p_list CUST_ACCNT_MTN_PRE_ORDER_LIST; OUT_CIDB_ORDER_ID NUMBER; --This is the main cursor CURSOR cur_test_init_load IS SELECT CUST_ID_NO AS CUST_ID, ACCT_NO AS ACCT_NUM, EQ_ORD_NO AS POS_ORDER_ID, NULL AS POS_PRE_ORDER_ID, NETACE_LOC_ID AS POS_LOC_CD FROM TEST_INIT_LOAD GROUP BY CUST_ID_NO, ACCT_NO, EQ_ORD_NO, NULL, NETACE_LOC_ID ORDER BY CUST_ID_NO, ACCT_NO, EQ_ORD_NO, NETACE_LOC_ID; /****************************************************************************** NAME: record_ship_temp_orders PURPOSE: Retrieve data loaded into TEST_INIT_LOAD to be processed by Record_Pre_Order and then shipped by Ship_Pre_Order. DATE: Oct 2010 Referenced by: To be executed internally ******************************************************************************/ BEGIN DBMS_OUTPUT.put_line('Starting prcess '||TO_CHAR(SYSTIMESTAMP,'hh:mi:ss.FF')); OPEN cur_test_init_load; IF cur_test_init_load%NOTFOUND THEN DBMS_OUTPUT.put_line('No data found on TEST_INIT_LOAD'); CLOSE cur_test_init_load; raise_application_error(-20102, 'No data found on TEST_INIT_LOAD'); END IF; out_orders_processed := cur_test_init_load%ROWCOUNT; --Before the first fetch, %ROWCOUNT returns 0 DBMS_OUTPUT.put_line('Start record '||TO_CHAR(SYSTIMESTAMP,'hh:mi:ss.FF')); LOOP --Iterate cursor FETCH cur_test_init_load INTO my_cust_id, my_acct_num, my_pos_order_id, my_pos_pre_order_id, my_pos_loc_cd; IF out_orders_processed = 0 THEN out_orders_processed := cur_test_init_load%ROWCOUNT; --Set a value to the OUT param END IF; EXIT WHEN cur_test_init_load%NOTFOUND;--Exits when process is completed DBMS_OUTPUT.put_line('Processing '||my_cust_id||'-'||my_acct_num||', my_pos_order_id='||my_pos_order_id||', my_pos_pre_order_id='||my_pos_pre_order_id||', my_pos_loc_cd='||my_pos_loc_cd); --Calculate total_order_lines, add_lines_cnt & upg_lines_cnt SELECT count(1) AS TOTAL_ODER_LINES INTO my_total_order_lines FROM TEST_INIT_LOAD WHERE CUST_ID_NO = my_cust_id AND ACCT_NO = my_acct_num AND EQ_ORD_NO = my_pos_order_id; SELECT count(1) AS ADD_LINES_CNT INTO my_ord_add_lines_cnt FROM TEST_INIT_LOAD WHERE CUST_ID_NO = my_cust_id AND ACCT_NO = my_acct_num AND EQ_ORD_NO = my_pos_order_id AND ESN_CHG_RSN_CD IS NULL; SELECT count(1) AS UPG_LINES_CNT INTO my_ord_upg_lines_cnt FROM TEST_INIT_LOAD WHERE CUST_ID_NO = my_cust_id AND ACCT_NO = my_acct_num AND EQ_ORD_NO = my_pos_order_id AND ESN_CHG_RSN_CD IS NOT NULL; DBMS_OUTPUT.put_line('my_total_order_lines '||my_total_order_lines||', my_ord_add_lines_cnt='||my_ord_add_lines_cnt||', my_ord_upg_lines_cnt='||my_ord_upg_lines_cnt); --Generate List of Items to be inserted into CUST_ACCT_MTN_ORDER SELECT CUST_ACCNT_MTN_PRE_ORDER(CUST_ID, ACCT_NUM, NPA || NXX || TLN, -1, 'X', MTN_ORDER_TYPE, SYSDATE, NVL(EO_ALT_NPA||EO_ALT_NXX||EO_ALT_TLN,0)) BULK COLLECT INTO p_list FROM ( SELECT CUST_ID_NO AS CUST_ID, ACCT_NO AS ACCT_NUM, NPA , NXX , TLN, NVL(ESN_CHG_RSN_CD,'A') AS MTN_ORDER_TYPE, EO_ALT_NPA,EO_ALT_NXX,EO_ALT_TLN FROM TEST_INIT_LOAD WHERE CUST_ID_NO = my_cust_id AND ACCT_NO = my_acct_num AND EQ_ORD_NO = my_pos_order_id AND ESN_CHG_RSN_CD IS NULL UNION SELECT CUST_ID_NO AS CUST_ID, ACCT_NO AS ACCT_NUM, NPA , NXX , TLN, 'U' AS MTN_ORDER_TYPE, EO_ALT_NPA,EO_ALT_NXX,EO_ALT_TLN FROM TEST_INIT_LOAD WHERE CUST_ID_NO = my_cust_id AND ACCT_NO = my_acct_num AND EQ_ORD_NO = my_pos_order_id AND ESN_CHG_RSN_CD IS NOT NULL ); FOR i in 1 .. p_list.count LOOP DBMS_OUTPUT.put_line ('STRUCT '||i||'=> mtn='||p_list(i).mtn||', mtn_order_type='||p_list(i).mtn_order_type||', bdy_mtn='||p_list(i).bdy_mtn); END LOOP; --Call SP's BEGIN DBMS_OUTPUT.put_line('Calling Record_Pre_Order'); RECORD_PRE_ORDER ( my_cust_id, my_acct_num, my_pos_order_id, my_pos_pre_order_id, my_pos_loc_cd, my_total_order_lines, my_ord_add_lines_cnt, my_ord_upg_lines_cnt, 0, p_list, OUT_CIDB_ORDER_ID ); DBMS_OUTPUT.put_line('Calling Ship_Pre_Order'); SHIP_PRE_ORDER ( my_cust_id, my_acct_num, my_pos_order_id, my_pos_loc_cd, OUT_CIDB_ORDER_ID ); DBMS_OUTPUT.put_line('End record '||TO_CHAR(SYSTIMESTAMP,'hh:mi:ss.FF')); DBMS_OUTPUT.put_line('-------'); p_list := NULL; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line ('***ERROR*** '||SQLERRM); out_orders_processed := out_orders_processed - 1; --Decrease failed orders END; END LOOP; DBMS_OUTPUT.put_line('Process completed'); DBMS_OUTPUT.put_line('End of process '||TO_CHAR(SYSTIMESTAMP,'hh:mi:ss.FF')); CLOSE cur_test_init_load; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (SQLERRM); ROLLBACK; RAISE; END; /
Friday, October 8, 2010
SQL Joins explained with Venn's Diagrams
Una simple ayuda
El lenguaje SQL es aveces el gran olvidado por los desarrolladores, cada vez abundan mas los frameworks que abstraen al desarrollador del contacto con el modelo de negocio.
He escrito este documento, basándome en otros similares para ayudar a entender con un diagrama de Vann, los diferentes tipos de Join's que SQL92 soporta.
Existen tecnologías que abstraen completamente del modelo de negocio, para el desarrollador funcionan creando una serie de clases de Dominio que define el modelo, sin importar qué base de datos o de qué fabricante sea esta. Por ejemplo Ruby on Rails, GRails, ... usando un conjunto de tecnologías, como Hibernate configuradas por convención dentro del propio framework.
También es muy extendido el uso de aplicaciones que permiten modelar el negocio de forma gráfica, ERM (como DBSchema), y normalmente después se usa un ORM's que les hacen el trabajo sucio de forma elegante y segura.
Todo vale, pero la realidad de las empresas TIC es que necesitan profesionales serios y conscientes que entiendan y controlen todas las capas para que un sistema funciona como se espera y está diseñado, y no se deje nada al azar.
El desarrollador, muy frecuentemente recurre a activar trazas o aplicaciones de monitorización de actividad que nos desvelen, qué está haciendo nuestro framework con nuestro modelo de negocio. Existen aplicaciones que esnifan directamente de la base de datos esta información para que podamos analizarla, el obsoleto Profile y Analyzer que incluía Microsoft SQL Server es un ejemplo de ellos.
Finalmente cuando tenemos delante la query sucede que hace tanto tiempo que no trabajamos con SQL que no entendemos qué hace exactamente, sobre todo si está trabajando con tablas relacionadas y nos encontramos con una consulta que afecta a varias entidades.
Espero que este documento ayude a reducir el tiempo invertido a descubrir porqué se comporta una SQL de un modo u otro.
ConvencionesEste documento asume que siempre que la "TABLA A" esta a la izquierda y la "TABLA B" a la derecha de las sentencias. Para los ejemplos vamos a utilizar 2 tablas que van a contener los siguientes datos:
|
|||||||||||||||||||||||||||||||||||||
INNER JOINSELECT * FROM TableA INNER JOIN TableB ON TableA.name = TableB.name
El resultado son solo el conjunto de registros que coinciden en ambas tablas. |
|||||||||||||||||||||||||||||||||||||
FULL OUTER JOINSELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name
El resultado es el conjunto total de registros de ambas tablas, coincidiendo aquellos registros cuando sea posible. Si no hay conicidencias, se asignan nulos. |
|||||||||||||||||||||||||||||||||||||
FULL OUTER JOIN WHERESELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableA.id IS null OR TableB.id IS null
El resultado es un conjunto de records únicos en la TablaA y en la TablaB, hacemos el Full Outer Join y excluimos los registros que no queremos con el Where |
|||||||||||||||||||||||||||||||||||||
LEFT OUTER JOINSELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name
El resultado son todos los registros de la TablaA, y si es posible las coincidencias de la TablaB. Si no hay coincidencias, el lado derecho mostrará nulos. |
|||||||||||||||||||||||||||||||||||||
LEFT OUTER JOIN WHERESELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableB.id IS null
El resultado es un conjunto de registros que solo están en la TablaA, no en la TablaB. Hacemos lo mismo que en un Left Outer Join, pero eliminamos los registros que no queremos de la TablaB con el Where |
|||||||||||||||||||||||||||||||||||||
CROSS JOINExiste también la posibilidad de cruzar todos los registros con todos (producto cartesiano), imposible de dibujar con un diagramas VennSELECT * FROM TableA CROSS JOIN TableB Imaginarse el resultado de todos los registros por todos es muy fácil, si tenemos 4 registros en cada tabla 4 x 4 = 16. |
¡Ojo al hacer esto en tablas con muchos registros! |