Está en la página 1de 20

3/2/2016

SQL|TheBastardsBookofRuby

12/03/2011:Thisbookisintheverypreliminarystages.Noneofthecontentisguaranteedtobethoroughoraccurate.SeetheAboutPage.
TheBastardsBookofRuby
AProgrammingPrimerforCountingandOtherUnconventionalTasks
Home
About
Contents
Resources
Blog
Contact
Supplementals

SQL
Anintroductiontorelationaldatabasesandtheirquerylanguages

IKEAinRedHook.PhotobyDanNguyen
Chapteroutline
Datacrunchingwithoutdatabases
GettingstartedwithSQLite
SQLsyntax
BeyondSELECT
UsingSQLite3withRuby
PreviousChapter:TheMechanizeGem
NextChapter:UsingNonRubyProgramsWithRuby
SQL,shortforStructuredQueryLanguageisaprogramminglanguageforqueryingandmanagingdatabases.Ithasitsownsyntaxanddifferentdatabase
systemsincludingMicrosoftAccess,MySQL,PostgreSQL,andSQLiteallhavetheirownvariationsofSQL.
IintroduceSQLtoacquaintyouwithdatabasesingeneral,astheyareessentialtoanyseriouskindofdatacrunching/storingwebapplication.Don'texpect
thistobeanexhaustivereferenceofSQL.Butyou'llseehowtheRubylanguagecanbeusedtoworkwithacompletelydifferentpieceofsoftware.
Thissectionisentirelyoptional.ButIwillbeusingdatabasesformostofthedataheavyprojectsthatIplantocoverinthisbook.Iprovideanarchetypical
usecasewithCalifornia'sCommmonSurgeriesdatabase.
Fairwarning:Evenafterafewyearsofdabblingwithit,SQLsyntaxisstillbefuddlingtome.Anditmightbethesameforyou,too.However,thepower
(andubiquity)ofSQLdatabaseshassofarsurpassedmyclumsinessinusingit.I'vefoundthatthebestwaytolearnistothinkofthekindsofqueriesand
aggregationsthatyoumightbeabletodoinExceleasily.AndthenlookuphowotherprogrammershavewrittenthemupinSQL.
ThebestresourceIhavefoundonthewebforSQLexamples,byfar,isfromArtfulSoftwareDevelopment,authorsof"GetItDoneWithMySQL".Artful
Softwarehasgenerouslycreatedanonlineversionofachapterfocusingoncommonqueries:theresultisareadable,giganticlistofvirtuallyeveryqueryI
http://ruby.bastardsbook.com/chapters/sql/

1/20

3/2/2016

SQL|TheBastardsBookofRuby

havesofarimagined.

Datacrunchingwithoutdatabases
Let'stakeaminutetostepbackandseehowdataprogrammingmightworkwithouttheuseofadatabase.Includedamongthelistofthisbook'sprojectsisa
stockmarketanalysiswhichreadsfromacommadelimitedtextfilethatcontainsthedateandtheDowJonesindexforthatday.
YoucaneasilycreateaHashthatholdsthevalues,likeso:
{:company=>"AcmeInc.",:ticker_symbol=>"ACME",
:state=>"CA",:category=>"widgets",:date=>"8/1/2010",
:closing_price=>"12.00"}

Butthingsgetunwieldywhendealingwithdatawithmultipleentriesperentity:
[
{:company=>"AcmeInc.",:ticker_symbol=>"ACME",
:state=>"CA",:category=>"widgets",:date=>"8/3/2010",
:closing_price=>"16.00"},
{:company=>"AcmeInc.",:ticker_symbol=>"ACME",
:state=>"CA",:category=>"widgets",:date=>"8/2/2010",
:closing_price=>"14.00"},
{:company=>"AcmeInc.",:ticker_symbol=>"ACME",
:state=>"CA",:category=>"widgets",:date=>"8/1/2010",
:closing_price=>"12.00"}
]

Suchdatacanbeexpressedasamanytoonerelationship,inwhichtherearemanyclosingpricerecordspercompany:
[{
:company=>"AcmeInc.",:ticker_symbol=>"ACME",
:state=>"CA",:category=>"Widgets",
:closing_price_records=>
[{:date=>"8/3/2010",:closing_price=>"16.00"},
{:date=>"8/2/2010",:closing_price=>"14.00"},
{:date=>"8/1/2010",:closing_price=>"12.00"}]
},
{
:company=>"AppleSoft",:ticker_symbol=>"APLSFT",
:state=>"TN",:category=>"Computers",
:closing_price_records=>
[{:date=>"8/3/2010",:closing_price=>"16.00"},
{:date=>"8/2/2010",:closing_price=>"14.00"},
{:date=>"8/1/2010",:closing_price=>"12.00"}]
}]

Thisdatastructureanarrayofhashes,eachofwhichcontainscompanyinformation,includinganarrayofhashesofstockpricesforthatcompanyisnot
terriblydifficulttoworkwithforsimplequeries.Forinstance,tofindallinstancesinwhich"Acme,Inc."hadaclosingpriceoflessthan$10,inthe
timeframeof2009to2010,youmighttry:
data.select{|c|c[:company]=="AcmeInc."}[0][:closing_price_records].select{|r|
r[:date]>="2009"&&r[:date]<="2010"&&r[:closing_price]<10
}

ThisquerywouldobviouslyreturnfunkyresultsbecauseIwroteouttheexamplesusingdatesandamountsasstringsinsteadofusingtheDateandnumber
classes.Butit'sjustpseudocode,yougetthepicture...
SomecodersmaybemorecomfortabletranslatingtextfilesintoRubydatastructures.Buttherealityisinthedataworldisthatyou'llencountermany
datasetsthatareeitherinaSQLformatoraremoreeasilystoredassuch.Databasesystemsarealsodesignedtohandlemassivedatasetsandtooptimize
complicatedqueries.
IlearnedsomeSQLbeforeIknewRubyjustbecauseIhadtoformywork,andsoit'sahabitformetomixbothlanguagesinmyscripts.ButIreallycannot
arguethatmyworkflowisabestpracticeorthatit'sworthyourtimetojuggletwodifferentlanguages.ButIdobelievethatSQLandotherdatabase
languagesaswellasdatabasedesigningeneralissoubiquitousthatyouneedtopickuptheknowledgeatsomepoint.
IonlyofferthischapterasaquickstartupguideanddemonstrationofSQLinthecontextofRubyprograms.Thankfully,basicSQLqueriesareprettyeasyto
figureoutforourpurposes.
TheprojectexaminingthecostofcommonsurgeriesinCaliforniahasasectioncomparingthecodeforexecutingSQLqueriesversusiteratingthrougha
Rubycollection.
Returntochapteroutline

