Ciao a tutti, con questo articolo riprendiamo un argomento trattato tempo fa da Andrea Busi dedicato a SQLite e, nello specifico, cercheremo di ampliare le conoscenze sull’integrazione dei database nelle nostre applicazioni iPhone e iPad spiegando come inserire, modificare o eliminare dati dal DB, un argomento utile che ogni sviluppatore iOS prima o poi dovrà affrontare.
Creare il database SQLite
Come ormai saprete sicuramente, SQLite è l’unica risorsa che possiamo utilizzare se vogliamo implementare un database interno alle nostre applicazioni, certo ci sono altri metodi per la gestione dei dati (CoreData, Parsing, …) ma quello che interessa a noi oggi è proprio SQLite. Se ancora non sapete come si costruisce un Database SQLite, potete leggere il precedente tutorial di Andrea (linkato sopra) che spiega come creare il db tramite un ottimo plugin gratuito per firefox: SQLite Manager, che potete scaricare seguendo questo indirizzo, in alternativa, potrete creare il vostro db tramite terminale (presente in ogni Mac) o ancora tramite qualche software a pagamento di terzi, come SQLiteManager o SQLiteConverter, recensiti sulle nostre pagine.
Cosa vedremo in questo tutorial?
Quello di oggi, in realtà, non sarà un vero e proprio tutorial passo passo, cercherò invece di fare una carellata delle cose principali da sapere per lavorare con questo tipo di database, e in particolare mi soffermerò sulle tre funzioni principali, praticamente indispensabili:
- Caricare valori dal DB
- Inserire valori nel DB
- Eseguire query (delete, update..) per eliminare e aggiornare i record del DB
Bene, partiamo!
La prima cosa da fare, ovviamente, è creare un database. Fatto questo, prima di iniziare con la programmazione occorre considerare una cosa fondamentale: se dovete lavorare su un File, quindi inserire od eliminare dati dovete NECESSARIAMENTE copiarlo nella cartella “Documents” della vostra app. Questo perché i file che trascinate dentro il vostro progetto faranno parte dell pacchetto iniziale dell’applicazione e, tutti i file all’interno di questo pacchetto, sono disponibili in realtà in SOLA LETTURA. Senza questo piccolo accorgimento, non sarete in grado di lavorare con i dati sul database.
Copiare il database SQLite nella cartella documents
Creato il database, prendete il file generato e trascinatelo nel vostro progetto in Xcode (nel menù ad albero sulla sinistra) facendo attenzione a copiarlo. Come detto poco fa, però, questo non basta e dovremo creare una copia del file nella cartella Document della nostra applicazione. Per far questo, nel metodo viewDidLoad (o in qualsiasi altro metodo che venga richiamato prima dell’utilizzo del DB) aggiungiamo il codice necessario per effettuare la copia:
//percorso file su cartella documents
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *path = [documentsDir stringByAppendingPathComponent:@"nomeDB.sqlite"];
//controllo se il file esiste
if(![[NSFileManager defaultManager] fileExistsAtPath:path])
{
//se non esiste lo copio nella cartella dosuments
NSString *pathLocale=[[NSBundle mainBundle] pathForResource:@"nomeDB" ofType:@"sqlite"];
if ([[NSFileManager defaultManager] copyItemAtPath:pathLocale toPath:path error:nil] == YES)
{
NSLog(@"copia eseguita");
}
}
Vediamo cosa abbiamo appena scritto. Le prime 3 righe di codice servono per ottentere il path in cui andremo a mettere il DB, nel nostro caso la cartella documents.
Con pathLocale andiamo invece ad ottenere il percorso del nostro file dentro il progetto, cioè quello in sola lettura per intenderci, quindi copiamo il file da pathLocale a path.
Fatto questo siamo certi che il nostro Database sarà a questo punto disponibile anche in scrittura.
Prepariamo il progetto e importiamo la libreria SQlite
Andiamo ora a studiare le tre funzioni per interagire con il nostro bellissimo database posizionato al posto giusto. Per prima cosa, nella nostra classe, quella in cui andremo ad implementare le funzioni, dobbiamo importare la classe libsqlite3.dylib, ricordiamoci di aggiungere nel relativo file di intestazione (.h) l’import:
#import
Nel file di implementazione (.m), quindi, sotto l’istruzione:
#import
aggiungiamo il seguente codice:
static sqlite3 *database = nil;
static sqlite3_stmt *selectstmt = nil;
static sqlite3_stmt *addStmt = nil;
static sqlite3_stmt *delStmt = nil;
La prima riga è il puntatore al DB, mentre le alte due sono gli statement per operare sul database. Il primo per selezionare gli elementi, il secondo per l’inseriemento degli elementi, mentre l’ultimo è infine per l’eliminazione.
Leggere i valori (record) dal database SQLite
Partiamo ora con il primo metodo, quella per ottenere (ovvero leggere) i valori dal nostra database SQLite:
-(NSArray *)caricaValoriDaDBStandard:(NSString *)query :(NSArray *)arrayKey
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:@"nomeDB.sqlite"];
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
// query che ricava i valori
const char *sql = [query UTF8String];
sqlite3_stmt *selectstmt;
// lista temporanea
NSMutableArray *listaTemp = [[[NSMutableArray alloc] init] autorelease];
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK)
{
while(sqlite3_step(selectstmt) == SQLITE_ROW)
{
// Oggetto che contiene i vari elementi
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
// ricaviamo i valori letti dalla query
for (int k=0; k<[arrayKey count]; k++)
{
//NSMutableString *contenuto = [NSMutableString alloc];
if (sqlite3_column_text(selectstmt, k) == NULL)
{
//NSMutableString *contenuto = [[NSMutableString alloc] initWithFormat:@""];
//NSLog(@" %@ \n", contenuto);
[dictionary setObject:@"" forKey:[arrayKey objectAtIndex:k]];
//[contenuto release];
}
else
{
NSMutableString *contenuto = [[NSMutableString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt, k)];
//NSLog(@" %@ \n", contenuto);
[dictionary setObject:contenuto forKey:[arrayKey objectAtIndex:k]];
[contenuto release];
}
}
[listaTemp addObject:dictionary];
[dictionary release];
}
}
NSLog(@"%@", listaTemp);
sqlite3_close(database);
return listaTemp;
}
else
{
sqlite3_close(database);
NSMutableArray *listaTemp = [[[NSMutableArray alloc] init] autorelease];
return listaTemp;
}
}
ricordate di dichiarare nel file .h il metodo, in questo modo:
-(NSArray *)caricaValoriDaDBStandard:(NSString *)query :(NSArray *)arrayKey;
Come funziona? In pratica a questa funzione viene passata la stringa contenente la query più un array contenente le chiavi dei valori che andremo a recuperare. Per esempio, se dobbiamo ottenere nomeutente, username e password dal db, dovremo creare una stringa simile a questa:
"SELECT nomeutente, username, password FROM miaTabella"
e un array che conterrà le 3 stringhe "nomeutente", "username" e "password". Otterremmo quindi un array in cui all'interno sarà presente un dizionario per ogni riga (record) del database e ogni valore sarà contrassegnato con la chiave inserita nell'array (ricordatevi di inserire le chiavi nello stesso ordine in cui le mettete nella query).
Passiamo ad analizzare il codice. Nelle prime 3 righe otteniamo il percorso del nostro DB. Successivamente apriamo la connessione con esso e, se tutto è andato correttamente a buon fine, possiamo procedere.
Trasformiamo la nostra query in UTF8 (formato compatibile per i DB) e creiamo lo statement per l'esecuzione della query.
Il secondo if lancia la query e se il risualtato ottenuto è SQLITE_OK significa che tutto è andato bene.
A questo punto non ci resta che "ciclare" su ogni riga di risultato, tramite sqlite3_step. Quello che facciamo dentro il while è un po' più semplice: dichiariamo un dizionario e a questo punto sappiamo quanti elementi abbiamo ottenuto della query (grazie all'array di chiavi) creiamo un for e andiamo a riempire il nostro valore, associando il valore preso dal DB con
sqlite3_column_text(selectstmt, k)
dove k parte da 0 e arriva al numero di valori che dobbiamo ottenere e come chiave la stringa presa dall'array di chiavi. Se il valore ottenuto dalla query fosse null, inseriamo come valore una stringa vuota. Aggiungiamo il dizionario all'array principale e ricominciamo il ciclo finchè non abbiamo più record a disposizione. Infine chiudiamo il database.
Ecco quindi un esempio di come possiamo ricavare valori dal nostro database:
NSString * query = @” SELECT nomeutente, username, password FROM miaTabella”;
NSArray * arrayQuery = [[NSArray alloc] initWithObjects:@”nomeutente”,@”username”,@”password”,nil];
NSArray * arrayElementr = [self caricaValoriDaDBStandard:query :arrayQuery];
così facendo otterremo un array composto da un dizionario per ogni riga del DB, quindi accediamo ai valori con le chiavi inserite nell'array di partenza.
Inserire dati nel database SQLite
Vediamo ora come fare per inserire degli elementi nell'array.
Questa è la dichiarazione nel .h:
-(void)inserisciValoriDaDB:(NSArray *)insert :(NSString *)sqlAdd;
questo motedo funziona così:
sqlAdd sarà la nostra query di inserimento, mentre l'array insert conterrà gli elementi da inserire, per esempio se volete inserire "nomeutente, username, password" creeremo analogamente all'esempio precedente, un array contenenete le stringhe dei 3 elementi.
Ecco il codice:
- (void)inserisciValoriDaDB:(NSArray *)insert :(NSString *)sqlAdd
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:@"nomeDB.sqlite"];
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
if(addStmt == nil)
{
const char *sql = [sqlAdd UTF8String];
if(sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL) != SQLITE_OK)
NSAssert1(0, @"Error while creating add statement. '%s'", sqlite3_errmsg(database));
}
for (int k=0; k < [insert count]; k++)
{
sqlite3_bind_text(addStmt, (k+1), [[insert objectAtIndex:k ]UTF8String], -1, SQLITE_TRANSIENT);
NSLog(@"%@", [insert objectAtIndex:k ]);
}
if(SQLITE_DONE != sqlite3_step(addStmt))
{
NSAssert1(0, @"Error while inserting data. '%s'", sqlite3_errmsg(database));
}
sqlite3_reset(addStmt);
addStmt = nil;
}
else
sqlite3_close(database);
}
Le prime tre istruzioni le abbiamo già viste, servono per ottenere il percorso del DB. Apriamo quindi il DB, controlliamo che lo statement per l'inserimento sia nil (questo per evitare di fare operazioni mentre se ne stanno eseguendo altre) e se lo è, procediamo con la preparazione della query nello statement.
Fatto questo procediamo con il ciclo for. Qui prendiamo l'array che abbiamo passato al metodo (che ci indica il numero di colonne che dobbiamo inserire) e inseriamo nello statement ogni element da scrivere, a questo punto non ci resta altro che eseguire lo statement con
sqlite3_step(addStmt)
Fatto questo resettiamo lo statement e lo impostiamo a nil per poterlo riutilizzare.
Per poter utilizzare questo metodo la nostra Stringa di query deve essere composta da due parti:
@"INSERT INTO nomeTabella ( nomeutente, username, password) VALUES (?, ?, ?)"
INSERT INTO deve essere sempre presente, seguito dal nome della tabella e tra parentesi i campi che dobbiamo inserire seguiti da VALUES, ovvero i valori che dovranno essere inseriti nello statement. Infine occorre un ? per ogni valore messo dentro le parentesi e che quindi sarà presente anche nell'array.
Eliminare dati dal database SQLite
Vediamo ora il metodo di eliminazione, molto simile a quello di inserimento.
Nel file di intestazione (.h) dichiariamo il metodo:
- (void)eliminaValoriDaDB:(NSString *)number :(NSString *)sqlString;
Le query per l'eliminazione funzionano in modo analogo a quelle di inserimento. Su number avrò il valore da mettere dentro lo statement mentre la query sarà dello stesso stampo di quelle per l'inserimento:
@"DELETE FROM nomeTabella WHERE ID =?"
Usiamo sempre il simbolo ? per inseirire la stringa o il valore da eliminare.
- (void)eliminaValoriDaDB:(NSString *)number :(NSString *)sqlString
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *dbPath = [documentsDir stringByAppendingPathComponent:@"nomeDB.sqlite"];
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
if(delStmt == nil)
{
const char *sql = [sqlString UTF8String];
//const char *sql = [sqlDel UTF8String];
if(sqlite3_prepare_v2(database, sql, -1, &delStmt, NULL) != SQLITE_OK)
NSAssert1(0, @"Error while creating del statement. '%s'", sqlite3_errmsg(database));
}
if (![number isEqualToString:@""] || number != nil)
{
sqlite3_bind_int(delStmt, 1, [number intValue]);
}
if (SQLITE_DONE!= sqlite3_step(delStmt)) {
NSAssert1(0, @"Error while deleting. '%s'", sqlite3_errmsg(database));
}
sqlite3_reset(delStmt);
delStmt = nil;
}
else
sqlite3_close(database);
}
Come vedete il codice è pressochè simile a quello per inserire i valori nel DB tranne il controllo sulla stringa number che usiamo per controllare che non sia vuota o nulla.
Modificare i dati (record) nel database SQLite
I metodi appena illustrati sono tre esempi di quello che possiamo fare con un database SQLite, volendo potremmo realizzare anche un metodo per fare l'update (ovvero l'aggiornamento di un record), cambiando semplicemente la query del metodo di inserimento.
Conclusioni
L'ultima cosa da modificare è il
+(void)finalizeStatements
qui dobbiamo chiudere gli statement, se sono aperti quindi diventerà:
+(void)finalizeStatements
{
if(database)
sqlite3_close(database);
if (selectstmt) {
sqlite3_finalize(selectstmt);
}
if (addStmt)
{
sqlite3_finalize(addStmt);
}
if (delStmt)
{
sqlite3_finalize(delStmt);
}
}
E' tutto! Spero possa esservi utile per integrare le operazioni da fare con i vostri database SQLite.
Alla prossima 🙂
Andrea Cappellotto
29 Responses to “T#089 – Inserire, modificare ed eliminare dati da un DB SQLite nelle nostre applicazioni iPhone e iPad”
16 Marzo 2011
danielaFantastico Grazie !
Una domanda, se tra i campi del file sqlite, ho il nome di un file tipo”file.htm”, come faccio ad estrapolarlio per visualizzarli in una view ( o una webview?).
Ho provato a fare così ma non va:
NSString *fileDitta = [NSString stringWithFormat:@”%@”,[itemAtIndex objectForKey:@”filehtm”]];
Grazie 🙂
17 Marzo 2011
TacaOttimo! Un buon inizio di argomento..
Ma come si fa a visualizzare i dati ricavati in elenchi e viste in dettaglio?
17 Marzo 2011
Andrea Cappellottociao, per caricare dei file html devi usare una webview utilizzando quella scritta ottieni il nome del file html, ma poi devi trovare il percorso del file…. prova a postare una discussione nel forum:)
17 Marzo 2011
MatteoCiao a tutti vorrei sapere se è possibile in un app per iOS leggere dati da un db esterno online. Se sì, avete mai fatto un tutorial che lo spieghi? Grazie, ottimo sito.
18 Marzo 2011
Andrea Cappellottociao, per collegarti ad un DB on-line ti devi creare un webservice per farti ritornare i dati tramite richieste…. non ci si può collegare direttamente ad un db on-line
20 Marzo 2011
fabiosalve,
vorrei sapere se possibile come posso far salvare i dati della mia app ogni giorno ad un’ora prestabilità??o cmq alla riapertura dell’app dopo quell’ora…grazie in anticipo e complimenti per il sito e il tutorial
20 Marzo 2011
gianlucasaroniComplimenti per la guida ma ho un problema… uso xcode4 e non so dove inserire il file .sqlite… quale sarebbe la cartella “Documents” dell’app?? io l’ho inserito dove sono tutte le classi pero non me lo trova infatti mi dice “not found” (parlo del metodo
-(void)inserisciValoriDaDB:(NSArray *)insert :(NSString *)sqlAdd;
il primo non l’ho implementato ancora perchè cmq devo prima inserire nel db da app…
help!
ciao
22 Marzo 2011
nemoCiao a tutti, scusate la mia ignoranza ma non capisco dove sia la famosa cartella Documents, devo crearla nel progetto o cosa?Vi ringrazio
22 Marzo 2011
nemoCome non detto ora ho capito
28 Marzo 2011
LuigiPer caso avete il sorgente del progetto?
Grazie,
Luigi
1 Aprile 2011
UovoCiao, ma come faccio a richiamare il metodo
-(NSArray *)caricaValoriDaDBStandard:(NSString *)query:(NSArray *)arrayKey ?
Grazie
7 Aprile 2011
DevAlCiao! Grazie per l’ottimo tuttorial.
Credo di aver omesso qualcosa ma non riesco a venirne a capo. Gli oggetti all’interno del mio array sono formattati in questo modo:
{
tabella = campo1;
},
{
tabella = campo2;
},
ma io ho la necessità di avere all’interno dell’array solo gli oggetti “campo”, pensi sia un problema di query?
Grazie.
14 Aprile 2011
SilviaCiao!
Ho seguito passo passo il tutto, ma la copia che viene effettuata risulta essere vuota. Potete aiutarmi?
Grazie
24 Aprile 2011
Scinfuil metodo di eliminazione non è tanto chiaro .
il valore per ID è un numero ma di cosa ? se io ho solo come nel esempio USER,PASSWORD,NOME
ID non c’è perciò va in crash .
29 Maggio 2011
Stefano CatanzaroIl file del progetto potete pubblicarlo? grazie!
14 Giugno 2011
PaulBlackSalve sono nuovo della programmazione iPhone e mi sto cimentando, dopo aver letto diversi manuali su di una applicazione di ricerca su di una database sqlite. Ho provato ad implementare la classe sopra descritta ma quando vado a compilare mi da una warning del tipo : warning: ‘Basequadra’ may not respond to ‘-SelectDB::’. Qualcuno mi può aiutare? Grazie
15 Giugno 2011
iCiccioDove è possibile reperire il tutorial completo? ho trovato solo quello di Andrea, ma non implementato con quanto descritto sopra. grazie
15 Giugno 2011
PaulBlackIl problema precedente è stato risolto ma adesso utilizzando “caricaValoriDaDBStandard” da come risultato una NSArray che contiene a sua volta un NSDictionary..Scusate ma mia poca esperienza ma come si fa a leggere i dati con la key impostata? Mi spiego il tutorial dice che cito:
“così facendo otterremo un array composto da un dizionario per ogni riga del DB, quindi accediamo ai valori con le chiavi inserite nell’array di partenza.”
Ma c’è qualcuno che ha avuto il mio stesso problema e sopratutto c’è qualcuno che me lo può spiegare. Vi ringrazio dell’eventuale collaborazione.
P.S.Come ho detto sono alle prime armi con la programmazione Obj-C.
23 Giugno 2011
Giuseppesalve a tutti! è possibile leggere e recuperare la password da un file db?
19 Agosto 2011
milonetInteresserebbe tantissimo anche a me.. il metodo migliore per fare questa cosa? devo fare un file xml che legge dati da un db e pubblicarlo in http o meglio https ?
26 Agosto 2011
LucaCiao a tutti, vorrei avere un’informazione.
Ho inserito il codice di questo tutorial su una app che sto sviluppando. Tutto funziona perfettamente. Il problema è nella gestione della memoria, ad ogni query continuo ad allocare della memoria senza mai riuscire a svuotarla. A qualcuno è gia capitato? Avete una soluzione?
Grazie
9 Gennaio 2012
AlessandroCiao a tutti,
ho una tableView che legge i dati da una tabella (database sqlite) e nella vista di dettaglio mi riempe automaticamente delle label con i valori presenti nei campi.
Il problema è il seguente:
– quando apro l’applicazione e seleziono una cella, la vista di dettaglio ha tutte le label vuote, dalla seconda selezione in poi tutte le label nella vista di dettaglio sono valorizzate.
Qualcuno sa perchè?
23 Gennaio 2012
FABIOraga ma a voi funziona la insert eseguita sul device e non sul simulatore??
14 Marzo 2012
Mcon un Oracle XML DB
che tipo di webservice mi conviene utilizzare?
thx
23 Maggio 2012
Konrad78Ciao a tutti, avrei un informazione da chiedervi.
ho realizzato la mia prima applicazione che utilizza un db sqlite.
dovrei rilasciare un primo aggiornamento ma ho un dubbio,
se rilascio questa versione, il db installato in precedenza dagli utenti verrà sovrascritto con questo vuoto? Negli aggiornamenti è possibile indicare i file da non sovrascrivere?
Vi ringrazio in anticipo.
Konrad
16 Luglio 2012
salvobefciao,
volevo sapere se è possibile sommare gli elementi di una colonna della query e se si come.
E in generale se ad esempio ho un database con più tabelle, se è possibile raggruppare e filtrare i valori.
Ad esempio prima tabella formata da:
id – nome – punteggio
seconda tabella:
id – classe- scuola – numero allievi
e cosi via.
come faccio a fare sommare il punteggio e filtrare i dati in maniera personalizzata.
Vi ringrazio anticipatamente, siete veramente eccezionali.
17 Luglio 2013
RaffaeleSu iOs7 (xCode5) Sqlite sembra che non ci sia, qualcuno di voi ha provato? Probabilmente perchè è ancora in beta, anche se mi sembra strano.
7 Ottobre 2013
GiovanniNon riesco a copiare il database dentro Documents… il path locale è null
17 Marzo 2014
giovanniciao, è possibile avere il file di progetto? non riesco a venirne a capo e quando faccio clicco sul pulsante ‘+’ mi va in crash.