En uno de los proyectillos personales que estoy haciendo me he encontrado con la necesidad de parsear sentencias insert de SQL y extraer los valores que se introducen en la tabla. Una primera idea puede ser la de sacar el substring después de la cadena "VALUES" hasta el final de la línea y usar después el método split con el carácter ','. Sin embargo esto tiene sus inconvenientes, y es que los valores introducidos no pueden tener comas. Obviamente, esto es una castaña y no vale para nada así que tocó ver si alguien más había tenido este problema y si de casualidad había alguna expresión regular por ahí... Lo mejor que encontré fue esto.
(INSERT INTO\s+)(\w+)(\s+\()([\w+,?\s*]+)(\)\s+VALUES\s+)((\(['?\w+'?,?\s*]+\)\,?;?\s*)+)
Lamentablemente, esto no cumplía mis necesidades... Lo único que encuentra son caractéres alfanuméricos y adiós al resto. No me quedaba ninguna otra alternativa así que abrí mi Expresso dispuesto a refrescar esas regex. Después de mucho pelearme por fín he conseguido una expresión que cubre mis necesidades y un programa que me muestra cada valor de forma independiente:
(?:INSERT INTO\s+)`(?:\w+)`(?:\s+\()(?:[`\w+`,?\s*]+)(?:\)\s+VALUES\s*)\(?(?<values>\w+|'.*?')(?:,\s*(?<values>\w+|'.*?'))*\);
La parte más importante de la consulta es (?<values>\w+|'.*?') (las dos veces que aparece). Por una parte le indicamos que todos los "aciertos" los meta dentro del grupo values. Por otro le decimos que "matchee" cualquier carácter ( '.*?' ) que esté entre comillas simples ( '.*?' ), que se repita 0 o n veces ( '.*?' ) y que además sea lo más pequeño posible (as few as possible). Y en esto radica el éxito. ¿Que pasaría si no lo hubiéramos separado? Pues que adiós a la correcta separación de los campos (haced la prueba y ya veréis).
El código en C# que muestra cada imprime por pantalla cada campo es el siguiente:
1: string pattern = @"(?:INSERT INTO\s+)`(?:\w+)`(?:\s+\()(?:[`\w+`,?\s*]+)(?:\)\s+VALUES\s*)\(?(?<values>\w+|'.*?')(?:,\s*(?<values>\w+|'.*?'))*\);";
2: MatchCollection mc = Regex.Matches(line, pattern);
3:
4: foreach (Match match in mc)
5: {
6: foreach (Capture capture in match.Groups["values"].Captures)
7: {
8: Console.WriteLine(capture.Value);
9: }
10: }
siendo line la línea que representa el INSERT (¡en una sola línea!).
Por fin puedo parsear cosas del estilo:
INSERT INTO `wp_postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES
(944, 79, 'keywords', 'Steve, jobs, apple, ipod, barrapunto, free, gratis, superordenador, computación,'),
(951, 81, '_edit_lock', '1211789640');
¿De dónde habré sacado yo una cosa tan fea?