SlideShare a Scribd company logo
QB
A QUERY BUILDER FOR
THE REST OF US
WHAT THIS TALK IS NOT:
> Database performance tips
> How to integrate qb in your application*
> About a new ORM Engine (though qb is be the backend for
one)
* WE'LL TOUCH ON THIS A TINY BIT
WHAT THIS TALK IS:
> How to abstract your SQL statements from your
database grammar
> Help to stop concatenating SQL strings
> Ideas for new patterns to manage complex SQL
WHO AM I?
ERIC PETERSON
!
Utah
"
Ortus
#
ForgeBox, ColdBox Elixir
$
Prolific Module Author
%
1 wife, 2 kids, (1 on the way)
WHAT IS QB?
WHAT IS QB?
QUERYBUILDER & SCHEMABUILDER
> A fluent, chainable syntax for building SQL statements
> An abstraction around different database engines
> An abstraction around queryExecute
> Heavily inspired by Eloquent from Laravel
WHY CREATE QB?
TELL ME IF YOU'VE SEEN
THIS ONE BEFORE
function find( slug, createdBy, popular /* ..and so forth. */ ) {
var q = new Query(); //
!
var sql = "SELECT id, slug, createdBy, updatedDate";
// need this for one of two arguments
if ( ! isNull( popular ) ) {
// fun fact, I was missing a comma here from a previous presentation
// this wouldn't have even worked!
sql &= ", COUNT(*) AS installCount";
}
sql &= " FROM entries"; // now we can add the from
if ( ! isNull( popular ) ) {
sql &= " JOIN installs ON entries.id = installs.entryId";
}
// need some sort of initial where statement
// otherwise we have to worry about `WHERE` vs `AND`
sql &= " WHERE 1 = 1";
if ( ! isNull( slug ) ) {
q.addParam(
name = "slug",
cfsqltype = "CF_SQL_VARCHAR",
value = "%#slug#%",
);
sql &= " AND slug LIKE :slug"; // don't forget the leading space!!
}
if ( ! isNull( createdBy ) ) {
q.addParam(
name = "createdBy",
cfsqltype = "CF_SQL_INTEGER",
value = "%#createdBy#%",
);
sql &= " AND createdBy = :createdBy";
}
if ( ! isNull( popular ) ) {
sql &= " GROUP BY id, slug, createdBy, updatedDate";
}
// make sure to put the order by at the end.
if ( ! isNull( popular ) ) {
sql &= " ORDER BY installCount DESC";
} else {
sql &= " ORDER BY updatedDate DESC";
}
q.setSql( sql );
return q.execute().getResult();
}
WHAT PAIN POINTS DO WE SEE?
> Manually concatenating SQL strings
> Repeating checks in multiple places
> if statements mean no chainability
WHAT COULD THIS LOOK
LIKE WITH QB?
function find( slug, createdBy, popular, count /* ..and so forth. */ ) {
return builder
.from( "entries" )
.select( [ "id", "slug", "createdBy", "updatedDate" ] )
.when( ! isNull( slug ), function( query ) {
query.where( "slug", "like", "%#slug#%" );
} )
.when( ! isNull( createdBy ), function( query ) {
query.where( "createdBy", createdBy );
} )
.when( ! isNull( popular ), function( query ) {
query
.addSelect( query.raw( "COUNT(*) AS installCount" ) )
.join( "installs", "entries.id", "installs.entryId" )
.groupBy( [ "id", "slug", "createdBy", "updatedDate" ] );
} )
.when( ! isNull( popular ), function( query ) {
query.orderBy("installCount", "desc");
}, function( query ) {
query.orderBy("updatedDate", "desc");
} )
.get();
}
AND TRUST ME, WE CAN (AND WILL)
MAKE THAT EVEN BETTER!
QB IS VALUABLE FOR SMALL SQL
STATEMENTS AS WELL!
SPOT THE PROBLEM
function loadPosts() {
return queryExecute("SELECT * FROM `posts`");
}
THE SYNTAX IS DATABASE
ENGINE SPECIFIC
THIS IS AN ALTERNATIVE SYNTAX FOR
OTHER ENGINES
function loadPosts() {
return queryExecute('SELECT * FROM "posts"');
}
IN QB, BOTH OF THESE STATEMENTS ARE COVERED
WITH THIS:
function loadPosts() {
return builder.from("posts").get();
}
AND THAT'S JUST THE TIP
OF THE ICEBERG
SO, WHY CREATE QB?
> Reduce typos
> Bridge database engine idiosyncrasies
> Stop concatentating SQL strings
> Create expressive fluent statements instead of code
littered with if statements
> Enable new Object-Oriented patterns with SQL
statements
QB BUILDING BLOCKS
> Query Builder / Schema Builder
> Grammars
> Query Utils
CONFIGURATION
DEFAULT GRAMMAR
CONFIGURATION
DEFAULT GRAMMAR
CONFIGURATION
Bundled grammars include:
MSSQLGrammar, MySQLGrammar,
OracleGrammar, PostgresGrammar
(We would love your help adding more.)
RETURN FORMAT
CONFIGURATION
> Return a query
> Return an array (default)
> Return something custom!
moduleSettings = {
qb = {
returnFormat = function( query ) {
return application.wirebox.getInstance(
name = "CFCollection",
initArguments = { collection = query }
);
}
}
}
CONFIGURATION
Configure your settings through the moduleSettings
struct in config/ColdBox.cfc.
component {
function configure() {
moduleSettings = {
qb = {
defaultGrammar = "MySQLGrammar"
}
}
}
}
QUERYBUILDER
SYNTAX
AN OVERVIEW
FROM
query.from( "posts" );
> Specifies the base table for the query
SELECT
query.select( "id" );
> Default is *
> Takes a single value, list, or array
> Overwrites any existing selected columns
ADDSELECT
query.addSelect( "id" );
> Adds a column to the select list
> If the selection is currently *, then the added column
will be the only column selected.
JOIN
query.from( "posts" )
.join( "users", "posts.user_id", "=", "users.id" );
> Add a join statement to the query
> Simple syntax for simple joins
> Complex joins can be specified using a callback
WHERE
query.where( "title", "=", "My Title" );
> Adds a where statement to the query
> Automatically params the value passed
> Accepts any SQL operator like <=, <>, or LIKE
WHERE MAGIC
query.where( "title", "My Title" )
Can skip the second parameter if you want the operator
to be =
MORE WHERE MAGIC
query.whereTitle( "My Title" )
onMissingMethod lets you write really terse equality
where statements
NESTED WHERE
query.from( "users" )
.where( "email", "foo" )
.orWhere( function( q ) {
q.where( "name", "bar" )
.where( "age", ">=", "21" );
} );
Becomes
SELECT *
FROM `users`
WHERE `email` = ?
OR ( `name` = ? AND `age` >= ? )
WHERECOLUMN
query.whereColumn( "updated_date", ">", "created_date" );
> Compares two columns
> Accepts any SQL operator like <=, <>, or LIKE
WHEREIN
query.whereIn( "id", [ 1, 45, 101 ] );
> Adds a where in statement to the query
> Automatically params the values passed
OTHER WHERE METHODS
query.whereBetween( "createdDate", startDate, endDate );
query.whereNull( "deletedDate" );
query.whereExists( function( q ) {
q.select( q.raw( 1 ) ).from( "products" )
.where( "products.id", "=", q.raw( """orders"".""id""" ) );
} );
WHERE METHODS WRAP-UP
There are corresponding orWhere and whereNot
methods for each where method.
GROUPBY
query.groupBy( "id" );
> Adds a group by statement
> Further calls add to any existing groupings
> Takes a single value, list, or array
HAVING
query.from( "users" )
.groupBy( "email" )
.having( builder.raw( "COUNT(email)" ), ">", 1 );
> Adds a having statement
> Works pretty much like a where statement
ORDERBY
query.orderBy( "id", "desc" );
> Adds a order by statement
> Further calls add to any existing orders
> Takes a single value, list, or array
LIMIT & OFFSET
query.limit( 1 ).offset( 5 );
> Adds a limit and offset to the query
> This one is a great example where normalizing database
engines is super nice.
MYSQL
LIMIT
SELECT * FROM `users` LIMIT 3
LIMIT & OFFSET
SELECT * FROM `users` LIMIT 15 OFFSET 30
POSTGRES
LIMIT
SELECT * FROM "users" LIMIT 3
LIMIT & OFFSET
SELECT * FROM "users" LIMIT 15 OFFSET 30
MSSQL
LIMIT
SELECT TOP (3) * FROM [users]
LIMIT & OFFSET
SELECT * FROM [users]
ORDER BY 1
OFFSET 30 ROWS
FETCH NEXT 15 ROWS ONLY
ORACLE
LIMIT
SELECT * FROM (
SELECT results.*, ROWNUM AS "QB_RN"
FROM (SELECT * FROM "USERS") results
) WHERE "QB_RN" <= 3
LIMIT & OFFSET
SELECT * FROM (
SELECT results.*, ROWNUM AS "QB_RN"
FROM (SELECT * FROM "USERS") results
) WHERE "QB_RN" > 30 AND "QB_RN" <= 45
RAW
query.select( query.raw( "COUNT(*) AS post_count" ) );
> The qb escape hatch
> Outputs exactly the text you give it
WHEN
query.when( rc.isActive, function( q ) {
q.where( "is_active", 1 );
}, function( q ) {
q.whereNull( "is_active" );
} );
> Control flow for chainable methods
TOSQL
query.from( "users" ).toSQL()
> Builds the query into a SQL statement
> Bindings will be available in the getBindings method
GET
query.from( "users" ).get();
> Builds and executes the current query
> Can accept columns as a shortcut
> Also accepts any options that can be passed to
queryExecute
FIND
query.from( "users" ).find( 1 );
> Shortcut method for retrieving records by primary key
> Default idColumn is id
> idColumn can be specified as second argument
FIRST
query.from( "users" ).first();
> Adds a limit( 1 ) to your query
> Also returns the struct value, not an array of one
> Returns an empty struct if nothing is found
EXISTS
query.from( "logins" )
.where( "user_id", user.getId() )
.exists();
> Returns a boolean
> Works with any configured query
> Just call exists instead of get
COUNT, MAX, MIN, SUM
(AGGREGATES)
query.from( "users" )
.max( "last_logged_in" );
> Returns a single value from query
> Takes the column to perform the operation on
VALUE
query.from( "users" )
.where( "id", "=", rc.id )
.value( "username" );
> Fetches only one record (like first)
> Returns the value of the column specified
VALUES
query.from( "users" )
.values( "username" );
> Returns an array of all matched records
> Returns only the value of the column specified in the
array
INSERT
query.from( "users" ).insert( {
"username" = "johndoe",
"email" = "john@example.com",
"password" = bcryptedPassword
} );
> Inserts data into a table
BATCH INSERT
query.from( "users" ).insert( [
{
"username" = "johndoe",
"email" = "john@example.com",
"password" = bcryptedPassword
},
{
"username" = "janedoe",
"email" = "jane@example.com",
"password" = bcryptedPassword
}
] );
> Uses the database-specific batch insert syntax
UPDATE
query.from( "users" )
.whereId( rc.id )
.update( { "password" = newPassword } );
> Updates all matched rows, so be careful
> Works off of the built query
UPDATEORINSERT
query.from( "users" )
.whereId( rc.id )
.updateOrInsert( {
"username" = "johndoe",
"email" = "john@example.com",
"password" = bcryptedPassword
} );
> Updates or inserts dependening on if the query exists
DELETE
query.from( "users" )
.whereConfirmed( 0 )
.delete();
> Deletes all matched rows, so be careful
> Works off of the built query
WHEW... !
QueryFilters
EXAMPLE
QueryFilters EXAMPLE
// AbstractQueryFilters.cfc
component {
property name="query";
function apply( required query, rc = {} ) {
variables.query = arguments.query;
for ( var key in rc ) {
if ( variables.keyExists( key ) && isCustomFunction( variables[ key ] ) ) {
var func = variables[ key ];
func( rc[ key ] );
}
}
return variables.query;
}
}
QueryFilters EXAMPLE
// EntryFilters.cfc
component extends="AbstractQueryFilters" {
function slug( slug ) {
query.where( "slug", "like", "%#slug#%" );
}
function createdBy( authorName ) {
query.where( "createdBy", createdBy );
}
function popular() {
query.addSelect( query.raw( "COUNT(*) AS installCount" ) )
.join( "installs", "entries.id", "installs.entryId" )
.groupBy( [ "entries.id", "slug", "createdBy", "updatedDate" ] )
.orderBy( "installCount", "desc" );
}
}
QueryFilters EXAMPLE
// handlers/Entries.cfc
component {
property name="EntryFilters" inject="id";
function index(event, rc, prc) {
var query = getInstance( "Builder@qb" );
query.select( [ "entries.id", "slug", "createdBy", "updatedDate" ] )
.from( "entries" )
.orderBy( "updatedDate", "desc" );
EntryFilters.apply( query, rc );
prc.entries = query.get();
}
}
SEE?
I TOLD YOU WE'D MAKE
THAT EVEN BETTER!
DEMO!
CONTRIBUTING
GRAMMARS
CONTRIBUTING
DOCS
CONTRIBUTING
QB Into the Box 2018
WHO KNOWS WHAT YOU WILL
CREATE WITH THIS?
THANKS!
> qb docs
> qb on ForgeBox
> me on Twitter (_elpete)
> me on ForgeBox (elpete))
> CFML Slack (elpete)

