Definire una relazione

È possibile stabilire delle relazioni tra le tabelle, in modo da poter sempre reperire tramite Operazioni di lettura i valori presenti nelle tabelle collegate.

Pensiamo per esempio all’indirizzo di un cliente, che dipende dall’indirizzo legato alla sua anagrafica. In questo modo aggiornando l’indirizzo nella tabella di origine anagrafica, verrà riproposto l’indirizzo aggiornato anche al momento di una nuova fatturazione dalla tabella fattura, questo automaticamente senza necessità di aggiornamento manuale.

In ogni record di Genropy è disponibile in qualunque momento anche l’intero record in relazione, individuabile percorrendo il relation_path contrassegnato dal simbolo con la @. Per esempio, aprendo l’Inspector di Genropy su un qualsiasi record di una tabella in relazione, troveremo la seguente situazione:

../_images/fatt-relazione.png

Seguendo quindi la relazione troveremo per intero il record in relazione, in questo caso il cliente associato alla fattura corrente.

Questo ci permetterà di reperire dati in qualsiasi momento da tutte le colonne, semplicemente seguendo la relazione in questione, tramite semplici Operazioni di lettura .

La relazione può essere definita come segue:

tbl.column('cliente_id',size='22', group='_',name_long='!![it]Cliente'
                                      ).relation('cliente.id',
                                                  relation_name='fatture',
                                                  mode='foreignkey',onDelete='raise')

A una normale colonna , quindi, di cui definiamo le caratteristiche come descritto in precedenza, possiamo appendere l’oggetto relation, che definiamo come segue:

  • con related_column='cliente.id' (o implicitamente, solo cliente.id), stabiliamo la relazione con la tabella cliente e con la colonna id. In questo caso omettiamo il package, in quanto è lo stesso della tabella di origine, altrimenti andrà indicato prima della tabella. Stabilendo la relazione con la pkey ne garantiamo l’assoluta unicità.

  • con relation_name='fatture' chiediamo che alla relazione inversa sia dato il nome fatture, in questo modo dalla tabella cliente, seguendo il path @fatture potremo individuare tutte le fatture del cliente. Si noti che il parametro è facoltativo, se non specificato di default verrebbe utilizzato nomepackage_nometable_nomecolonna, ma in questo modo rendiamo il nome della relazione più mnemonico e facilmente leggibile.

  • con mode='foreignkey' stabiliamo un vincolo di integrità referenziale a livello SQL e ci assicuriamo quindi che il database garantirà che i valori assegnati a questo campo siano validi ed effettivamente presenti nella tabella in relazione.

  • con onDelete='raise', infine, definiamo il comportamento da tenere in caso di eliminazione di un record, in questo caso verrà sollevata un’eccezione che impedisce l’eliminazione del record referenziato

Relazioni «uno a uno»

Di default, le relazioni in Genropy sono di tipo «molti a uno», ovvero a un record cliente possono corrispondere molti record fattura con questo in relazione.

È però possibile prevedere comportamenti differenti, per esempio in alcuni casi è possibile richiedere che la relazione sia uno a uno. Ipotizziamo per esempio di avere dei flussi ordine-DDT-fattura: a un ordine di un cliente seguirà un movimento di magazzino e infine verrà generata una fattura. In questo caso sarà un solo record di ordine a generare a sua volta un solo movimento ed infine una e una sola fattura.

La definizione di questa relazione come uno a uno avrà come conseguenza che la relazione sarà perfettamente percorribile in entrambi i versi.

La definizione avviene tramite l’aggiunta del parametro one_one (in questo caso vediamo quella nella tabella fattura, tra la fattura e il movimento):

tbl.column('movimento_id',size='22' , group='_', name_long='!![it]Movimento').relation(
                      'movimento.id', one_one='*', mode='foreignkey', onDelete='cascade')

