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);
> 
> 

Reply via email to