More Related Content

What's hot (20)

PDF
Python in the database
pybcn
 
PDF
Auto-GWT : Better GWT Programming with Xtend
Sven Efftinge
 
KEY
Gwt and Xtend
Sven Efftinge
 
PPTX
Python database interfaces
Mohammad Javad Beheshtian
 
PDF
Get database properties using power shell in sql server 2008 techrepublic
Kaing Menglieng
 
PDF
Administering and Monitoring SolrCloud Clusters
lucenerevolution
 
PPTX
Test driven development (java script & mivascript)
Miva
 
PDF
MySQL User Conference 2009: Python and MySQL
Ted Leung
 
PDF
Cassandra Day NYC - Cassandra anti patterns
Christopher Batey
 
DOC
Flashback (Practical Test)
Anar Godjaev
 
PPTX
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Domenic Denicola
 
PDF
Cassandra Summit 2015
jbellis
 
PDF
Cassandra summit keynote 2014
jbellis
 
PPTX
ElasticSearch for .NET Developers
Ben van Mol
 
PDF
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PDF
Scala active record
鉄平 土佐
 
PDF
Cassandra nice use cases and worst anti patterns
Duyhai Doan
 
PDF
greenDAO
Mu Chun Wang
 
PDF
Lean React - Patterns for High Performance [ploneconf2017]
Devon Bernard
 