Si noti che il parametro one_one può essere settato o a True, e in quel caso la relazione sarà individuabile al path completo @package_nometabella_nomecolonna, o alternativamente a one_one='*', in quel caso la relazione viene contraddistinta dal semplificato path @nome_tabella.

Relazioni «molti a molti»

La descrizione di una relazione «molti a molti», invece, ovvero quella per la quale a molti clienti possono corrispondere molte tipologie di servizi erogate, o a dei candidati molti titoli di studio, ecc, necessita la creazione di una tabella di relazione ad hoc.

Non sarà quindi presente un campo di relazione nella tabella stessa, bensì la relazione sarà identificata da una tabella di collegamento, che si occuperà di creare le due relazioni, una in una direzione e una in un’altra.

La relazione quindi tra un record candidato e dei titoli di studio sarà espressa, nella tabella di relazione che possiamo chiamare candidato_titolo_studio, in questo modo:

class Table(object):

  def config_db(self, pkg):
      tbl =  pkg.table('candidato_titolo_studio', pkey='id', name_plural='!!Titoli di studio candidato',
                       name_long=u'!!Titolo di studio candidato', caption_field='titolo_studio')
      self.sysFields(tbl)

      tbl.column('candidato_id',size='22',name_long = '!!Candidato',group='_').relation('candidato.id',
                                                                                  onDelete='cascade',
                                                                                  mode='foreignkey',
                                                                                  relation_name='titoli_studio')
      tbl.column('titolo_studio_id',size='22',name_long = '!!Titolo studio',group='_').relation('titolo_studio.id',
                                                                                  onDelete='cascade',
                                                                                  mode='foreignkey',
                                                                                  relation_name='candidati')

Si noti che nella connessa tabella candidato potremo seguire la relazione dal suo relation_name @titoli_studio, e da titolo_studio potremo trovare i candidati in @candidati.


Parametri:

Nome parametro

Tipo

Descrizione

related_column

T

Generalmente implicito, individua con quale colonna è stabilita la relazione (es: “glbl.comune.id”)

relation_name

T

Permette di dare un identificativo alla relazione inversa, individuando il valore che verrà restituito (es: nella tabella clienti, il relation_name potrebbe essere proprio “clienti”).

mode

T

Generalmente mode=”foreignkey”, a indicare che tra le due colonne vige un vincolo di integrità referenziale. Alternativamente può essere mode=”relation”, che definisce una relazione logica senza vincoli di integrati e «case- sensitive», oppure mode=”insensitive” (come il precedente ma «case-insensitive»). Se omesso, in quel caso di default mode=”relation”

onDelete

T

Permette di indicare il comportamento in caso di eliminazione. Valori possibili: “raise” (se c’è una relazione il campo non può essere eliminato), “cascade” (entrambi vengono eliminati), “ignore” (non succede nulla), “setnull” (la relazione viene eliminata)

onDelete_sql

T

Come il precedente, ma a livello di Sql, senza informare quindi eventuali trigger presenti

one_one

B

Se impostato a True, definisce la relazione come «uno a uno», con la conseguenza che la relazione diventa percorribile anche al contrario seguendo il path «package_nometabella_nomecolonna». È possibile utilizzare al posto di one_one=True, one_one=”*”, così facendo il path predefinito diventa solo @nome_tabella

eager_one

B

one_

T

L’etichetta data alla relazione inversa, al gruppo («one_group») o direttamente alla colonna («one_name»), se la relazione è uno a uno

many_

T

L’etichetta data alla relazione inversa, al gruppo («many_group») o direttamente alla colonna («many_name»), se la relazione è molti a molti

deferred

B

Se impostato a True, in caso di mode=”foreignkey” il sistema non andrà in errore se viene indicata una id per la relazione per la quale non è stato ancora effettuato il commit

inheritLock

B

Se impostato a False, in caso di record primario bloccato è comunque possibile aggiungere o modificare i record secondari

inheritProtect

B

Se impostato a False, in caso di record primario bloccato è comunque possibile aggiungere o modificare i record secondari

Autore della sezione: Davide Paci