GettingstartedwithSQLite
Forthefirstpartofthischapter,therewon'tbeanyRubycoding.I'llwalkyouthroughhowtosetupSQLiteonyoursystem,howtoworkwithinits
environment,andthenintroduceaseriesofexamplequeries.
Databasesystems,likeanyothersoftware,canbeabeartoinstall.IchooseSQLitebecauseitiseasytoinstallandrequirescomparativelylittleoverhead.
http://ruby.bastardsbook.com/chapters/sql/

2/20

3/2/2016

SQL|TheBastardsBookofRuby

InstallationofSQlite

MacOSXLeopardandafteralreadyhaveitpreinstalled.Windowsusersmayhavetofollowthesesteps:
1.GototheSQLitedownloadpageandlookforthePrecompiledBinariesForWindowssection.
2.Downloadtwozipfiles:onefortheshellandonefortheDLL
3.UnziptheminC:\Windows\System32.Youmayalso/insteadwanttohavethesefilesinC:\ruby\bin(ifyouusedthedefaultsettingsforinstallingRuby)
Again,myknowledgeofsoftwareinstallationismostlyinformedbyGoogleandStackOverflow.Iftheseinstructionsdon'twork,youmayhavetodosome
searching.
RunningSQLite

IfSQLiteiscorrectlyinstalled,youshouldbeabletogotoyourcommandlineandtype:sqlite3
Youshouldseesomethinglikethis:
>>sqlite3test.db
SQLiteversion3.6.12
Enter".help"forinstructions
sqlite>

ThisistheSQLiteinteractiveshell.YoucantypeinSQLitecommandsjustasyoudoinRuby'sirb.
There'soneimportantandimmediatesyntaxdistinction.InRuby,alineofcode(aslongasyouaren'tinthemiddleofastring,block,orparenthized
expression)willexecutewhenyouhitEnter.InSQL,youneedtoendeachstatementwithasemicolon;
Here'sa"Helloworld"statementtoseeifeverythingisworking:
sqlite>select"helloworld";
helloworld

UsingFirefoxasaSQLiteinterface
Ifyouhaven'talready,IrecommendinstallingtheFirefoxbrowser.Notonlyisitagoodbrowser,butithasmanypluginsthatareuseful(andfree)toolsfor
everyprogrammer.
FirefoxusesSQLitetostorebrowsinghistorysotheuser"lazierthanthou"hascreatedapluginSQLiteManagerthatservesasaconvenientgraphicaluser
interfaceforexploringSQLanddatabasefiles.
Forthescreenshotsinthisexample,IwillbeusingSQLiteManager.Youcandownloadithere.

TheresultsfromanexamplequeryintheSQLiteManagerpluginforFirefox.
Asampledatabaseofstockprices

Forthepurposesofthischapter,I'vecreatedadatabaseoftheS&P500companiesandtheirhistoricalstockprices,usingthelistingatWikipediaandthe
YahooAPI.
Downloadithere:sp500historicalstockprices.zip
http://ruby.bastardsbook.com/chapters/sql/

3/20

3/2/2016

SQL|TheBastardsBookofRuby

Unzipthefileinadirectory.Thenameofthefileshouldbe:sp500data.sqlite
Afteryou'vedownloadedSQLiteManager,openuptheFirefoxbrowserandgototheToolssubmenu:

ClickontheSQLiteManageritem.Thisshouldpopupanewprogramwindowthatlookslikethis:

ClickontheOpenicon(circledinred)andselectthesp500data.sqlitefile.
IntheleftsideoftheSQLiteManagerwindowshouldbealistofsubmenusrelatingtothesp500data.sqlitedatabasefile.SelectTablesandthen
`companies`.
Thenintherowofnoniconbuttonsnearthetop,selectBrowse&Search:

http://ruby.bastardsbook.com/chapters/sql/

4/20

3/2/2016

SQL|TheBastardsBookofRuby

Inthisview,youhaveaspreadsheetlikeviewofeachdatatable.Thisisniceforremindingyourselfwhatthetablelookslike.Butyoutypicallywon'tbe
usingthisviewtosearchfordata.
Writeyourfirstquery

Inthetoprowoftextbuttons,clickExecuteSQL.ThisiswhereyouwriterawSQLqueries.

Let'swriteaquerytolisttechcompaniesandorderthembystate.InthetextboxlabeledEnterSQL,typethefollowingandclick:RunSQL
SELECT*FROMcompaniesWHEREsector="InformationTechnology"ORDERBYstate,name

Aspreadsheetlikelistingshouldappearatthebottom.ItwilllooksimilartotheBrowse&Searchview.Thedifferenceisthatourcustomquery
generatedthislisting.
ForthenextsectiononSQLsyntax,thisiswherewewillbepracticingourqueries.

http://ruby.bastardsbook.com/chapters/sql/

5/20

3/2/2016

SQL|TheBastardsBookofRuby

Bonus:Rightclickingonqueryresultswillrevealseveraloptionsforcopyingtoyoursystem'sclipboard,includingCopyRow(s)asCSV(MSExcel
Compatible).
Returntochapteroutline

SQLsyntax
ThebestwaytoapproachSQLlanguageistothinkofthatSinitsname:Structured
Earlyon,you'regoingtogetalotoferrormessagesasyoutrytofigureouttheproperorderofaSQLstatement,butatleastthere'sa(usually)consistent
patterntomemorize.
We'llstartwiththesimplestbuildingblockofastatementandgraduallyaddontoit.
YoucanpracticethisnextsectioninanySQLenvironmentyoulike.Again,I'llbeusingFirefox'sSQLiteplugin,butanythingthatallowsyoutoopenupa
SQLitedatabaseandtypeinSQLitecommandswillsuffice.
WewillnotbewritinganyRubycodeduringthisexplorationoftheSQLbasics.

TheSELECT
MostoftheSQLstatementsinvolveselectingrecordsfromthedatabase.TheSQLsyntaxforthatis:SELECT.
Besidesretrievingrowsfromthedatabase,theSELECTcanbeaskedtoselectliteralvalues,suchasnumbersandstrings.
SELECTtakesalistofvaluesornamesoftablesandcolumns,separatedbycommas.

Thefollowingwillreturnonerecordwithtwocolumns,oneforeachstring:
SELECT"Helloworld","Andthisisanotherstring";

Acoupleofsyntaxthings:
Capitalizationusuallydoesn'tmatter.However,IwilltrytoputSQLspecificsyntaxinalluppercaseforeasierreading.
Endeachcommandwithasemicolon.

TheFROM
InordertoSELECTrecordsfromourdatabase,wehavetospecifythenameofthetargettable.ThisisdonewithFROM.
Toselectallcolumnsfromatable,weSELECTtheasteriskoperator*
SELECT*FROMcompanies_and_stocks

Fromoursampledatabase,weshouldgetabout30,000*rowswithallthecolumnsfromcompanies_and_stocks.WiththeFirefoxplugin,itshouldlooklike
this:
http://ruby.bastardsbook.com/chapters/sql/