Python in the database
pybcn
 
Auto-GWT : Better GWT Programming with Xtend
Sven Efftinge
 
Gwt and Xtend
Sven Efftinge
 
Python database interfaces
Mohammad Javad Beheshtian
 
Get database properties using power shell in sql server 2008 techrepublic
Kaing Menglieng
 
Administering and Monitoring SolrCloud Clusters
lucenerevolution
 
Test driven development (java script & mivascript)
Miva
 
MySQL User Conference 2009: Python and MySQL
Ted Leung
 
Cassandra Day NYC - Cassandra anti patterns
Christopher Batey
 
Flashback (Practical Test)
Anar Godjaev
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Domenic Denicola
 
Cassandra Summit 2015
jbellis
 
Cassandra summit keynote 2014
jbellis
 
ElasticSearch for .NET Developers
Ben van Mol
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Scala active record
鉄平 土佐
 
Cassandra nice use cases and worst anti patterns
Duyhai Doan
 
greenDAO
Mu Chun Wang
 
Lean React - Patterns for High Performance [ploneconf2017]
Devon Bernard
 

Similar to QB Into the Box 2018 (20)

PDF
ITB2019 Faster DB Development with QB - Andrew Davis
Ortus Solutions, Corp
 
PDF
ITB 2023 qb, Migration, Seeders. Recipe For Success - Gavin-Pickin.pdf
Ortus Solutions, Corp
 
