ODBC-SQL: Usare i nomi dei campi

11 02 2008

google_translate.gif Translate To English!

Talvolta, avendo a che fare con ODBC e l’sql, farebbe comodo estrarre i dati di un campo facendo riferimento al suo nome, e non alla sua posizione (1, 2, 3, etc…).

Purtroppo, fin troppo spesso i linguaggi non offrono, di base, questa funzionalita`. Fortunatamente e` una funzione facilmente implementabile. Ecco come:

;==============================================================================
; @SYNTAX (get-field-id argFieldName )
;	argFieldName: name of the field. It is case-sensitive.
;
; @RETURN the position of the field in the record.
;
; @EXAMPLE
;	> (ODBC:query "...")
;	> (setq result (ODBC:fetch-row) )
;	> (println (string (result (get-field-id "BARCODE_IP") ) ) )
;   "1.2.3.4"                       ^^^^^^^^^^^^^^^^^^^^^
;
; @NOTE
;	Database should be open, with the record already fetched.
;==============================================================================

(define (get-field-id argFieldName ,
   locColDescription	; Description of the columns of the current record.
)
   (setq locColDescription (map ODBC:column-atts (sequence 1 (ODBC:num-cols))) )
   (index
      (fn (x)
         (= (string (x 0)) argFieldName )
      );fn	

      locColDescription
   );index
);define

O piu` brevemente (ma forse un po` meno leggibile…):

(define (get-field-id argFieldName , locColDescription)	; Description of the columns of the current record.
   (setq locColDescription (map ODBC:column-atts (sequence 1 (ODBC:num-cols))) )
   (index (fn(x)(= (string (x 0)) argFieldName ) );fn
      locColDescription );index
);define

Come si usa? E` molto semplice. Prima di tutto aprite una connessione ODBC valida, usando ODBC:connect.
Poi preparare ed eseguite la vostra query select con ODBC:query. Infine estraete un record con ODBC:fetch-row.
A questo punto, per estrarre i dati del record appena letto, usate la precedente funzione get-field-id. Questa funzione altro non fa` che ricercare la posizione del campo di cui si e` chiesto il dato (il nome del campo lo dovrete passare come argomento alla funzione stessa).

Ecco un esempio (illustrato anche nell’intestazione della funzione stessa):

> (ODBC:query "...")
> (setq result (ODBC:fetch-row) )
> (println (string (result (get-field-id "BARCODE_IP") ) ) )
"1.2.3.4"

La funzione (get-field-id “BARCODE_IP”) ci restituisce la posizione del campo BARCODE_IP. Tale valore e` usato per estrarre il campo n-esimo di result, la quale contiene la lista di dati del record estratto.

Spero possa esservi d’aiuto (io la uso quotidianamente!).

Una ultima nota: trovo meraviglioso poter risolvere un problema come questo senza ricorrere a dei cicli (esempio usando il FOR) come invece si farebbe in molti altri linguaggi di programmazione. newLISP e` semplice, pulito, espressivo.

A presto! :-)





Mappare le colonne con i loro nomi

30 01 2008

google_translate.gif Translate To English!

Translate to English Usando i drivers ODBC con newLisp vi accorgerete ben presto che, a seguito di una SELECT, vi verranno ritornati i dati, riga per riga (usando ODBC:fetch-row), in una lista. Il problema e` che sarete costretti a prendere i dati di ogni campo usando la posizione (“0″ per il primo campo; “1″ per il secondo; etc…). Sarebbe molto piu` pratico trovare i campi per nome.

NewLisp non finira` mai di stupirmi: ho iniziato ad affrontare il problema questa mattina, e l’ho risolto in pochissimo tempo. Ma cio` che mi ha colpito di piu` e` l’eleganza con cui si possono fare queste cose.
Questo e` il codice per fare la mappatura:

