Tim Bunce wrote:
> Looks interesting, but I'd suggest the Databases chapter rather than String
> Processing.
Has someone created the PAUSE namespace entry needed for me to go ahead and
upload the module? (It will be ready soon -- before YAPC at any rate.) Again
I apologize the namespace form wasn't working for my original request. I can
try that form again if it makes your job easier...
:-)
Phil
> 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);