Cody Pendant has asked for the wisdom of the Perl Monks concerning the following question:
I'm currently thinking about SQL selects and how they can be constructed, so that I can construct them from subroutines and make things more modular.
Something like
use strict; sub make_query { my @clauses = @_; my $count = 0; my $sql = "select * from employees \n"; foreach (@clauses) { if ($count > 0) { $sql .= ' AND ' } else { $sql .= ' WHERE ' } foreach ($_) { my @criteria = @{$_}; if ($criteria[2] !~ /^\d+$/) { $criteria[2] = "'$criteria[2]'"; } $sql .= " @criteria \n"; } $count++; } return $sql; } my @array = ( [ 'name', '=', 'smith' ], [ 'salary', '>', '50000' ], [ 'department', 'NOT LIKE', 'eng%' ] ); print make_query(@array); ### select * from employees ### WHERE name = 'smith' ### AND salary > 50000 ### AND department NOT LIKE 'eng%'
So am I missing something, going about it all wrong, making security holes for myself, working too hard, not working hard enough ... any thoughts?
($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss') =~y~b-v~a-z~s; print
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: How do Monks programatically construct SQL selects
by graff (Chancellor) on Sep 03, 2003 at 07:00 UTC | |
As for the code you proposed, it seems more or less headed in the right direction, but maybe you're not going far enough in the amount of flexibility you're trying to achieve. First off, you probably shouldn't be trying to handle the quotation marks around condition values in the "where" clause -- you'll have a problem when someone wants to look for a name like "O'Hara". Use "?" placeholders for the condition values, and pass the actual values as extra parameters when you execute the statement. (This will also make the process more secure.) Also, it's risky to do a "select * from table" sort of query; it can happen that a column will be added to the table someday, or some other "event" will cause the database to return the columns in a different order from what the perl script was expecting. Ask for the specific columns you want, by name -- and if you don't know the column names, there are ways to find out what they are, and you should do that. (If you're using DBI to return each matching row into a hash, keyed by field name, then this is less of a problem.) Finally, I'm not sure how much good it does you to generate the sql statement in a sub and then execute it somewhere else -- especially if you're using the "?" place-holders in the statement (as you should). It may be better for the sub to prepare and execute the statement as well. Assuming you know how the table or RDB schema is defined, and can scope out the appropriate range of "options" for selection criteria, it's not that hard to set up a GUI or web interface to support on-the-fly creation of SQL for a "select" statement; your example, using a single table and conditions that are all conjoined by "and" is the easiest case -- consider that for each field, you need to present the name of the field (so the user knows what is being controlled), a pull-down menu to choose a suitable comparison operator, and a type-in box to provide a suitable value to test for. (Presumably, you could also provide "is null" and "is not null" for each field, either as separate choices on the "operator" menus, or by accepting the string "NULL" in the type-in box, with the "=" or "!=" operator.) It gets trickier if you're hoping to support joins on two or more tables, or you want to combine "and" and "or" conjunctions in a single query (which would require putting parens in the right places as you construct the statement). Supposing that the user's specs are stored in a hash, keyed by field name, the code to create the sql statement could be something like this (which has not been tested, and would normally include some error checks at strategic points): Naturally, there are other ways to do this, and maybe the modules cited in earlier replies would make it all even easier. (Maybe you've noticed that I left out the handling of "is (not) null" conditions "as an exercise for the reader"; you could also consider offering options like an "order by" clause, etc.) update: fixed a missing "=" in the sub | [reply] [d/l] |
Re: How do Monks programatically construct SQL selects
by dws (Chancellor) on Sep 03, 2003 at 07:58 UTC | |
I don't. I tend to work in highly normalized schemas, with "interesting" queries (3-6 table joins, some non-equi-joins, some outer joins). I could spend time trying to warp a framework into generating workable queries, or I could code them by hand. So far, I've gotten there quicker by coding them by hand. By approaching the problem from use cases, building specific queries, I avoid having to solve a bunch of otherwise general-case problems, and can spend the time instead writing specific unit tests. The problem with programatically generated queries is edge cases--particularly vendor-specific ones. By the time you fight your way around one or two of them, you've eaten up more time than you would have spent hand-coding a few dozen queries.
| [reply] |
by mpeppler (Vicar) on Sep 03, 2003 at 18:30 UTC | |
How do Monks programatically construct SQL selects.Indeed. It's interesting how this particular question comes up again and again. Yes, SQL queries can be generated, but when you start using a normalized schema auto-generated queries tend to fall down. In order to get good, repeatable performance it is essential to write well behaved queries (i.e. queries where the JOIN and/or WHERE clauses use appropriate indexes) as a single badly written query can bring even very powerful hardware to its knees... This is one of the reasons why I always try to use stored procedures where I can. Michael | [reply] |
Re: How do Monks programatically construct SQL selects
by antirice (Priest) on Sep 03, 2003 at 02:26 UTC | |
Class::DBI with Class::DBI::AbstractSearch. antirice | [reply] |
Re: How do Monks programatically construct SQL selects
by dragonchild (Archbishop) on Sep 03, 2003 at 12:17 UTC | |
Now, if you're determined to re-invent the wheel, I would suggest the following decomposition: And, I'm just referring to the one whose docs I've actually glanced at. Solutions suggested by other monks, IIRC, do even more than Class::DBI. So, why do you want to re-invent the wheel again? ------ The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6 Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified | [reply] [d/l] |
Re: How do Monks programatically construct SQL selects
by nite_man (Deacon) on Sep 03, 2003 at 06:28 UTC | |
Try to look at module DBIx::SearchBuilder::Record, which implements a Perl interface for build of SQL queries, espessially this module is useful for serialize and deserialize objects to the database. Maybe it will be good for you.
_
_
_
_
_
_
| [reply] |
Re: How do Monks programatically construct SQL selects
by clscott (Friar) on Sep 03, 2003 at 12:08 UTC | |
I think you're making too much work for yourself, someone has already done it for you. This module can generate pretty complicated WHERE statements easily. For example, simple key=value pairs are taken to mean equality, and if you want to see if a field is within a set of values, you can use an arrayref. Let's say we wanted to SELECT some data based on your criteria:
This module also handles insert, update and delete, as well as ORing and ANDing your conditional clauses --Clayton | [reply] [d/l] |
Re: How do Monks programatically construct SQL selects
by simonm (Vicar) on Sep 03, 2003 at 05:10 UTC | |
| [reply] |
Re: How do Monks programatically construct SQL selects
by yosefm (Friar) on Sep 03, 2003 at 11:36 UTC | |
Yep. It's understandable that you need to construct an SQL query dynamically. I had to do the same thing when search parameters were given through an XML. But since this kind of Structured information is exactly what perl is for, there are ways to make your code do more with less. The code I use:
The way I did this, I had XML tags stating:
Of course, your construction need are probably different, so customize happily. What I really wanted to say was use map, join, reduce, and other list functions instead of loop and concatenation. As for modules, sometimes it's just quicker, and surely more fun (if you're not under a deadline) to do your own coding, and you always learn from it. Well, TIMTOWTDI... PS - If you're expecting outside input throw in extra validation and taint checking code or you'll get errors from the DB or much worse. | [reply] [d/l] [select] |
by hardburn (Abbot) on Sep 03, 2003 at 13:40 UTC | |
$db_query .= ';'; # Not mandatory, just for good order. Actualy not. I don't know what DBD you're using, but some databases don't like having a trailing semicolon when fed via DBI. It's generally better to leave it out. ---- Note: All code is untested, unless otherwise stated | [reply] |
Re: How do Monks programatically construct SQL selects
by glwtta (Hermit) on Sep 03, 2003 at 12:51 UTC | |
I use Class::DBI to store these components in a database: as Fields, Filters, Joins and Groups (purely an organizational thing). You can get some cute optimizations with this, for example if a field is selected from another table it usually is inserted as a subselected, but if a filter is added on that same table, a join (inner) is automatically included and the field uses that as well. The real advantage of this for me is that it provides methods to quickly construct html forms for these queries (coupled with Template::Toolkit a Filter "knows" how to draw itself), after which I can just give the parsed CGI.pm query object to the SQL thing and get back the complete SQL statement. Makes working with sessions trivial as well. I am not sure how many people would find this sort of thing useful, but in this case it's been working pretty well so far. | [reply] |
by dragonchild (Archbishop) on Sep 03, 2003 at 13:10 UTC | |
Actually, you're not. You're using components (like Class::DBI and Template::ToolKit) to create a custom application. The particular wheel in question is supplied, in your case, by Class::DBI. The OP was discussing how to code the functionality provided by Class::DBI, which you are intelligently using. :-) ------ The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6 Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified. | [reply] |
Re: How do Monks programatically construct SQL selects
by Roger (Parson) on Sep 05, 2003 at 05:25 UTC | |
Example:
Where the a, b, c, d can be variables or hardcoded values. This is certainly more readable than using a bunch of join's. | [reply] [d/l] |
Re: How do Monks programatically construct SQL selects
by Anonymous Monk on Sep 04, 2003 at 07:28 UTC | |
DESCRIPTION A very simple example. It instanciates the generator and creates some example output, which could be simply piped to an mysql database for testing (be careful if tables/database name is existing. I guess not !). CODE my $table = 'sql_statement_construct_generator_table'; my $database = 'sql_statement_construct_generator_db'; my %types = ( row1 => 'VARCHAR(10) AUTO_INCREMENT PRIMARY KEY', row2 => 'INTEGER', row3 => 'VARCHAR(20)' ); my %columns = ( row1 => '1', row2 => '2', row3 => '3' ); my %alias = ( row1 => 'Name', row2 => 'Age', row3 => 'SocialID' ); my $sql = new SQL::Generator( LANG => 'MYSQL', post => ";\n", history => 1, autoprint => 0, prettyprint => 0 ) or die 'constructor failed'; $sql->CREATE( DATABASE => $database ); $sql->USE( DATABASE => $database ); $sql->CREATE( COLS => \%types, TABLE => $table ); $sql->DESCRIBE( TABLE => $table ); $sql->INSERT( COLS => keys %columns , VALUES => values %columns , INTO => $table ); foreach (keys %columns) { $columns{$_}++ } $sql->INSERT( SET => \%columns , INTO => $table ); foreach (keys %columns) { $columns{$_}++ } $sql->REPLACE( COLS => keys %columns , VALUES => values %columns , INTO => $table, ); $sql->SELECT( ROWS => '*', FROM => $table ); $sql->SELECT( ROWS => keys %types , FROM => $table ); $sql->SELECT( ROWS => \%alias, FROM => $table, WHERE => 'row1 = 1 AND row3 = 3' ); $sql->DROP( TABLE => $table ); $sql->DROP( DATABASE => $database ); # evocate an errormsg print "\nDumping sql script:\n\n"; for( $sql->HISTORY() ) { printf "%s", $_; } OUTPUT CREATE DATABASE sql_statement_construct_db; USE sql_statement_construct_db; CREATE TABLE sql_statement_construct_table ( row1 VARCHAR(10) AUTO_INCREMENT PRIMARY KEY, row2 INTEGER, row3 VARCHAR(20) ); DESCRIBE sql_statement_construct_table; INSERT INTO sql_statement_construct_table ( row1, row2, row3 ) VALUES( 1, 2, 3 ); INSERT INTO sql_statement_construct_table SET row1='2', row2='3', row3='4'; REPLACE INTO sql_statement_construct_table ( row1, row2, row3 ) VALUES( 3, 4, 5 ); SELECT * FROM sql_statement_construct_table; SELECT row1, row2, row3 FROM sql_statement_construct_table; SELECT row1 AS 'Name', row2 AS 'Age', row3 AS 'SocialID' FROM sql_statement_construct_table WHERE row1 = 1 AND row3 = 3; DROP TABLE sql_statement_construct_table; DROP DATABASE sql_statement_construct_db;BE WARNED: The documentation isn't that deep. You can do a lot, but its not documented. The 'AUTOLOAD' feature lets you skip the oo-interface. BTW: Currently its very msql-centric. Good Luck, Murat | [reply] |