(let (conta -1) (setq mappatura (map (fn(x) (inc 'conta) (list conta (ODBC:column-atts x) ) ) (sequence 1 (ODBC:num-cols)))) )

Ok, ok… avete ragione… scriviamolo un po` meglio! :-)

(let (conta -1)
  (setq mappatura(map
      (fn(x)
        (inc 'conta)
        (list conta (ODBC:column-atts x) )

      );fn

      (sequence 1 (ODBC:num-cols) ) )
    );map
  );setq
);let

Pulito ed elegante:espressivo. Sono sicuro che si potrebbero fare molte ottimizzazioni, ma questo l’ho fatto in pochissimo, e funziona benissimo!

Come si usa? Cosa fa`?

L’uso e` semplice: aprite una connessione al vostro DB (ODBC:connect), poi lanciate una query SELECT (ODBC:query). Poi chiamiamo una fetch… e` tutto (ODBC:fetch-row).

Facendo girare il precedente programmino, esso vi restituira` una lista con il numero del campo ed il suo nome. Esempio:

(0 ("STATUS00                         " 12 30))
(1 ("ACTION00                         " 12 5))
(2 ("MACHINE00                        " 12 30))
(3 ("COD_PRJ00                        " 12 13))

A questo punto la variabile mappatura conterra` i dati precedenti. Quindi, usando il lookup, completeremo la magia! Quindi…

> (lookup 4 mappatura)
("MACHINE00                        " 12 30)

Quindi…

> (string ((lookup 4 mappatura) 0))
"MACHINE"

Semplicissimo!

Ma cosa fa` il programma esattamente? Analizziamolo pezzo per pezzo.

L’espressione (let conta -1 mi permette di creare una variabile locale, con valore -1.
L’espressione (sequence 1 (ODBC:num-cols) ) genera una lista di numeri da 1 al numero di colonne ottenute come risultato della SELECT (nel nostro caso sono 4).
La funzione lamba fn(x) (vi ricordo che la dicitura fn e` sinonimo di lambda), grazie all’uso della funzione map, prende i valori della sequence precedente, un valore alla volta, (nel nostro caso 1, 2, 3, 4), e restituisce una nuova lista composta dal contatore (conta) e dai dati restituiti dalla funzione ODBC:column-atts.
Quest’ultima funzione restituisce il nome della colonna, il tipo di dato (stringa/intero/etc…), e la lunghezza di una colonna nella posizione x; scrivendo quindi (ODBC:column-atts 3) otterremo i dati della prima colonna (si inizia a contare da uno):

("MACHINE00                        " 12 30)

La variabile conta, essendo esterna alla funzione, viene incrementata ogni volta, ed indica la posizione del campo nella lista del record prelevato.

La funzione map e` molto potente, espressiva ed elegante. Grazie al suo utilizzo, unito ad una funzione lambda, siamo riusciti a fare quello che, in moltissimi altri linguaggi, ci avrebbe costretto a cicli FOR, creazione di variabili temporanee, etc…

NewLisp e` magnifico!

A presto!





Query parametriche

30 01 2008

google_translate.gif Translate To English!

Poco fa` mi sono trovato di fronte ad un problema piuttosto interessante. Stavo scrivendo una query per un database Oracle, e dovevo rendere questa query parametrizzabile.
La query era simile alla seguente:

SELECT * FROM MiaTable WHERE campo1 = :parametro1 AND campo2 = :parametro2

Il campo1 era un numero intero, mentre il campo2 era una stringa.

Ho allora creato una piccola funzione in grado di sostituire automaticamente, e in un colpo solo, tutti i parametri della query. Inoltre la stessa funzione, in base alle indicazioni fornite, poteva formattare i parametri nel modo corretto (per esempio aggiungendo gli apici singoli intorno alla stringa).

La funzione e` questa:

(define (replace-sql argSql argReplace , (delimiter "") )
	(dolist (i argReplace)
 		(if (= (i 1) 'string) (setq delimiter "'"))
		(replace (i 0) argSql (string delimiter (i 2) delimiter)  )
	 );dolist

	argSql	; Valore di ritorno
)

Come si usa? Semplicissimo!
Il primo parametro e` la query stessa (non importa se e` una SELECT, una INSERT o altro!) ed il secondo parametro e` una lista di elementi da sostituire nella query. Ecco un esempio completo:

;Query parametrica.
(setq sql "select * from myTable where t1 = :t1 and t2 = :t2")

; Questi sono i parametri da sostituire.
(setq valori '(
    (":t1" integer "100")
    (":t2" string "Pippo")
) )

(replace-sql sql valori)

La variabile valori contiene delle liste, in cui ognuna e` cosi` composta:

  1. Nome completo del parametro (esempio: “:t1″)
  2. Tipo di dato (per ora solo “string” esegue una vera formattazione aggiungendo gli apici; potete pero` modificare molto facilmente la funzione originale aggiungendo altri tipi di dato).
  3. Valore del campo (esempio: “Pippo”)

La funzione ritornerà il codice SQL con tutti i valori correttamente sostituiti.

Attenzione: poiche` viene eseguito un “brutale” replace, non usate parametri che possano avere nomi che compaiono in altre parti del codice sql stesso, altrimenti effettuerete il replace anche di altre parti di codice! Vi suggerisco nomi tipo: <par1> oppure @par1@.

Spero che questa funzione vi possa essere utile!

Alla prossima!

UPDATE: su suggerimento di Carlo (grazie!), ho modificato la funzione per poter riconoscere, in modo automatico, il datatype dei parametri.
La funzione completa e` la seguente:
;==============================================================================
; @SYNTAX (replace-params SQL-Code parameters-list)
;	SQL-Code	: SQL statement.
;	parameters-list	: List composed in this way:
;			     ( params01 params02 params03 ... )
;			  Every "param" is a list, and is composed in this way:
;			     (param-name datatype value)
;				param-name	: name of the parameter
;				value		: value that will substitute the parameter (string)
;				datatype	: datatype (e.g.: integer, string, date, etc...).
;						  This value is optional. If not passed, it will be automatically
;						  detected from the function self.
;
;	Currently, parameter type can be one of the following:
;		string	: the value will be surrounded with single apex.
;		date	: the value must be in the following format: DD-MM-YYYY.
;					It will be automatically formatted by this function.
;		float	:
;		integer	: will be inserted without any other extra-job.
;
; @RETURN SQL string with all requested terms replaced.
;
; Replace one or more sql parameters with the real values.
;
; @EXAMPLE
;	In this example, the original sql string is "sql". The variable "val"
;	contains two lists that contain needed info to make the replacement.
;		":t1" is INTEGER and the value is "100"
;		":t2" is STRING and the value is "Alessandro"
;
; 		(setq sql "select * from myTable where t1 = :t1 and t2 = :t2")
;
; 		(setq val '(
; 			(":t1" integer "100")
; 			(":t2" "Alessandro")
; 		) )
;
;		(replace-params sql val)
;==============================================================================

(define (replace-params argSql argReplace ,
	(locPrefix "") 		; string before the value
	(locPostfix "")		; string after the value
	(locDatatype nil)	; datatype for a specific value
)
	(dolist (i argReplace)
		(if (= (length i) 2)	; Datatype is missing: I will take the default one.
			(cond 						;IF
				( (string? (i 1) ) 	(setq locDatatype 'string) )
				( (integer? (i 1)) 	(setq locDatatype 'integer) )
				( (float? (i 1) ) 	(setq locDatatype 'float) )
			);cond
			(setq locDatatype (i 2)	)	;ELSE
		);if

		(cond
			(;condition_1
				(= locDatatype 'string) (setq locPrefix "'" locPostfix "'")
			)
			(;condition_2
				(= locDatatype 'date) (setq locPrefix "to_date(" locPostfix {, 'DD-MM-YYYY')} )
			)
		);cond

		(replace (i 0) argSql (string locPrefix (i 1) locPostfix)  )
	);dolist

	argSql
)