6/20

3/2/2016

SQL|TheBastardsBookofRuby

*inthecompanies_and_stockstable,onlyrecordsinthemostrecentcoupleofmonthsareincluded.Theothertableshavethefullsetofdata.
Wecanalsoselectspecificcolumnsofatable:
SELECTcompanies_and_stocks.name,companies_and_stocks.date,
companies_and_stocks.closing_priceFROMcompanies_and_stocks

Thedotoperatordenotesthatname,dateandclosing_pricebelongtocompanies_and_stocks.Specifyingthetablenameisoptionalunlessmorethanone
tableinyourdatabasehasacolumnwiththegivenname.
Andyoudon'thavetoselectonlycolumns.YoucanSELECTcalculationsbasedonvaluesinacolumn.Thefollowingcommandselectsseveralcolumnsanda
columnconsistingoftheclosing_pricevaluesmultipliedby10.Thevaluesinnameandticker_symbolareconvertedtouppercaseandlowercasecharacters,
respectively:
SELECTUPPER(name),
LOWER(ticker_symbol),
10*closing_price
FROMcompanies_and_stocks;

NotethatwhitespaceisnotsignificantinMySQL,justlikeinRuby.Youcanuselinebreaksandtabstomakeaquerymorereadable.

TheWHERE
Gettingbackeveryrowfromadatatableisn'talwaysnecessaryoruseful.Sotofiltertheresults,weusetheWHEREkeywordtospecifyconditions,suchas:
"returntherowswheretheamountisgreaterthan42".
Comparisonandlogicaloperators

Thecomparisonoperators<,>,<=,!=andsofortharethesameasinRuby.However,theequalityoperatorusesjustasingle=
Logicaloperatorsarewrittenout:ANDinsteadof&&,ORinsteadof||.
Operation
Equalto

Notequalto

...and...

Ruby
=

putstrueif4==2+2

[...]WHEREamount=2

!=

!=

putsfalseif5!=2+2

[...]WHEREamount!=2

&&

AND

putstrueif2+2==4&&1+1==2 [...]WHEREamount>2ANDamount>5
||

...or...

SQL

==

OR

putsfalseif2+2==5||1+1==3 [...]WHEREamount=2ORamount=5

Thefollowingcodewouldfetchallrowswhereclosing_priceisbetween100and200:
SELECT*FROMcompanies_and_stocksWHEREclosing_price>100ANDclosing_price<200;

TheWHEREconditionscangetascomplexasyouwant,includingtheuseofaseriesoflogicaloperatorsandparenthesestospecifyorderofcomparisons:
SELECT*FROMcompanies_and_stocksWHERE

http://ruby.bastardsbook.com/chapters/sql/

7/20

3/2/2016

SQL|TheBastardsBookofRuby

(closing_price>100ANDclosing_price<200)
OR(sector='Financials'ANDclosing_price>200);

SELECT*FROMcompanies_and_stocks
WHEREclosing_price>100AND
closing_price<200ORsector='Financials'ANDclosing_price>200;