PDF
PerlApp2Postgresql (2)
Jerome Eteve
 
PPTX
Pyhton with Mysql to perform CRUD operations.pptx
Ramakrishna Reddy Bijjam
 
PPT
JDBC – Java Database Connectivity
Information Technology
 
PPTX
Database Connectivity using Python and MySQL
devsuchaye
 
PDF
Sql Injection Myths and Fallacies
Karwin Software Solutions LLC
 
PPT
Sql injection
Nitish Kumar
 
PPTX
Linuxfest Northwest 2022 - MySQL 8.0 Nre Features
Dave Stokes
 
PPTX
Cfml features modern coding into the box 2018
Ortus Solutions, Corp
 
PPT
ShmooCon 2009 - (Re)Playing(Blind)Sql
Chema Alonso
 
ODP
My sql
Nadhi ya
 
PPT
My sql with querys
NIRMAL FELIX
 
PPT
MySql slides (ppt)
webhostingguy
 
PPTX
Learn PHP Lacture2
ADARSH BHATT
 
PPTX
SQL for Web APIs - Simplifying Data Access for API Consumers
Jerod Johnson
 
PPT
Raj mysql
firstplanet
 
PPTX
DBMS UNIT 9.pptx..................................
VishwanathJustRockin
 
PPTX
Html web sql database
AbhishekMondal42
 
ODP
Mysqlppt
poornima sugumaran
 
ITB2019 Faster DB Development with QB - Andrew Davis
Ortus Solutions, Corp
 
ITB 2023 qb, Migration, Seeders. Recipe For Success - Gavin-Pickin.pdf
Ortus Solutions, Corp
 
PerlApp2Postgresql (2)
Jerome Eteve
 
Pyhton with Mysql to perform CRUD operations.pptx
Ramakrishna Reddy Bijjam
 
JDBC – Java Database Connectivity
Information Technology
 
Database Connectivity using Python and MySQL
devsuchaye
 
Sql Injection Myths and Fallacies
Karwin Software Solutions LLC
 
Sql injection
Nitish Kumar
 
Linuxfest Northwest 2022 - MySQL 8.0 Nre Features
Dave Stokes
 
Cfml features modern coding into the box 2018
Ortus Solutions, Corp
 
ShmooCon 2009 - (Re)Playing(Blind)Sql
Chema Alonso
 
My sql
Nadhi ya
 
My sql with querys
NIRMAL FELIX
 
MySql slides (ppt)
webhostingguy
 
Learn PHP Lacture2
ADARSH BHATT
 
SQL for Web APIs - Simplifying Data Access for API Consumers
Jerod Johnson
 
Raj mysql
firstplanet
 
DBMS UNIT 9.pptx..................................
VishwanathJustRockin
 
Html web sql database
AbhishekMondal42
 
Mysqlppt
poornima sugumaran
 
Ad

More from Ortus Solutions, Corp (20)

PDF
TheFutureIsDynamic-BoxLang witch Luis Majano.pdf
Ortus Solutions, Corp
 
PDF
June Webinar: BoxLang-Dynamic-AWS-Lambda
Ortus Solutions, Corp
 
PDF
BoxLang-Dynamic-AWS-Lambda by Luis Majano.pdf
Ortus Solutions, Corp
 
PDF
What's-New-with-BoxLang-Brad Wood.pptx.pdf
Ortus Solutions, Corp
 
PDF
Getting Started with BoxLang - CFCamp 2025.pdf
Ortus Solutions, Corp
 
PDF
CFCamp2025 - Keynote Day 1 led by Luis Majano.pdf
Ortus Solutions, Corp
 
