Manipuler simplement du CSV grace a SQLite

ViPHP
ViPHP | 5462 Messages

08 nov. 2010, 12:28

Il arrive souvent en tant que dèv, de manipuler du CSV, beaucoup de personnes utilisent Excel, c'est donc un format simple d'import/export pour une base de données.

Malheureusement il est moins souple a manipuler et filter qu'une base SQL, Mysql propose nativement le stockage en CSV, et quelques astuces pour pouvoir en manipuler un directement, mais qui nécessite un accès complet au serveur puisqu'il faut directement agir sur le dossier /data.

Ici on va voir une méthode un peu plus passe partout et rapide grâce a SQLite, qui propose d'ouvrir une base en mémoire avec :memory: (et si la base est trop grosse rien n'empêche de créer un fichier temporaire .db).

On va donc utiliser 2 classe : PDO et SplFileObject, donc PHP 5 , SQLite étant compiler par défaut avec PDO pas de soucis a se niveau la (même chez free).
- PDO aura l'avantage de la requête preparé (qui existe aussi sous Sqlite3), et commence à être connu de tous.
- SplFileObject fait partie de la lib SPL, parcourir du CSV sera donc beaucoup rapide (et plus simple)

Pour le CSV (test.csv) :
Jean|Machin|Paris
Jean|Truc|Nice
Paul|Bla|Nice
Pour le code :
try 
{
    $dbh = new PDO('sqlite::memory:');
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e)
{
    exit($e->getMessage());
}    