Thedifferencebetweentheabovequeries(rememberthatwhitespacedoesn'tmatter)isthatthefirstquerywillfetchrowswhereclosing_priceisbetween
100and200ORifsectorhasastringvalueequalto'Financials'whilehavingaclosing_pricevaluegreaterthan200.
Thesecondquery,withouttheparentheses,willreturnnothing,becausethefinalAND'edconditionclosing_price>200willneverbetrueifthefirsttwo
conditionsclosing_priceismorethan100andlessthan200isalsotrue.
Usingstrings

AsinRuby,SQLtreatsstringsdifferentlythannumbers.Stringsmustbeenclosedinsingleordoublequotemarks:
SELECT*FROMcompanies_and_stocksWHEREticker_symbol="TXN"

Inthesampledatabase,allthedataisfreeofdoublequotemarks.Sousethosetoavoiderroneouslyquotedqueriessuchas:
SELECT*FROMcompanies_and_stocksWHEREname='John'sApples'
Howtogethacked

Thefollowingisjustasegueondatabasesecurityanddoesn'tapplytowhatwe'reworkingonnow.
RememberinRubyhownotproperlyclosingstringsoffwithquotemarksledtosomeannoyingerrors?InSQLLand,sucherrorsallowforthemosteasily
performedyetcatastrophictechniqueforhackingwebsitesanddatabases.
Thehackoccursanytimeawebsiteorprogramtakesininputfromtheuserwithoutsanitizingit.Imaginethatawebsiteallowstheusertoenterstock
symbols.Thewebsitethenconnectstothedatabasewiththefollowingqueryandinsertstheuser'sinputinbetweenthequotemarks:
SELECT*FROMcompanies_and_stocksWHEREticker_symbol=""

Butinsteadofenteringastocksymbol,theusertypesquotationmarksandsomeevilSQLcode.Belowistheoriginalquerywiththeuser'smalicious
inputhighlightedinred:
SELECT*fromcompanies_and_stocksWHEREticker_symbol="whatev";
DROPTABLEcompanies_and_stocks;SELECT"Haha!"

(Don'truntheabovescriptunlessyouwanttoredownloadthesampledatabase)
ThisiscalledSQLinjectionandiseasilyprevented.Inthiscase,thesanitizinginvolvesinsertingescapingbackslashesinfrontofeveryquotationmarkfrom
theuser:
SELECT*fromcompanies_and_stocksWHEREticker_symbol="whatev\";
DROPTABLEcompanies_and_stocks;SELECT\"Haha!"

Thequeryendsupbeingaharmless(andfruitless)searchforaticker_symbolequaltowhatev\";DROPTABLE...
Mostscriptinglanguageshavelibrariesthathandlethiseasily.AndyetSQLinjectionscontinuetoresultinspectacularhackingattacks.
Justsomethingtoputonyourchecklistbeforeyoustartbuildingyourfirstdatabasebackedwebsite...However,theSQLite3gemhasaconvenientmethodfor
handlingtheproperquotingofinput,whichIcoverinthesectionaboutplaceholders.

FuzzymatchingwithLIKE
TheLIKEkeywordallowsyoutodopartialmatchingoftextvalues.Bydefault,itmatchesvalueswithoutregardtocapitalization.Soinsteadof:
SELECT*FROMcompanies_and_stocksWHEREname='V.F.Corp.'ORname='V.F.CORP.';

TheuseofLIKEwillcaptureallvariationsofcapitalization:
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'V.F.CORP.';

Thepercentagesign%,whenusedaspartofastringvalue,servesasawildcard.Thefollowingthreestatementsselectrowsinwhichnamestartswith'Ba'
(again,caseinsensitive),endswith'co',orcontainsthecharacters'oo'somewhere:
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'Ba%';
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'%co';
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'%oo%';

http://ruby.bastardsbook.com/chapters/sql/

8/20

3/2/2016

SQL|TheBastardsBookofRuby

ORDERBY
TheresultsetcanbesortedwiththeORDERBYsyntax,followedbyacolumnname:
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'Ba%'ORDERBYclosing_price;

Youcansortbymorethanonecolumn.Thefirstlistedcolumnwillhavethemostpriority:
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'Ba%'ORDERBYstate,closing_price;

ThesortordercanbespecifiedwiththeASCascendingorderisthedefaultandDESCkeywords:
SELECT*FROMcompanies_and_stocksWHEREnameLIKE'Ba%'ORDERBYstateASC,closing_priceDESC;

LIMIT
ThisisoftenthelastkeywordinaSELECTstatement.Itlimitsthenumberofrowsreturned,whichisusefulincaseswhereyoumightjustwantthefirstrow
(atopasortedlist)fromaquery:
SELECT*FROMcompanies_and_stocks
WHEREnameLIKE'Ba%'
ORDERBYstateASC,closing_priceDESC
LIMIT1;

AggregationswithAVGandCOUNT
Asidefromfetchingrowsofrawdata,aquerycancalculateaggregationsuponcolumns.Forexample,here'showtocounthowmanyrowswherethe
companynamebeginswith"A":
SELECTCOUNT(1)FROMcompanies_and_stocks
WHEREticker_symbolLIKE"A%"

TheCOUNTfunctiontakesinoneargument.Inthiscase,Isimplysupplyitwiththevalue1toindicatethatIjustwantaplaincountofrowsreturned.
Tofindanaverageofafield,useAVG(),whichtakesinoneargument,suchasacolumnofnumbers.
Ifyoupassinacolumnname,theAVG()functionwillcalculatethesumofthevaluesinthatcolumndividedbythenumberofrowsreturned:
SELECTAVG(closing_price)FROMcompanies_and_stocks
WHEREticker_symbolLIKE"A%"

UsingGROUPtoaggregatebyclusters
TheGROUPBYclause,followedbyalistofcolumnnamesseparatedbycommas,willcollapserowswithmatchingvalues.Thefollowingquerywillgroupby
thecompanyname,whichwillreturnonerowperuniquecompanyname:
SELECT*fromcompanies_and_stocksGROUPBYticker_symbol;

AstheS&500has500companies,weexpectthenumberofrowsreturnedtobe500.
CombiningGROUPBYwithanaggregatefunctionwillreturnanaggregationdoneonthatcluster.Toreturntheaverageclosingstockpricebycompany,we
modifytheabovequeryandtheresultwillagainhaveonerowpercompany:
SELECTname,ticker_symbol,avg(closing_price)FROMcompanies_and_stocksGROUPBYticker_symbol;

Exercise:UseGROUPBYandMAXtoreturnmaximumvalues

Returnaresultsetthatcontainsthehighestclosingpricepersector.UsetheMAXaggregatefunction,whichreturnsthemaximumvaluewithinacolumn.
Solution

SELECTname,sector,MAX(closing_price)FROMcompanies_and_stocksGROUPBYsector

Aliases,withASandHAVING
http://ruby.bastardsbook.com/chapters/sql/

9/20

3/2/2016

SQL|TheBastardsBookofRuby

Tomodifythenameusedtorefertoacolumnortable,simplyadd"ASsome_name_for_the_alias"forthegivencolumnortablename:
SELECTname,AVG(closing_price)ASavg_cFROMcompanies_and_stocksGROUPBYname;

Sometimesaliasesareusedasabbreviationsincaseswhereaqueryreferstoatableorcolumnmultipletimes(whichwehaven'thadtodowhileworkingwith
ouronetable).Inothercases,aliasesarerequired,particularlywhennamingtheresultofafunction.
Intheabovequery,IaliasAVG(closing_price)asavg_c.NowIcanrefertoavg_cwhenwritingaconditionalstatement,suchas"findallcompaniesinwhich
theaverageclosingprice(i.e.avg_c)isgreaterthan10.0"
HAVINGandWHERE

However,thisconditionalstatementcannotbeputaftertheWHEREclause.TheSQLenginehasnotresolvedthevalueofAVG(closing_price)bythetimeit
getstotheWHEREclause.SowehavetousetheHAVINGkeyword,whichcomesafteranyWHERE(andJOINstatements,whichIcoverinthenextsection):
SELECTname,AVG(closing_price)ASavg_cFROMcompanies_and_stocksGROUPBYnameHAVINGavg_c>50.0;

Joins
OneofthemainreasonstousearelationaldatabaselikeSQLiteistheabilitytoorganizedatainanormalizedway.Inourexampledataset,wehave500
companies,eachwithoneormorestockpricerecord.Inaflattextfile,youwouldhavetorepeatthecompany'sinformationwitheverystockpricerecord:
nameticker_symbolsectorcitystatedateopenhighlowclosevolumeclosing_price
AgilentTechnologiesIncAInformationTechnologySantaClaraCalifornia2010011330.4730.7830.0530.69244560030.69
AgilentTechnologiesIncAInformationTechnologySantaClaraCalifornia2010010830.6430.8530.4030.80267090030.8
AgilentTechnologiesIncAInformationTechnologySantaClaraCalifornia2010010531.2131.2230.7630.96299430030.96
AlcoaIncAAMaterialsPittsburghPennsylvania2011092310.0310.319.9510.073974970010.07
AlcoaIncAAMaterialsPittsburghPennsylvania2011092011.5711.6311.2211.252288740011.25
AlcoaIncAAMaterialsPittsburghPennsylvania2011091511.9112.0111.7811.981988610011.98

Thefirstfivecolumnsarerepeatedforeveryrecord,eventhoughtheonlydifferencesbetweenrowsareinthedateandstockvalues.That'salotofredundant
information.Butwasteddiskspaceisn'teventhemainconcernhere(mostlybecausediskspaceissocheap)thebiggerheadacheisdataintegrity.
Let'spretendthatGooglechangesitstickersymbolfromGOOGtoGGLE.Everyrowintheflattextfilemustreflectthatchangetothetickersymbol
column.Notonlyarethedatavaluesrepeated,butsoaretheoperationsneededtomaintainthatdata.
Inamorecomplicateddatastructure,underrealworldconditions,youmayrunintocaseswhereallthenecessary(butredundant)updateoperationsweren't
completed,causingyourdatatofalloutofsync.
Manytoonerelationship

Withadatabase,wecanreducethisredundancybycreatingtwotables:oneforthecompanyinformationandoneforthecompanystockperformance.The
relationshipbetweencompanyandstockpricerecordsisamanytoonerelationship:Onecompanyhasmanystockpricerecords.

Uniquekeys

Inthestock_pricestable,howdoweknowwhichrecordsbelongtowhichcompany?Weneedacolumninstock_pricesthatreferstothecompanyofeach
record.Inotherwords,somekindofidentifyingvalueuniquetoeachcompanythatcanbematchedagainstacolumnincompanies.name.
Anobviousidentifierwouldbethevalueincompanies.nameorincompanies.ticker_symbol,assumingnotwocompaniessharethesamevalue:

http://ruby.bastardsbook.com/chapters/sql/

10/20

3/2/2016

SQL|TheBastardsBookofRuby

However,thisresultsintheproblemofhavingcompanies.namebeingrepeatedlyneedlesslyinsideofthestock_pricestable.Thiscanbeavoidedbycreatinga
newcolumntypicallycalled'id'forthecompaniestableandacorrespondingstock_prices.company_idcolumn.

Again,thinkoftherealworldimplications.Ifweusecompanies.nameasauniqueidentifier,wehavetochangethatvalueaswellasallthecorresponding
stock_prices.company_namevalues.Butbygivingeachcompanyanarbitraryidvaluewhichcanbeasimpleintegertheonlythingeachrowin
stock_priceshastoworryaboutisthatithastherightidvalue,which(ideally)shouldneverchange,nomatterwhatidentitycrisisacompanymayundergo.
TheJOINandONsyntax

OK,backtoSQLcode.Nowthatwe'vesplitourdataintotwotables,weneedtouseaJOINkeywordtoreturnrowsthatcontainfieldsfrombothtables.The
followingreturnsallrowswiththecompanyandstockpriceinfo(thus,prettymuchwhatwecoulddowithSELECT*fromcompanies_and_stockspreviously):
SELECTcompanies.*,stock_prices.*
FROMcompanies
INNERJOINstock_prices
ONstock_prices.company_id=companies.id
WHEREstock_prices.closing_price>100

http://ruby.bastardsbook.com/chapters/sql/

11/20

3/2/2016

SQL|TheBastardsBookofRuby

Let'sbreakthesyntaxdown:
SELECTcompanies.*,stock_prices.*
Becausewewanteverycolumnfromeachtable,wehavetoexplicitlyrefertoeachtableintheSELECTkeyword.
FROMcompanies
ThisisslightlyconfusingbecauseintheSELECT,wereferredtoanothertable,stock_prices.However,theSELECTonlyspecifieswhatcolumnswewant
toreturn,regardlessifitsfromtheFROMspecifiedtableornot.Specifyingcompanieshereonlydefinesastartingsetofdatatojoinothertablesonto.
INNERJOINstock_prices
Thereareotherkindsofjoins,butINNERJOINisoneofthemorecommonlyused,andisusefulforourpurpose.Theinnerjoinwillreturnonerecord
foreverycombinationofrowintheleftsideandrowintherightside.So,anINNERJOINwithoutanykindofconditionaloperators(usedinON)will
returnthenumberofrowsequaltothenumberofrowsincompaniesmultipliedbythenumberofrowsinstock_prices.
ONstock_prices.company_id=companies.id
YoucantryrunningthequerywithouttheONclause,butitwillprobablycrashyourprogram.
Theresultisamassivetable(again,numberofcompaniesrows*numberofstock_pricesrow)ofnonsensicaldata,becausetheINNERJOINreturns
everycombinationofcompanyandstockpricerecord,whethertherecordbelongstothecompanyornot.
Infact,runningthisquery:SELECTCOUNT(1)FROMcompaniesINNERJOINstock_priceswillresultin228,389,500records(theproductof500
companiestimes450,000+stockpricerecords)
TheONkeywordisusedtospecifyaconditionforthosematches.Wedon'twanteverycombinationofcompanyandstockprice.Weonlywantthe
combinationsinwhichstock_prices.company_id=companies.id.AswithWHERE,youcanusealongseriesofbooleanandlogicaloperatorsas
conditions.
WHEREstock_prices.closing_price>100
TheWHEREkeyword,aswe'vebeenusingit,comesaftertheJOINandONkeywords.
ItmayseemthatyoucanputtheconditionusedinWHEREinsidetheONkeyword.Thisoftenworksfine(Ican'tsayI'vetriedallthepossibilities).Butthe
generalruleis:
UseONtoconstraintherelationshipsbetweenjoinedtables.
UseWHEREtolimittherecordsreturnedoverall.
Thismakesparticularsenseincaseswheretherearemultipletablesjoinedtogether.Forexample:
SELECT*FROMt1
INNERJOINt2ONt1.id=t2.xid
INNERJOINt3ONt2.id=t3.yid

Subqueries
JustincaseyouthoughtthatJOINclausesdidn'tmakeaSELECTstatementbusyenough:Subqueriesallowyoutopullinaderivedtableordatapointusinga
separatequery.
Theyaresometimesreferredtoasnestedqueries,astheyarequeriesinsideanother(main,or"outer")query.
Let'ssaywewanttofindtheresultsetwithallthecolumnsfromthecompaniestable,plustheclosingpriceforeachcompanyonthemostrecentdateinthe
stock_pricestable.
Let'sfirstlookathowyouwouldfindthelatestclosingpriceforjustthecompanywithidof1:
SELECTcompany_id,date,closing_priceFROMstock_pricesWHEREcompany_id=1ORDERBYdateDESCLIMIT1

Usingasubquery,wecanrunthatqueryforeverycompanyincompanies:
http://ruby.bastardsbook.com/chapters/sql/

12/20

3/2/2016

SQL|TheBastardsBookofRuby

SELECTcompanies.*,
(SELECTclosing_priceFROMstock_prices
WHEREcompanies.id=company_id
ORDERBYdateDESCLIMIT1)ASlatest_closing_price
FROMcompanies

Inthisexample,weareusingthesubquerytopullinasingledatapointthelatestclosing_pricewherestock_prices.company_id=companies_id.Iuse
LIMIT1toexplicitlylimitthesubqueryresultto1resultrow.SQLiteappearstotakethetoprowbydefault,butotherflavorsofSQLmayraiseanerrorifthe
subqueryhasmorethanoneresult.
However,thesubquerymustreturnonlyonecolumn.
Aliasthesubqueryresult

Itisusefulbutnotnecessarytoaliasthesubquery.YoucanusethealiasinasubsequentWHEREclause:
SELECTcompanies.*,
(SELECTclosing_priceFROMstock_prices
WHEREcompanies.id=company_id
ORDERBYdateDESCLIMIT1)ASlatest_closing_price
FROMcompanies
WHERElatest_closing_price>100

Theresult:

Themostrecentclosingstockpriceabove100percompany
Bonusquestion:What'sthedifferencebetweenhavingtheclosing_price>100conditioninthesubqueryversushavingitasaconditioninthemainquery,
i.e.WHERElatest_closing_price>100?
Answer:Inthesubquery,thatconditionwillfindthelatestclosing_pricethatwasabove100.Inthemainquery,thisconditiononlyreturnscompaniesin
whichtheirmostrecentclosing_pricewasabove100.Thelattergroupisequaltoorsmallerthantheformergroup,asmanycompaniesmayhavehada
closing_priceabove100throughoutthetimespancoveredinstock_prices.
Infact,usingtheclosing_priceconditioninthesubquerywillreturnaresultsetofeverycompany,thoughsomeofthemwillhavenovaluefor
latest_closing_priceifnoneoftheirstockpricerecordshadaclosing_price>100.Tryityourself:
SELECTcompanies.*,
(SELECTclosing_priceFROMstock_pricesWHEREcompanies.id=company_id
ANDclosing_price>100
ORDERBYdatedescLIMIT1)aslatest_closing_price
FROMcompanies

Theresult:

http://ruby.bastardsbook.com/chapters/sql/

13/20

3/2/2016

SQL|TheBastardsBookofRuby

Themostrecentclosingstockpricepercompany,ifthepricewasgreaterthan100
UsingasubqueryintheWHEREclause

SubqueriescanbeexecutedoutsideoftheSELECTclauseofaquery.Inthefollowing,I'veputitintheWHEREclause:
SELECTcompanies.*,closing_priceASlatest_closing_price
FROMcompanies
INNERJOINstock_prices
ONcompany_id=companies.id
WHEREdate=(SELECTdateFROMstock_pricesASs2ORDERBYdateDESCLIMIT1)

Thetablebelowcomparestwoversionsofthesamequery:whenthesubqueryisintheWHEREclause(asshownintheexampleimmediatelyabove)tothe
previousversionofthisquery,wherethesubquerywaspartofthemainquery'sSELECTclause:
SUB1:SubqueryinmainSELECTclause

SUB2:SubqueryinWHEREclause

SELECTcompanies.*,closing_priceASlatest_closing_price
SELECTcompanies.*,
FROMcompanies
(SELECTclosing_priceFROMstock_prices
INNERJOINstock_prices
WHEREcompanies.id=company_id
ONcompany_id=companies.id
ORDERBYdateDESCLIMIT1)
WHEREdate=(SELECTdate
ASlatest_closing_price
FROMstock_prices
FROMcompanies
ASs2ORDERBYdateDESCLIMIT1)

Numberoftimessubqueryexecutes:
Onceforeachcompanyrow
Executionspeed(onmylaptop):
1,500milliseconds

Numberoftimessubqueryexecutes:
Justonce
Executionspeed(onmylaptop):
1530milliseconds

Thereisoneveryimportantdistinction,however:IntheversioninwhichthesubqueryisintheWHEREclause(SUB2),thesubqueryexecutesonlyonce.
Hence,itonlyfindsasinglelatestdatewithwhichtofiltertheresultsby.
Therefore,ifagivencompanydoesnothavearecordforthatdate,itwon'tshowupinthemainquery'sresults.SoSUB2returnsonly496companiesinstead
oftheexpected500thatSUB1returns.

http://ruby.bastardsbook.com/chapters/sql/

14/20

3/2/2016

SQL|TheBastardsBookofRuby

SUB2:SubqueryinWHEREclause
Returntochapteroutline

BeyondSELECT
ThischapteronlycoversasubsetofSQLcommandsrelatingtoretrievingrecords.Wedidnotcoverthesyntaxforcreatingtables,insertingrecords,updating
anddeletingrecords.
Nordidwecovertheconceptofcreatingindexesfortables,whichisnecessaryforspeedySELECTstatements(inthesampledata,I'veindexedthetablesfor
you).
ButyouknowenoughaboutthestructureofSQLconceptstogroktheSQLitemanualyourself.IntheCommonSurgeriesproject,Iincludeawalkthroughof
theseSQLoperations.
Ialsohavedoneaquickwriteup,withcode,demonstratinghowIgatheredthestocklistingsandcreatedtheSQLitedatabase.
Returntochapteroutline

UsingSQLite3withRuby
Sofarwe'vebeenwritingpureSQL,whichisprettypowerfulonit'sown.Butifyou'relikeme,SQL'ssyntaxseemsmoreopaquethanscriptinglanguages
suchasRubyandPython.
WhenSQLqueriesinvolvesubqueriesandjoins,thesyntaxcangetespeciallydifficult.WithRuby,though,wecanusetheconstructswealreadyknow
suchasloopsandcollectionsandcombinethemwithSQLcallstodoawiderangeofpowerfuldatacrunchingtechniques.

Thesqlite3gem
Atthebeginningofthischapter,youinstalledtheSQLite3software.TogetittoplaywithRuby,youneedtoinstallthesqlite3gem:
geminstallsqlite3

Thehavebeenafewproblemsreportedongettingthisgemtoinstall.Iwishtherewereacureall,butthesolutiontowhateverproblemyoumighthaveis
dependentonhowyouinstalledSQLite(wasitpreinstalled?DidyouuseHomebrew(MacOSX)?).Again,searchingtheInternetisyourbestbet.
Testoutthesqlite3gem

Onceyou'veinstalledthegem,tryoutthescriptbelow.Itsimplycreatesanewdatabasefilenamed"hello.sqlite"andthenexecutesthefollowingSQL
operations:
1.CREATEatablenamedtestdatawithcolumnsclass_nameandmethod_name
2.INSERTabunchofrows
3.AndseveralSELECTstatements:numberofentries,the20longestmethodnames,andthe10mostcommonmethodnames
TheresultsfromtheSQLSELECTstatementwillbestoredasaRubyarrayandtheniteratedthrough
require'rubygems'
require'sqlite3'
DBNAME="hello.sqlite"
File.delete(DBNAME)ifFile.exists?DBNAME

http://ruby.bastardsbook.com/chapters/sql/

15/20

3/2/2016

SQL|TheBastardsBookofRuby

DB=SQLite3::Database.new(DBNAME)
DB.execute("CREATETABLEtestdata(class_name,method_name)")
#LoopingthroughsomeRubydataclasses
#Thisisthesameinsertquerywe'lluseforeachinsertstatement
insert_query="INSERTINTOtestdata(class_name,method_name)VALUES(?,?)"
[Numeric,String,Array,IO,Kernel,SQLite3,NilClass,MatchData].eachdo|klass|
puts"Insertingmethodsfor#{klass}"
#asecondloop:iteratethrougheachmethod
klass.methods.eachdo|method_name|
#Note:method_nameisactuallyaSymbol,soweneedtoconvertittoaString
#via.to_s
DB.execute(insert_query,klass.to_s,method_name.to_s)
end
end
##Selectrecordcount
q="SELECTCOUNT(1)FROMtestdata"
results=DB.execute(q)
puts"\n\nThereare#{results}totalentries\n"
##Select20longestmethodnames
puts"Longestmethodnames:"
q="SELECT*FROMtestdata
ORDERBYLENGTH(method_name)
DESCLIMIT20"
results=DB.execute(q)
#iterate
results.eachdo|row|
putsrow.join('.')
end
##Selectmostcommonmethods
puts"\nMostcommonmethodnames:"
q="SELECTmethod_name,COUNT(1)ASmcountFROMtestdataGROUPBYmethod_nameORDERBYmcountDESC,LENGTH(method_name)DESCLIMIT10"
results=DB.execute(q)
#iterate
results.eachdo|row|
putsrow.join(":")
end

Thisisthesampleoutput:
Thereare715totalentries
Longestmethodnames:
Numeric.instance_variable_defined?
Numeric.protected_instance_methods
String.instance_variable_defined?
String.protected_instance_methods
Array.instance_variable_defined?
Array.protected_instance_methods
#...

IfyouopenupthefileusingtheFirefoxSQLiteManager(whereeverthescriptsavedhello.sqlite),thetestdatatablelookslikethis:

AsviewedwithFirefoxSQLiteManager
Thenextsectionwillwalkthroughsomeofthebasicfunctionalityofthesqlite3gem.TherestofthischapterwillworkoffofthedatabaseofStandard&
Poor's500stocklistings.Savethe.sqlitefiletowhateverdirectoryinwhichyouaretestingoutyourRuby+sqlite3gemscripts.
http://ruby.bastardsbook.com/chapters/sql/

16/20

3/2/2016

SQL|TheBastardsBookofRuby

Openingupadatabase
require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')

Thefirststepistorequirethesqlite3library.ThenwecreateahandleforadatabasefilewiththeSQLite3::Database.newmethod.
Ifthefiledoesn'texist,anewSQLitedatabaseiscreated.

EXECUTE
results=db.execute("SELECT*fromcompanies;")

TheexecutemethodisthebasicwayofdoingsinglequeriesinRuby.Inthechaptersofar,wehaven'thadtheneedtodomorethanonestatementatonce.
However,you'llfindyourselfwantingtowritemultiplequeriesinasinglestringwhendoingINSERTorUPDATEoperations.TheSQLite3docsdescribethe
execute_batchmethod.

Exercise:Useexecute
WriteouttheRubycodeneededtoopenadatabaseandthenselectallcompanieswithnamesthatbeginwiththeletter'C'
Solution

require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')
db.execute("SELECT*FROMcompanieswherenameLIKE'C%'")

Queryresults
Intheaboveexercise,whatformdoesthereturnvalueofdb.executecomein?
require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')
results=db.execute("SELECT*FROMcompanieswherenameLIKE'C%'")
putsresults.class
#=>Array

TheresultscomeasanArray,whichmeansyoucaniteratethroughthemlikeanycollection:
results.each{|row|putsrow.join(',')}
72,C.H.RobinsonWorldwide,CHRW,Industrials,EdenPrairie,Minnesota
73,CA,Inc.,CA,InformationTechnology,Islandia,NewYork
74,CablevisionSystemsCorp.,CVC,ConsumerDiscretionary,Bethpage,NewYork
75,CabotOil&Gas,COG,Energy,Houston,Texas
76,CameronInternationalCorp.,CAM,Energy,Houston,Texas
77,CampbellSoup,CPB,ConsumerStaples,Camden,NewJersey
78,CapitalOneFinancial,COF,Financials,TysonsCorner,Virginia
79,CardinalHealthInc.,CAH,HealthCare,Dublin,Ohio

Eachentryinthearrayisitselfanarray,withelementsforeachcolumnofthequery.Sothinkofthereturnedresultsasatable,inwhicheachrowisinthe
mainarray:
putsresults[2].join(',')
#=>74,CablevisionSystemsCorp.,CVC,ConsumerDiscretionary,Bethpage,NewYork

Andeachrowcontainsanarrayofcolumns:
putsresults[2][1]
#=>CablevisionSystemsCorp.
Accessthecolumnsbycolumnname

IfyourSELECTqueryreturnsalotofcolumns,you'llprobablyfinditeasiertorefertothecolumnsbynameratherthannumericalindex.Youcandothiswith
theSQLitegembychangingapropertyonthedatabase:
db.results_as_hash=true

NoweachrowwillactasaHashnotethatyouhavetochangethesettingbeforeexecutingthequery:
require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')

http://ruby.bastardsbook.com/chapters/sql/

17/20

3/2/2016

SQL|TheBastardsBookofRuby

db.results_as_hash=true
results=db.execute("SELECT*FROMcompanieswherenameLIKE'C%'")
putsresults[0].class
#=>Hash
puts"#{results[0]['name']}isbasedin#{results[0]['city']},#{results[0]['state']}"
#=>C.H.RobinsonWorldwideisbasedinEdenPrairie,Minnesota

Exercise:Filterresultswithselect
Intheearlierdiscussiononsubqueries,weexaminedaquerythatattemptedtofindthelateststockpricepercompany.
Thistime,insteadofusingsubqueries,writetwoSQLqueriesandcombineitwithRubyloopinglogic.Rememberthatsubqueriescanbethoughtofas
"innerqueries"tobeexecutedbeforethequeryinwhichtheyarenested.
WritetheRubycodethatwillperformthefollowingquerywithoutsubqueries:
SELECTcompanies.*,closing_priceASlatest_closing_price
FROMcompanies
INNERJOINstock_prices
ONcompany_id=companies.id
WHEREdate=(SELECTdateFROMstock_pricesASs2ORDERBYdateDESCLIMIT1)
Solution

require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')
db.results_as_hash=true
inner_results=db.execute("SELECTdateFROMstock_pricesORDERBYdateDESCLIMIT1")
latest_date=inner_results[0]['date']
results=db.execute("
SELECTcompanies.*,closing_priceASlatest_closing_price
FROMcompanies
INNERJOINstock_prices
ONcompany_id=companies.id
WHEREdate='#{latest_date}'
")
results.each{|row|puts"#{row['name']}:#{row['latest_closing_price']}"}

Theresults:
AgilentTechnologiesInc:40.89
AlcoaInc:11.57
AppleInc.:404.95
AmerisourceBergenCorp:42.08
AbbottLaboratories:54.22
...
ZionsBancorp:18.24
ZimmerHoldings:54.3

Placeholders
TheSQLite3gemhasaniftyfeaturethattakescareofthequotesandescapingbackslashesforus.Let'ssaywehaveaprogramthathasanarrayofcity
names:
city_names=["NewYork","Coeurd'Alene","Boise"]

Andthentheprogramloopsthrougheachcitynameandrunsthisquery:
db.execute("SELECT*FROMcompaniesWHEREcityLIKE'#{city_name}%'")

There'saslightproblemhere,though.Whencity_nameis"Coeurd'Alene",theprogramwillattempttoexecutethefollowingquery:
db.execute("SELECT*FROMcompaniesWHEREcityLIKE'Coeurd'Alene%'")

Doyouseetheerror?It'scausedbythecitynamehavinganapostrophe,whichprematurelyclosesthestringintheSQLquery.It'seasytofixthisby
rewritingthecode,likeso:
db.execute("SELECT*FROMcompaniesWHEREcityLIKE\"Coeurd'Alene\%"")

Butinaprogramwithmorecomplicatedqueries,thisisapaintodo.Thankfully,wecanhavetheSQLitegemdotheworkforusbyusingplaceholdersin
thequeryandthenpassingextravaluestotheexecutemethod:
http://ruby.bastardsbook.com/chapters/sql/

18/20

3/2/2016

SQL|TheBastardsBookofRuby

city_names=["NewYork","Coeurd'Alene","Boise"]
city_names.eachdo|city_name|
res=db.execute("SELECT*FROMcompaniesWHEREcityLIKE?","#{city_name}%")
puts"Numberofcompaniesin#{city_name}:#{res.length}"
end

Thequestionmark?isusedasaplaceholder
Thenumberofextraargumentsmustmatchthenumberofplaceholders.Intheaboveexample,thereisoneplaceholder,thus,oneextraargument.
Iftherearemultipleplaceholders,theextraargumentsareinsertedinorder,lefttoright.
Themainthingistomakesureyournumberofargumentsmatchthenumberofplaceholdersandtokeeptheminthecorrectorder.
Exercise:Findstockpriceswithinarandomrange

Writeaprogramthat:
Generatestworandomnumbers,xandy,withybeinggreaterthanx.Bothnumbersshouldbebetween10and200.
Executesaquerytofindallstock_pricesinwhichtheclosing_priceisbetweenxandy
Outputsthenumberofstock_pricesthatmeettheabovecondition
Doesthisoperation10times
Solution

require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')
10.timesdo
x=rand(190)+10
y=x+rand(200x)
res=db.execute("SELECTCOUNT(1)fromstock_pricesWHEREclosing_price>?ANDclosing_price<?",x,y)
puts"Thereare#{res}recordswithclosingpricesbetween#{x}and#{y}"
end

Sampleoutput:
Thereare4639recordswithclosingpricesbetween125and164
Thereare12795recordswithclosingpricesbetween101and193
Thereare23304recordswithclosingpricesbetween51and56
Thereare306415recordswithclosingpricesbetween24and112
Thereare125928recordswithclosingpricesbetween46and100
Thereare29776recordswithclosingpricesbetween74and109
Thereare157recordswithclosingpricesbetween195and199
Thereare270recordswithclosingpricesbetween174and180
Thereare1792recordswithclosingpricesbetween133and148
Thereare6290recordswithclosingpricesbetween120and171
Usinganarraywithplaceholders

Theexecutemethodwillacceptanarrayasanargument,whichitwillautomaticallybreakapartintoindividualarguments.Soofcourse,thenumberof
elementsinthearraymustmatchthenumberofplaceholders:
db.execute("SELECT*fromtable_xwherename=?ANDage=?anddate=?,['Dan',22,'20061031'])

Exercise:Findallcompanynamesthatbeginwitharandomsetofletters

Writeaprogramthat:
Generatesarandomnumber,from1to10,ofrandomalphabeticalletters.
Executesaquerytofindallthecompanynamesthatbeginwithanyofthesetofrandomletters
Outputsthenumberofcompaniesthatmeettheabovecondition
Doesthisoperation10times
Themaindifferencebetweenthisexerciseandthepreviousoneisthatyoudon'tknowhowmanyplaceholdersyou'llneedforthequery.Youcanusestring
interpolationandEnumerablemethodstodynamicallygeneratetheplaceholders.
Hint:YoucangenerateanarrayofalphabetletterswiththisRange:
letters=('A'..'Z').to_a
Solution

Youdon'thavetouseinterpolationsincewe'reonlydealingwithsingleletterswithnochanceofapostrophes.Butthisispractice:
require'rubygems'
require'sqlite3'
db=SQLite3::Database.new('sp500data.sqlite')

http://ruby.bastardsbook.com/chapters/sql/

19/20

3/2/2016

SQL|TheBastardsBookofRuby

LETTERS=('A'..'Z').to_a
10.timesdo
random_letters=LETTERS.shuffle.first(rand(10)+1)
q=random_letters.map{"nameLIKE?"}.join('OR')
res=db.execute("SELECTCOUNT(1)fromcompaniesWHERE#{q}",random_letters.map{|r|"#{r}%"})
puts"Thereare#{res}companieswithnamesthatbeginwith#{random_letters.sort.join(',')}"
end

Sampleresults:
Thereare186companieswithnamesthatbeginwithC,G,M,P,T,W,Y,Z
Thereare219companieswithnamesthatbeginwithB,C,H,I,M,N,S,V,Z
Thereare185companieswithnamesthatbeginwithC,M,N,O,Q,S,T
Thereare104companieswithnamesthatbeginwithC,O,P,U
Thereare14companieswithnamesthatbeginwithR
Thereare74companieswithnamesthatbeginwithB,M,Q,R
Thereare109companieswithnamesthatbeginwithE,F,M,T,X,Y
Thereare189companieswithnamesthatbeginwithB,C,E,F,H,I,O,R,V,Y
Thereare191companieswithnamesthatbeginwithA,G,H,I,M,N,Q,V,W,Z
Thereare28companieswithnamesthatbeginwithJ,W
Takeitslow

Ifyou'rejustlearningRuby,thenhavingtofigureoutanothercompletelydifferentsyntaxSQLisgoingtobedifficult.Sodon'tfeeloverwhelmedthisis
supposedtobealittlecomplicated.
Mymainpurposewastointroduceyoutotheconceptsanddemonstratetheiruseindaytodayprogramming.SowhenyougetcomfortablewithRubyand
haveafewdataintensiveprogramsinmind,you'llatleastknowwheretostart.
IneverlearnedSQLformallybecauseIdidn'tthinkIwantedtobeadatabaseprogrammer.AndIstilldon't.IonlylearnedtheSQLIknowbylookingup
referencesandaskingforhelp.You'llfindthatonceyouknowaconceptexistswhetheritisSQLoranythingyou'llpickitupquicklywhenyouneedto
actuallyuseit.
Manyoftheprojectsinthisbook(will)usedatabasessimplyasafastwaytostoreandaccessinformationyoucanlearnbyexamplefromtheprojects.And
ArtfulSoftware'sCommonMySQLQueriespageisagoldmineofexamples.
NextChapter
UsingNonRubyProgramsWithRuby
PreviousChapter
TheMechanizeGem
ChapterOutline
TableofContents
TheBook

Version:0.1
Home
About
Contents
Resources
Blog
Twitter:@bastardsbook
Facebook
AuthorInfo

DanNguyenisajournalistinNewYork
Email:dan@danwin.com
Blog:danwin.com
Twitter:@dancow
Tumblr:eyeheartnewyork
Flickr
Copyright2011

http://ruby.bastardsbook.com/chapters/sql/

20/20

También podría gustarte