Looks interesting, but I'd suggest the Databases chapter rather than String Processing.
Tim. On Thu, May 23, 2002 at 05:17:35PM -0400, Phil R Lawrence wrote: > Since the PAUSE form is not responding for me, please forgive this manual > email... > > Thanks, > Phil R Lawrence > prl -AT- cpan -DOT- org > > Name of the module > SQL::Snippet > > Module List Chapter > 11) String Processing, etc. > > Development Stage (Note: No implied timescales) > b -- beta > > Support Level > d -- developer > > Language Used > p -- perl > > Interface Style > O -- object oriented > > Public license > p -- Standard Perl > > Description in Module List (44 chars limit) > Enables an OO Interface to Your RDBMS > > Places where this module has been or will be discussed publicly > clpm, YAPC::NA 2002 > > Modules with similar functionality > SQL::QueryBuilder::Simple, Relations::Query > > Rationale > OVERVIEW > > SQL::Snippet enables an OO (Object Oriented) interface to your RDBMS (Relational > Database Management System). It does this by generating correct SQL based on > your OO requests. Actual RDBMS-specific info (table names, joins, subselects, > etc.) does not appear in your invocant script. > > While other modules exist that will create SQL for you, they do so only insofar > as the invocant script passes RDBMS-specific info. SQL::Snippet is much simpler > to use. You tell it you want SQL for 'this' or 'that', and it takes care of > figuring out what tables and joins are involved. > > > APPLICATION > > Reports and other scripts that use SQL often look like someone's first effort at > web programming with Perl; only instead of being littered with HTML they are > full of SQL: SQL that is duplicated in another script, and another, and > another... So if it's a good thing to get your HTML out of your Perl scripts > and into its own repository, why not do the same with your SQL? > > SQL::Snippet will require you to create a Perl module full of, well, snippets of > SQL. For example, that snippet you use to limit a selection by gender. And > that other one you use to grab all the miscreants that have never ordered from > you even though they're still receiving your catalog. Wait, that's two > snippets: the 'catalog_recipient' population and the > 'never_ordered_a_single_thing' limit, which creates a subset of the > 'catalog_recipient' population. > > The point is that, like most things, you can break those SQL statements down > into their parts and store them in one central module, instead of in individual > scripts. Then you can use SQL::Snippet to dynamically assemble these snippets > as needed. This can even be done in an ad hoc manner; you can let the user > build whatever question he likes of the RDBMS, and SQL::Snippet will take care > of turning it into SQL. Once you have modularized your SQL into snippets, all > you're left with is some easy OO syntax. > > > PARADIGM > > There are four elements to the paradigm that SQL::Snippet uses to enable easy > interaction with your RDBMS. > 1. pop - Populations > 2. lim - Limits applicable to those populations > 3. parm - Parameters needed to flesh out the pops and lims > 4. methods - each of the above three items has various built in > methods associated with it. Additionally, objects > and methods are automatically generated from the > snippets in your Repository as needed. (See below.) > > Populations > Population objects are real world things that are represented in your RDBMS. > The following are example of objects you might find in your RDBMS: > - people > - suppliers > - parts > - assemblies > - SKUs (Stock Keeping Units) > > All of these are real world things. Information about them in your RDBMS may be > contained in one and only one table, or the information may be normalized > (split) between many different tables. In the latter case, the SQL necessary to > query your object of interest could get rather complicated, necessarily > including all of the relevant tables and joins. > > SQL::Snippet abstracts the details of your RDBMS table structure into objects > that you can simply reference in your scripts; SQL::Snippet will fill in all the > details behind the scenes. For example: > > # prints the canonical SQL statement needed to > # query info about assemblies from your RDBMS > print $snippet->pop->assemblies->query; > > Limits > Limits are types of real world attributes that can apply to the population > objects. For example, gender is a type of attribute applicable to people, but > not to assemblies. Weight is a type of attribute applicable to both people and > assemblies. By indicating a limit for a given population, you can sharpen the > definition of that population. For example: > > # apply the gender limit to our 'people' population > $snippet->pop->people->lim->new( 'gender' ); > > Note that neither we nor the user of the script has yet specified what gender to > limit the population by (Unknown, Female, etc.). We have only indicated that > the population should have a gender limit applied to it. To complete the job we > need to look at the next element of the SQL::Snippet paradigm. > > Parameters > Parameters are the actual attributes used by population limits. In the above > example we specified a gender limit, so now we should specify the gender > parameter to be used by that limit. The gender limit requires only one > parameter, aptly named 'gender'. > > # limit the people population to those with gender attribute of > # Female or Unknown > $snippet->pop->people->lim->gender->parm->gender( ['U','F'] ); > > Actually, we don't need to say: > $snippet->pop->people->lim->new( 'gender' ); > before saying: > $snippet->pop->people->lim->gender->parm->gender( ['U','F'] ); > > The gender limit is autoinstantiated in the latter example. In fact, it's all > autoinstantiated, from pop to gender. You start your script with a simple > snippet object, and the rest autoinstantiates as needed. > > Methods > In the above examples you see that pop, lim, and parm are autoinstantiated > objects furnished by SQL::Snippet. There are multiple methods associated with > each one. For example, we called the 'new' method on lim. But note that most > of the methods and objects we used were actually named by us, the *users* of > SQL::Snippet, not by me, the guy who wrote SQL::Snippet. For example, not only > did we use the autoinstantiated 'gender' object, we also called the 'gender' > method on parm (see the last example above). Note that we, the users of > SQL::Snippet did not have to actually code a 'gender' method somewhere -- far > from it. This method was AUTOLOADed for us. All we had to do was create a > repository with pop, lim, and parm snippets, and the rest was automatic. For > example, here is the 'gender' snippet from SQL::Snippet::ExampleRepository, an > example repository included with the distribution: > > if ($parm eq 'gender') { > return ( > name => $parm, > default => '', > msg => "\nBy default, gender will bear no impact " . > "on selection. Press ENTER to accept " . > "this default, or, to limit the selection " . > "by gender, type one or more gender codes " . > "from the following list: M (Male), " . > "F (Female), U (Unknown)", > delimiter => ',', > prompt => 'Gender', > list_check => [ 'M','F','U' ], > ); > } elsif ($parm eq ... > > When you reference 'gender' in your code, either as an object or method (as in > the above examples), SQL::Snippet will automatically create the needed object or > method using this snippet as a source of basic information. If you can follow > this easy pattern for creating snippets, you can use SQL::Snippet. SQL::Snippet > itself automatically creates the back end stuff needed to allow the use of > intuitive OO syntax. > > > HOW I USE IT > > I use SQL::Snippet in a production system with seven basic populations that my > clients like to report on. I then have 31 different limits. Multiple limits > can, of course, be combined with a population. Not every limit is compatible > with all base populations, but most are, leaving the client with more possible > questions they can ask of the RDBMS than I have statistical chops to calculate. > :-) > > To make it all work I have 47 different parameters. Some limits only require > one parameter (i.e. the gender limit only needs one parameter -- gender), but > some require more than one. Also, many lim snippets need access to extra parms > in order to correctly tie into the population. For example, if the gender > information in the database is stored in table not included in the chosen pop > snippet, the gender lim snippet will also need something like 'base_table' and > 'linking_field' parms to correctly link itself in to the base population. > > Most of the populations are actually pre-limited (i.e. the populations don't > refer to so basic an object as 'people', but rather to different kinds of > people). Thus most require more than one parameter and tend to have multiple > tables included in their definition. > > My clients love their ability to ask anything they can think of from the RDBMS; > I love the ability to maintain the SQL snippets in one and only one place. > > > EXAMPLE SCRIPT 1 > > # specify the repository to be used. The repository is > # subclassed from SQL::Snippet. > use SQL::Snippet::ExampleRepository; > > # If you don't specify the parm values needed to fill out > # your pops and lims, the user will be prompted automatically. > # I use Term::Interact to enable this in interaction in a > # command line environment. > use Term::Interact; > > # We'll use DBI to execute the SQL we get from SQL::Snippet. > # Also, SQL::Snippet may use DBI to validate user input (if > # you so specify in the repository) and to quote parameters. > use DBI; > > my $ti = Term::Interact->new; > my $dbh = DBI->connect( ... ); > my $snippet = SQL::Snippet::ExampleRepository->new( > dbh => $dbh, > ui => $ti, > syntax => 'oracle' > ); > > > ###EXAMPLE 1### > > # We need to specify what our SELECT statement will be. > # A future version will allow prompting of the user to > # build his own SELECT based on meta-data supplied by > # the snippets. > $snippet->pop->catalog_recipient->select( 'SELECT count(*)' ); > > # get the SQL necessary to pull a count of the catalog > # recipient population out of your RDBMS. > print $snippet->pop->catalog_recipient->query; > > > ###EXAMPLE 2### > > # instead of grabbing the whole population of catalog > # recipients, let's limit it to those who never placed > # an order. > $snippet->pop->catalog_recipient->lim->last_order->parm->last_order( > 'null' > ); > > # our SELECT is still set for this pop from above... > print $snippet->pop->catalog_recipient->query; > > > ###EXAMPLE 3### > > # instead of forcing a last_order limit, let's let the user > # say whether or not he wants the limit, and if so what sort > # of limit. Possible inputs by the user might be: > # '> 1/1/2001' # with orders after 1/1/2001 > # -or- > # 'null' # with no orders ever > # -or- > # '' # let's not limit, thank you > > # set the limit for this pop > $snippet->pop->catalog_recipient->lim->new( 'last_order' ); > > # when generating the SQL, SQL::Snippet will notice we have > # requested a 'last_order' limit, but no parm value for > # 'last_order' has been set. The user will be prompted with > # whatever verbiage we have stored in the repository, and > # their input will be parsed by whatever logic we have in the > # repository. (See perldoc Term::Interact for the details > # of user prompting, and the source for SQL::Snippet:: > # ExampleRepository for boilerplate and example Repository > # logic. > print $snippet->pop->catalog_recipient->query; > > > EXAMPLE SCRIPT 2 > > # this example script uses some more advanced functionality. > # see perldoc SQL::Snippet for full documentation. > > use SQL::Snippet::ExampleRepository; > use Term::Interact; > use DBI; > > my $ti = Term::Interact->new; > my $dbh = DBI->connect( ... ); > my $snippet = SQL::Snippet::ExampleRepository->new( > dbh => $dbh, > ui => $ti, > syntax => 'oracle' > ); > > # all pops referenced from the current snippet will > # automatically have the zip limit applied. > $snippet->shared_lim->new( 'zip' ); > > # Since all pops will share the zip lim, let's set > # the zip parm value *once* the top level (instead > # of once for each pop at the pop->lim->parm level). > # Here, the value will be set via user interaction > # since we have passed in no value and none has > # been previously set. > $snippet->parm->zip->value; > > $snippet->pop->pre_sale->select( 'SELECT SKU, count(SKU)' ); > $snippet->pop->pre_sale->group_by( 'SKU' ); > $snippet->pop->pre_sale->order_by( 'count(SKU)' ); > > $snippet->pop->sale->select( 'SELECT SKU, count(SKU)' ); > $snippet->pop->sale->group_by( 'SKU' ); > $snippet->pop->sale->order_by( 'count(SKU)' ); > > $snippet->pop->re_sale->select( 'SELECT SKU, count(SKU)' ); > $snippet->pop->re_sale->group_by( 'SKU' ); > $snippet->pop->re_sale->order_by( 'count(SKU)' ); > > my $pre_sale_hits_by_SKU = $dbh->selectall_arrayref( > $snippet->pop->pre_sale->query > ); > my $sales_by_SKU = $dbh->selectall_arrayref( > $snippet->pop->sale->query > ); > my $re_sales_by_SKU = $dbh->selectall_arrayref( > $snippet->pop->re_sale->query > ); > > print " ----SKU---- --COUNT--\n"; > print "Pre-Sales Hits\n"; > for (@$pre_sale_hits_by_SKU) { > print " $_->[0] $_->[1]\n"; > } > print "Sales\n"; > for (@$sales_by_SKU) { > print " $_->[0] $_->[1]\n"; > } > print " Re-Sales\n"; > for (@$re_sales_by_SKU) { > print " $_->[0] $_->[1]\n"; > } > > # print any notes the repository has associated with > # limits placed on the whole report. > print scalar($snippet->get_shared_lim_notes); > >