PDF
What's New with BoxLang Led by Brad Wood.pdf
Ortus Solutions, Corp
 
PDF
Vector Databases and the BoxLangCFML Developer.pdf
Ortus Solutions, Corp
 
PDF
Using cbSSO in a ColdBox App Led by Jacob Beers.pdf
Ortus Solutions, Corp
 
PDF
Use JSON to Slash Your Database Performance.pdf
Ortus Solutions, Corp
 
PDF
Portable CI wGitLab and Github led by Gavin Pickin.pdf
Ortus Solutions, Corp
 
PDF
Tame the Mesh An intro to cross-platform tracing and troubleshooting.pdf
Ortus Solutions, Corp
 
PDF
Supercharging CommandBox with Let's Encrypt.pdf
Ortus Solutions, Corp
 
PDF
Spice up your site with cool animations using GSAP..pdf
Ortus Solutions, Corp
 
PDF
Passkeys and cbSecurity Led by Eric Peterson.pdf
Ortus Solutions, Corp
 
PDF
Legacy Code Nightmares , Hellscapes, and Lessons Learned.pdf
Ortus Solutions, Corp
 
PDF
Integrating the OpenAI API in Your Coldfusion Apps.pdf
Ortus Solutions, Corp
 
PDF
Hidden Gems in FusionReactor for BoxLang, ACF, and Lucee Users.pdf
Ortus Solutions, Corp
 
PDF
Geting-started with BoxLang Led By Raymon Camden.pdf
Ortus Solutions, Corp
 
PDF
From Zero to CRUD with ORM - Led by Annette Liskey.pdf
Ortus Solutions, Corp
 
TheFutureIsDynamic-BoxLang witch Luis Majano.pdf
Ortus Solutions, Corp
 
June Webinar: BoxLang-Dynamic-AWS-Lambda
Ortus Solutions, Corp
 
BoxLang-Dynamic-AWS-Lambda by Luis Majano.pdf
Ortus Solutions, Corp
 
What's-New-with-BoxLang-Brad Wood.pptx.pdf
Ortus Solutions, Corp
 
Getting Started with BoxLang - CFCamp 2025.pdf
Ortus Solutions, Corp
 
CFCamp2025 - Keynote Day 1 led by Luis Majano.pdf
Ortus Solutions, Corp
 
What's New with BoxLang Led by Brad Wood.pdf
Ortus Solutions, Corp
 
Vector Databases and the BoxLangCFML Developer.pdf
Ortus Solutions, Corp
 
Using cbSSO in a ColdBox App Led by Jacob Beers.pdf
Ortus Solutions, Corp
 
Use JSON to Slash Your Database Performance.pdf
Ortus Solutions, Corp
 
Portable CI wGitLab and Github led by Gavin Pickin.pdf
Ortus Solutions, Corp
 
Tame the Mesh An intro to cross-platform tracing and troubleshooting.pdf
Ortus Solutions, Corp
 
Supercharging CommandBox with Let's Encrypt.pdf
Ortus Solutions, Corp
 
Spice up your site with cool animations using GSAP..pdf
Ortus Solutions, Corp
 
Passkeys and cbSecurity Led by Eric Peterson.pdf
Ortus Solutions, Corp
 
Legacy Code Nightmares , Hellscapes, and Lessons Learned.pdf
Ortus Solutions, Corp
 
Integrating the OpenAI API in Your Coldfusion Apps.pdf
Ortus Solutions, Corp
 
Hidden Gems in FusionReactor for BoxLang, ACF, and Lucee Users.pdf
Ortus Solutions, Corp
 
Geting-started with BoxLang Led By Raymon Camden.pdf
Ortus Solutions, Corp
 
From Zero to CRUD with ORM - Led by Annette Liskey.pdf
Ortus Solutions, Corp
 
Ad

Recently uploaded (20)

PDF
introduction to computer hardware and sofeware
chauhanshraddha2007
 
PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PDF
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
PPTX
Agentic AI in Healthcare Driving the Next Wave of Digital Transformation
danielle hunter
 
PPTX
Farrell_Programming Logic and Design slides_10e_ch02_PowerPoint.pptx
bashnahara11
 
PDF
GDG Cloud Munich - Intro - Luiz Carneiro - #BuildWithAI - July - Abdel.pdf
Luiz Carneiro
 
PDF
The Future of Mobile Is Context-Aware—Are You Ready?
iProgrammer Solutions Private Limited
 
PPTX
AI Code Generation Risks (Ramkumar Dilli, CIO, Myridius)
Priyanka Aash
 
PDF
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 
PPTX
Agile Chennai 18-19 July 2025 | Workshop - Enhancing Agile Collaboration with...
AgileNetwork
 
PDF
The Future of Artificial Intelligence (AI)
Mukul
 
PPTX
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
PDF
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
PDF
How ETL Control Logic Keeps Your Pipelines Safe and Reliable.pdf
Stryv Solutions Pvt. Ltd.
 