$file = new SplFileObject('test.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY);
$file->setCsvControl('|');


$dbh->exec("CREATE TABLE test (lastname, firstname, city)");
$stmt = $dbh->prepare("INSERT INTO test VALUES (?, ?, ?)");    

foreach($file as $row)
{
    $stmt->execute($row);
}

$query = $dbh->query("SELECT * FROM test ORDER BY firstname");

echo '<table>';

while($row = $query->fetch(PDO::FETCH_ASSOC))
{
    echo '<tr><td>' . implode('</td><td>', $row) . '</td></tr>';
}

echo '</table>';

les 3 parties différentes :

la connexion : ici pas de mystères, c'est du PDO classique, avec un DSN SQLite, la base étant :memory:, elle sera donc effacer a la fin du script
try 
{
    $dbh = new PDO("sqlite::memory:");
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e)
{
    exit($e->getMessage());
}  
l'import :
- On crée un SplFileObject avec un flag CSV pour lu dire que c'est du CSV, et le flag SKIP_EMPTY pour omettre les lignes vierges (souvent a la fin) , avec setCsvControl on lui passe son séparateur, ici c'est le |
- On crée la table manuellement
- On crée notre requête préparer pour les INSERT
- On boucle sur l'iterator, chaque $row étant l'array de la ligne CSV
$file = new SplFileObject('test.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY);
$file->setCsvControl('|');

$dbh->query("CREATE TABLE test (lastname, firstname, city)");
$stmt = $dbh->prepare("INSERT INTO test VALUES (?, ?, ?)");    

foreach($file as $row)
{
    $stmt->execute($row);
}
la manipulation : la base étant créee et les données importées, à vous de faire ce que bon vous semble :wink:
$query = $dbh->query("SELECT * FROM test ORDER BY firstname");

echo '<table>';

while($row = $query->fetch(PDO::FETCH_ASSOC))
{
    echo '<tr><td>' . implode('</td><td>', $row) . '</td></tr>';
}

echo '</table>';
Astuce : si le CSV a une taille assez conséquente, ou que vous devez économisé de la RAM, on peux mettre la base dans un fichier temporaire php qui sera effacer a la fin du script
$temp = tmpfile();
$info = stream_get_meta_data($temp);

try 
{
    $dbh = new PDO("sqlite:{$info['uri']}");
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e)
{
    exit($e->getMessage());
}

ViPHP
AB
ViPHP | 5818 Messages

08 nov. 2010, 18:52

Super, très pratique :pouce:

La même version "automatisée" pour des fichiers CSV dont la première ligne contient les entêtes de colonnes. Dans ce cas de figure reste juste à paramétrer les deux premières lignes et éventuellement la troisième.
<?php

$fichier = 'test.csv';
$separateur = ';';
$table_temp = 'test_csv';


try
{
    $dbh = new PDO('sqlite::memory:');
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e)
{
    exit($e->getMessage());
}    


$file = new SplFileObject($fichier);
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY);
$file->setCsvControl($separateur);


$tab_champs = $file->current();

$champs_sqlite = implode(',',$tab_champs);

$champs_insert = array_fill(0,count($tab_champs),'?');

$champs_insert = implode(',',$champs_insert);


$dbh->exec("CREATE TABLE $table_temp ($champs_sqlite)");

$stmt = $dbh->prepare("INSERT INTO $table_temp VALUES ($champs_insert)");    


$file->next();

while($row = $file->current())
{        
    $stmt->execute($row);
    $file->next();
}


$query = $dbh->query("SELECT * FROM $table_temp");

echo '<table>';

while($row = $query->fetch(PDO::FETCH_ASSOC))
{
    echo '<tr><td>' . implode('</td><td>', $row) . '</td></tr>';
}

echo '</table>';
?>
Modifié en dernier par AB le 08 nov. 2010, 19:14, modifié 1 fois.

ViPHP
ViPHP | 5462 Messages

08 nov. 2010, 19:04

pas besoin de fgets, current te le formate direct en array, et pas obligé de faire un foreach si la première ligne dérange :wink:
$tab_champs = $file->current();
$file->next();
	
$champs_sqlite = implode(',',$tab_champs); 
$champs_insert = array_fill(0,count($tab_champs),'?'); 
$champs_insert = implode(',',$champs_insert);	
	
//...
 
while($row = $file->current())
{		
    $stmt->execute($row);
    $file->next();
}
on pourrais faire aussi dans le cas ou y'a pas d'entete par exemple : field1, field2 ....

ps : je t'ai répondu sur l'autre aussi :wink:

ViPHP
AB
ViPHP | 5818 Messages

08 nov. 2010, 21:01

ok code optimisé, enfin on ne gagne qu'environ 0.003 secondes sur un fichier de 5000 lignes mais bon... :)

ViPHP
ViPHP | 5462 Messages

09 nov. 2010, 15:22

ok code optimisé, enfin on ne gagne qu'environ 0.003 secondes sur un fichier de 5000 lignes mais bon... :)
c'est pour eviter de sauter la ligne, le foreach reset le tout, ca marchera mieux avec les lien web aussi, il aime par faire le rewind en stream

Eléphant du PHP | 185 Messages

10 nov. 2010, 01:02

Quel est l'intérêt de la partie SQLite dedans ? (pardonnez mon ignorance :oops: )

ViPHP
ViPHP | 5462 Messages

10 nov. 2010, 01:08

Quel est l'intérêt de la partie SQLite dedans ? (pardonnez mon ignorance :oops: )
faire des requêtes, des tris, des calcules bref tout ce qui est accessible via le SQL :wink:

Eléphant du PHP | 185 Messages

10 nov. 2010, 01:10

Ok, j'avais pas bien vu cette partie. En mettant un exemple contenant une des dites fonctionnalités et un ch'tit commentaire au-dessus ça serait tip-top ! ;)

ViPHP
ViPHP | 5462 Messages

10 nov. 2010, 01:15

Ok, j'avais pas bien vu cette partie. En mettant un exemple contenant une des dites fonctionnalités et un ch'tit commentaire au-dessus ça serait tip-top ! ;)
dans l'exemple y'a deja le tri, par ordre alphabétique sur les prénoms,

si on veux compter combien y'a de personne par ville on fera :
SELECT COUNT(id) FROM test_user GROUP BY city