PDF
The Past, Present & Future of Kenya's Digital Transformation
Moses Kemibaro
 
PDF
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
PDF
Researching The Best Chat SDK Providers in 2025
Ray Fields
 
PPTX
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
PDF
Market Insight : ETH Dominance Returns
CIFDAQ
 
PDF
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
introduction to computer hardware and sofeware
chauhanshraddha2007
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
Agentic AI in Healthcare Driving the Next Wave of Digital Transformation
danielle hunter
 
Farrell_Programming Logic and Design slides_10e_ch02_PowerPoint.pptx
bashnahara11
 
GDG Cloud Munich - Intro - Luiz Carneiro - #BuildWithAI - July - Abdel.pdf
Luiz Carneiro
 
The Future of Mobile Is Context-Aware—Are You Ready?
iProgrammer Solutions Private Limited
 
AI Code Generation Risks (Ramkumar Dilli, CIO, Myridius)
Priyanka Aash
 
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 
Agile Chennai 18-19 July 2025 | Workshop - Enhancing Agile Collaboration with...
AgileNetwork
 
The Future of Artificial Intelligence (AI)
Mukul
 
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
How ETL Control Logic Keeps Your Pipelines Safe and Reliable.pdf
Stryv Solutions Pvt. Ltd.
 
The Past, Present & Future of Kenya's Digital Transformation
Moses Kemibaro
 
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
Researching The Best Chat SDK Providers in 2025
Ray Fields
 
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
Market Insight : ETH Dominance Returns
CIFDAQ
 
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 

QB Into the Box 2018

  • 1. QB A QUERY BUILDER FOR THE REST OF US
  • 2. WHAT THIS TALK IS NOT: > Database performance tips > How to integrate qb in your application* > About a new ORM Engine (though qb is be the backend for one) * WE'LL TOUCH ON THIS A TINY BIT
  • 3. WHAT THIS TALK IS: > How to abstract your SQL statements from your database grammar > Help to stop concatenating SQL strings > Ideas for new patterns to manage complex SQL
  • 4. WHO AM I? ERIC PETERSON ! Utah " Ortus # ForgeBox, ColdBox Elixir $ Prolific Module Author % 1 wife, 2 kids, (1 on the way)
  • 6. WHAT IS QB? QUERYBUILDER & SCHEMABUILDER > A fluent, chainable syntax for building SQL statements > An abstraction around different database engines > An abstraction around queryExecute > Heavily inspired by Eloquent from Laravel
  • 8. TELL ME IF YOU'VE SEEN THIS ONE BEFORE
  • 9. function find( slug, createdBy, popular /* ..and so forth. */ ) { var q = new Query(); // ! var sql = "SELECT id, slug, createdBy, updatedDate"; // need this for one of two arguments if ( ! isNull( popular ) ) { // fun fact, I was missing a comma here from a previous presentation // this wouldn't have even worked! sql &= ", COUNT(*) AS installCount"; } sql &= " FROM entries"; // now we can add the from if ( ! isNull( popular ) ) { sql &= " JOIN installs ON entries.id = installs.entryId"; } // need some sort of initial where statement // otherwise we have to worry about `WHERE` vs `AND` sql &= " WHERE 1 = 1"; if ( ! isNull( slug ) ) { q.addParam( name = "slug", cfsqltype = "CF_SQL_VARCHAR", value = "%#slug#%", ); sql &= " AND slug LIKE :slug"; // don't forget the leading space!! } if ( ! isNull( createdBy ) ) { q.addParam( name = "createdBy", cfsqltype = "CF_SQL_INTEGER", value = "%#createdBy#%", ); sql &= " AND createdBy = :createdBy"; } if ( ! isNull( popular ) ) { sql &= " GROUP BY id, slug, createdBy, updatedDate"; } // make sure to put the order by at the end. if ( ! isNull( popular ) ) { sql &= " ORDER BY installCount DESC"; } else { sql &= " ORDER BY updatedDate DESC"; } q.setSql( sql ); return q.execute().getResult(); }
  • 10. WHAT PAIN POINTS DO WE SEE? > Manually concatenating SQL strings > Repeating checks in multiple places > if statements mean no chainability
  • 11. WHAT COULD THIS LOOK LIKE WITH QB?
  • 12. function find( slug, createdBy, popular, count /* ..and so forth. */ ) { return builder .from( "entries" ) .select( [ "id", "slug", "createdBy", "updatedDate" ] ) .when( ! isNull( slug ), function( query ) { query.where( "slug", "like", "%#slug#%" ); } ) .when( ! isNull( createdBy ), function( query ) { query.where( "createdBy", createdBy ); } ) .when( ! isNull( popular ), function( query ) { query .addSelect( query.raw( "COUNT(*) AS installCount" ) ) .join( "installs", "entries.id", "installs.entryId" ) .groupBy( [ "id", "slug", "createdBy", "updatedDate" ] ); } ) .when( ! isNull( popular ), function( query ) { query.orderBy("installCount", "desc"); }, function( query ) { query.orderBy("updatedDate", "desc"); } ) .get(); }
  • 13. AND TRUST ME, WE CAN (AND WILL) MAKE THAT EVEN BETTER!
  • 14. QB IS VALUABLE FOR SMALL SQL STATEMENTS AS WELL!
  • 15. SPOT THE PROBLEM function loadPosts() { return queryExecute("SELECT * FROM `posts`"); }
  • 16. THE SYNTAX IS DATABASE ENGINE SPECIFIC
  • 17. THIS IS AN ALTERNATIVE SYNTAX FOR OTHER ENGINES function loadPosts() { return queryExecute('SELECT * FROM "posts"'); }
  • 18. IN QB, BOTH OF THESE STATEMENTS ARE COVERED WITH THIS: function loadPosts() { return builder.from("posts").get(); }
  • 19. AND THAT'S JUST THE TIP OF THE ICEBERG
  • 20. SO, WHY CREATE QB? > Reduce typos > Bridge database engine idiosyncrasies > Stop concatentating SQL strings > Create expressive fluent statements instead of code littered with if statements > Enable new Object-Oriented patterns with SQL statements
  • 21. QB BUILDING BLOCKS > Query Builder / Schema Builder > Grammars > Query Utils
  • 24. DEFAULT GRAMMAR CONFIGURATION Bundled grammars include: MSSQLGrammar, MySQLGrammar, OracleGrammar, PostgresGrammar (We would love your help adding more.)
  • 25. RETURN FORMAT CONFIGURATION > Return a query > Return an array (default) > Return something custom!
  • 26. moduleSettings = { qb = { returnFormat = function( query ) { return application.wirebox.getInstance( name = "CFCollection", initArguments = { collection = query } ); } } }
  • 27. CONFIGURATION Configure your settings through the moduleSettings struct in config/ColdBox.cfc. component { function configure() { moduleSettings = { qb = { defaultGrammar = "MySQLGrammar" } } } }
  • 29. FROM query.from( "posts" ); > Specifies the base table for the query
  • 30. SELECT query.select( "id" ); > Default is * > Takes a single value, list, or array > Overwrites any existing selected columns
  • 31. ADDSELECT query.addSelect( "id" ); > Adds a column to the select list > If the selection is currently *, then the added column will be the only column selected.
  • 32. JOIN query.from( "posts" ) .join( "users", "posts.user_id", "=", "users.id" ); > Add a join statement to the query > Simple syntax for simple joins > Complex joins can be specified using a callback
  • 33. WHERE query.where( "title", "=", "My Title" ); > Adds a where statement to the query > Automatically params the value passed > Accepts any SQL operator like <=, <>, or LIKE
  • 34. WHERE MAGIC query.where( "title", "My Title" ) Can skip the second parameter if you want the operator to be =
  • 35. MORE WHERE MAGIC query.whereTitle( "My Title" ) onMissingMethod lets you write really terse equality where statements
  • 36. NESTED WHERE query.from( "users" ) .where( "email", "foo" ) .orWhere( function( q ) { q.where( "name", "bar" ) .where( "age", ">=", "21" ); } ); Becomes SELECT * FROM `users` WHERE `email` = ? OR ( `name` = ? AND `age` >= ? )
  • 37. WHERECOLUMN query.whereColumn( "updated_date", ">", "created_date" ); > Compares two columns > Accepts any SQL operator like <=, <>, or LIKE
  • 38. WHEREIN query.whereIn( "id", [ 1, 45, 101 ] ); > Adds a where in statement to the query > Automatically params the values passed
  • 39. OTHER WHERE METHODS query.whereBetween( "createdDate", startDate, endDate ); query.whereNull( "deletedDate" ); query.whereExists( function( q ) { q.select( q.raw( 1 ) ).from( "products" ) .where( "products.id", "=", q.raw( """orders"".""id""" ) ); } );
  • 40. WHERE METHODS WRAP-UP There are corresponding orWhere and whereNot methods for each where method.
  • 41. GROUPBY query.groupBy( "id" ); > Adds a group by statement > Further calls add to any existing groupings > Takes a single value, list, or array
  • 42. HAVING query.from( "users" ) .groupBy( "email" ) .having( builder.raw( "COUNT(email)" ), ">", 1 ); > Adds a having statement > Works pretty much like a where statement
  • 43. ORDERBY query.orderBy( "id", "desc" ); > Adds a order by statement > Further calls add to any existing orders > Takes a single value, list, or array
  • 44. LIMIT & OFFSET query.limit( 1 ).offset( 5 ); > Adds a limit and offset to the query > This one is a great example where normalizing database engines is super nice.
  • 45. MYSQL LIMIT SELECT * FROM `users` LIMIT 3 LIMIT & OFFSET SELECT * FROM `users` LIMIT 15 OFFSET 30
  • 46. POSTGRES LIMIT SELECT * FROM "users" LIMIT 3 LIMIT & OFFSET SELECT * FROM "users" LIMIT 15 OFFSET 30
  • 47. MSSQL LIMIT SELECT TOP (3) * FROM [users] LIMIT & OFFSET SELECT * FROM [users] ORDER BY 1 OFFSET 30 ROWS FETCH NEXT 15 ROWS ONLY
  • 48. ORACLE LIMIT SELECT * FROM ( SELECT results.*, ROWNUM AS "QB_RN" FROM (SELECT * FROM "USERS") results ) WHERE "QB_RN" <= 3 LIMIT & OFFSET SELECT * FROM ( SELECT results.*, ROWNUM AS "QB_RN" FROM (SELECT * FROM "USERS") results ) WHERE "QB_RN" > 30 AND "QB_RN" <= 45
  • 49. RAW query.select( query.raw( "COUNT(*) AS post_count" ) ); > The qb escape hatch > Outputs exactly the text you give it
  • 50. WHEN query.when( rc.isActive, function( q ) { q.where( "is_active", 1 ); }, function( q ) { q.whereNull( "is_active" ); } ); > Control flow for chainable methods
  • 51. TOSQL query.from( "users" ).toSQL() > Builds the query into a SQL statement > Bindings will be available in the getBindings method
  • 52. GET query.from( "users" ).get(); > Builds and executes the current query > Can accept columns as a shortcut > Also accepts any options that can be passed to queryExecute
  • 53. FIND query.from( "users" ).find( 1 ); > Shortcut method for retrieving records by primary key > Default idColumn is id > idColumn can be specified as second argument
  • 54. FIRST query.from( "users" ).first(); > Adds a limit( 1 ) to your query > Also returns the struct value, not an array of one > Returns an empty struct if nothing is found
  • 55. EXISTS query.from( "logins" ) .where( "user_id", user.getId() ) .exists(); > Returns a boolean > Works with any configured query > Just call exists instead of get
  • 56. COUNT, MAX, MIN, SUM (AGGREGATES) query.from( "users" ) .max( "last_logged_in" ); > Returns a single value from query > Takes the column to perform the operation on
  • 57. VALUE query.from( "users" ) .where( "id", "=", rc.id ) .value( "username" ); > Fetches only one record (like first) > Returns the value of the column specified
  • 58. VALUES query.from( "users" ) .values( "username" ); > Returns an array of all matched records > Returns only the value of the column specified in the array
  • 59. INSERT query.from( "users" ).insert( { "username" = "johndoe", "email" = "[email protected]", "password" = bcryptedPassword } ); > Inserts data into a table
  • 60. BATCH INSERT query.from( "users" ).insert( [ { "username" = "johndoe", "email" = "[email protected]", "password" = bcryptedPassword }, { "username" = "janedoe", "email" = "[email protected]", "password" = bcryptedPassword } ] ); > Uses the database-specific batch insert syntax
  • 61. UPDATE query.from( "users" ) .whereId( rc.id ) .update( { "password" = newPassword } ); > Updates all matched rows, so be careful > Works off of the built query
  • 62. UPDATEORINSERT query.from( "users" ) .whereId( rc.id ) .updateOrInsert( { "username" = "johndoe", "email" = "[email protected]", "password" = bcryptedPassword } ); > Updates or inserts dependening on if the query exists
  • 63. DELETE query.from( "users" ) .whereConfirmed( 0 ) .delete(); > Deletes all matched rows, so be careful > Works off of the built query
  • 66. QueryFilters EXAMPLE // AbstractQueryFilters.cfc component { property name="query"; function apply( required query, rc = {} ) { variables.query = arguments.query; for ( var key in rc ) { if ( variables.keyExists( key ) && isCustomFunction( variables[ key ] ) ) { var func = variables[ key ]; func( rc[ key ] ); } } return variables.query; } }
  • 67. QueryFilters EXAMPLE // EntryFilters.cfc component extends="AbstractQueryFilters" { function slug( slug ) { query.where( "slug", "like", "%#slug#%" ); } function createdBy( authorName ) { query.where( "createdBy", createdBy ); } function popular() { query.addSelect( query.raw( "COUNT(*) AS installCount" ) ) .join( "installs", "entries.id", "installs.entryId" ) .groupBy( [ "entries.id", "slug", "createdBy", "updatedDate" ] ) .orderBy( "installCount", "desc" ); } }
  • 68. QueryFilters EXAMPLE // handlers/Entries.cfc component { property name="EntryFilters" inject="id"; function index(event, rc, prc) { var query = getInstance( "Builder@qb" ); query.select( [ "entries.id", "slug", "createdBy", "updatedDate" ] ) .from( "entries" ) .orderBy( "updatedDate", "desc" ); EntryFilters.apply( query, rc ); prc.entries = query.get(); } }
  • 69. SEE?
  • 70. I TOLD YOU WE'D MAKE THAT EVEN BETTER!
  • 71. DEMO!
  • 76. WHO KNOWS WHAT YOU WILL CREATE WITH THIS?
  • 77. THANKS! > qb docs > qb on ForgeBox > me on Twitter (_elpete) > me on ForgeBox (elpete)) > CFML Slack (elpete)