-
Johannes Hörmann authoredJohannes Hörmann authored
SqlBuilder
- 1. Why
- 2. Basic usage
-
3. Examples and Use Cases
- 3.1. already complete condition
- 3.2. simple condition with and & or
- 3.3. JDito Variables
- 3.4. other condition than "# = ?"
- 3.5. condition like "year(#) = ?" with specific FieldType
- 3.6. Subquery
- 3.7. IN statement with array of values
- 3.8. IN statement with subquery
- 3.9. common usage in condition processes with $param variables
Please read the documentation in the Sql_lib itelsf if something is unclear.
1. Why
1.1. Prepared Statements
Prepared Statements should always be used because of
-
Security (Sql injection): If you are used to something like
"select * from PERSON where PERSONID = '" + persId + "'"
you WILL forget to use prepared statements if it is really nessecary:
"select * from PERSON where FIRSTNAME = '" + userInput + "'"
Here the user could input also any Sql.
If you are used to Prepared Statements even if you just select for ID’s, the security and quality of your code will be much higher. -
Speed: If the server executes the prepared statement, it may improve the performance compared to executing the sql-string.
For me the most important aspect is the security.
1.2. Problem
Prepared Statements in JDito are not very easy to use, because you have to load the datatypes for the columns manually and create an array like this:
["select * from PERSON where FIRSTNAME = ?", [[userinput, SQLTYPES.VARCHAR]]]
1.3. Solution
The SqlBuilder (former SqlCondition) provides methods to add conditions without manually loading the type.
So this:
var type = db.getColumnTypes("PERSON", ["FIRSTNAME"])[0]
var persons = db.table(["select FIRSTNAME, LASTNAME from PERSON where FIRSTNAME = ?", [[usereInput, type]]]);
becomes this:
var persons = newSelect("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.FIRSTNAME", userInput)
.table()
which is much cleaner code.
2. Basic usage
2.1. new SqlBuilder creation
Basically you can create a new builder by
var personSelect = new SqlBuilder()
.select("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.PERSONID", persId)
or with the shorthand function newSelect()
var personSelect = newSelect("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.PERSONID", persId)
It is also possible to only use the builder for conditions:
var personCond = new SqlBuilder()
.where("PERSON.PERSONID", persId)
or with the shorthand function newWhere()
var personCond = newWhere("PERSON.PERSONID", persId)
2.2. Conditions
For the conditions we have the methods:
-
where
-
and
-
or
and for each of them also a version with *IfSet: (see 2.2.3 for the difference)
-
whereIfSet
-
andIfSet
-
orIfSet
2.2.1. where
Has to be always used for the first condition.
This is mainly for a better semantic:
new SqlCondition.select("COL1")
.from("TAB1")
.where("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
looks better and more intuitive than:
new SqlCondition.select("COL1")
.from("TAB1")
.and("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
→ if you add a condition, the where keyword is mandatory.
newWhere() already includes the where. So this is perfectly valid:
newWhere("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
It is also valid to use .where without parameters. So something like this is possible:
var cond = newWhere();
if (someCondition...)
{
cond.and("TAB1.COL3", "myVal");
}
if (someOtherCondition...)
{
cond.and("TAB1.COL2", "myVal2");
}
Parameters
where() can have the following parameters:
-
pFieldOrCond
This is eithoer for specifying the field for the condition
(e.g. if you need the sql-condition: "FIRSTNAME = 'Fritz'" then this parameter can be "PERSON.FIRSTNAME").
Or you can provide a already complete condition if all other parameters are not used.
If used as pField, you can provide the table and collumn in different ways. -
as string: "TABLENAME.COLUMNNAME" (this only works if you have no '.' in your names and if you don’t need a table alias
-
as array: ["TABLENAME", "COLUMNNAME"]
-
as array with tableAlias: ["TABLENAME", "COLUMNNAME", "tableAlias"] Here the TABLENAME is used to load the SQLTYPE and tableAlias is used for generating the sql.
-
pValue
This is the value for the condition:
(e.g. if you need the sql-condition: "FIRSTNAME = 'Fritz'" then this parameter is "Fritz") -
pCondition
This parameter defines which condition is used. You can use any SQL. # will be replaced by the pFieldOrCond and ? is used for the pValue (default is "# = ?")
(e.g. if you need the sql-condition: "FIRSTNAME <> 'Fritz'" then this parameter is "# <> ?") -
pFieldType
This parameter is for specifying the SQLTYPE explicitely.
Most of the time you won’t need it, as the type is loaded from the db by the Tablename and Collumname you specified in pFieldOrCond
2.2.2. and / or
After you called .where somehow, you can add more conditions by using .and() / .or() they work exactly like .where() and also have the same parameters
2.2.3. *IfSet
The addition ot "IfSet" to the methods .where, .and, .or makes the condition optional.
This means:
While calling .where with a pValue which is null or undefined will result in an error:
var value = null;
var cond = newWhere("TAB1.COL2", null)
.and("TAB1.COL3", value)
// execution stops with error
whereIfSet will just ignore the whole condition
var value = null;
var cond = newWhereIfSet("TAB1.COL2", null)
.and("TAB1.COL3", value)
// code is executet and cond.build will result in ["TAB1.COL3 = ?", [['myVal', SQLTYPES.VARCHAR]]]
// As you can see. TAB1.COL2 is just ignored completely
2.3. select
select(pFields) just sets all fields defined to the select-section of the sql.
You may pass them as string, SqlBuilder (for subselects) or array (of strings and sqlbuilders)
The following examples are all perfectly valid:
-
just a string (note: aliases are possible): "CAMPAIGNCOSTID idAlias, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET"
-
just an array of strings: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET"]
-
additional keywords like distinct: "distinct CAMPAIGNCOSTID, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET"
-
array and additional keyword: ["distinct CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET"]
-
subselect: newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID")
-
array with strings and subselects: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET", newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID")]
2.4. from
from(pTable, pTableAlias) adds a from-part to the sql.
-
pTable This can either be a String or a SqlBuilder
You may use only the tablename or (not recommended) a full from clause already containing joins. (It is better to use .join for this)
Subselects by using a SqlBuilder are also working -
pTableAlias just adds an alias-name for the table.
2.5. join
A join can be added by using
-
.join(pTable, pCondition, pTableAlias, pPrefix, pReplacementForWordJoin)
-
.leftJoin(pTable, pCondition, pTableAlias)
-
.rightJoin(pTable, pCondition, pTableAlias)
The parameters are:
-
pTable is just the table name as string.
(it is possible but not recommended to use a whole join-string for the table e.g. "PERSON on PERSONID = PERSON_ID". Only use this, if you don’t have a seperate string for the on-condition.)
Subselects are also possible by using a SqlBuilder
(e.g. newSelect("FIRSTNAME").from("PERSON")) -
pCondition is the on-condition.
You can provide it as String (e.g. "PERSONID = PERSON_ID")
or as SqlBuilder (e.g. newWhere("PERSON.PERSONID", myPersonid)) -
pTableAlias just adds an alias-name for the table.
-
pPrefix here you can specify a prefix for the join. (e.g. "left")
.leftJoin / .rightJoin fill this automatically -
pReplacementForWordJoin is for using something different as the word "join". This is added to support db-specific use cases.
An example:
var costData = newSelect("CAMPAIGNCOSTID, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET")
.from("CAMPAIGNCOST")
.leftJoin("CAMPAIGNSTEP", newWhere("CAMPAIGNSTEP_ID = CAMPAIGNSTEPID"))
.where("CAMPAIGNCOST.CAMPAIGN_ID", campaignId)
.table();
2.6. group by
groupBy(pFields) just adds a group by to the sql
the parameter can be filled the same way as .select(pFields)
3. Examples and Use Cases
3.1. already complete condition
If the first parameter is the only one you set, it is treated as a complete condition:
var name = 'Admin'
var cond = newWhere("PERSON.FIRSTNAME is not null") // a simple String
.and(newWhere("PERSON.LASTNAME", name)) // another SqlBuilder (note: only the condition is used from it)
.or(["FIRSTNAME is null and LASTNAME = ?", [["Meier", SQLTYPES.VARCHAR]]]) // a prepared statement array
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'Tim' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Meier' ) ", [['Tim', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Meier', SQLTYPES.VARCHAR]]]
This is also usefull for creating Brackets e.g. for smth. like "(FIRSTNAME = 'Tim' or FIRSTNAME = 'Fritz) and LASTNAME = 'Admin'"
var cond = newWhere(newWhere("PERSON.FIRSTNAME", "Tim")
.or("PERSON.FIRSTNAME", "Fritz"))
.and("PERSON.FIRSTNAME", "Peter")
// ( ( PERSON.FIRSTNAME = 'Tim' or PERSON.FIRSTNAME = 'Fritz' ) and PERSON.FIRSTNAME = 'Peter' )
By using another newWhere the Tim and Fritz are grouped together.
As comparison without the grouping:
var cond = newWhere("PERSON.FIRSTNAME", "Tim")
.or("PERSON.FIRSTNAME", "Fritz")
.and("PERSON.FIRSTNAME", "Peter")
// ( PERSON.FIRSTNAME = 'Tim' or PERSON.FIRSTNAME = 'Fritz' and PERSON.FIRSTNAME = 'Peter' )
3.2. simple condition with and & or
You can just combine and & or as you like:
var cond = newWhere("PERSON.FIRSTNAME", "Tim")
.and("PERSON.LASTNAME", "Admin")
.or("PERSON.FIRSTNAME", "Peter")
.and("PERSON.LASTNAME", "Meier")
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'Tim' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Meier' ) ", [['Tim', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Meier', SQLTYPES.VARCHAR]]]
(Note: the AND before OR: (Tim && Admin) || (Peter && Meier))
3.3. JDito Variables
It is also possible to just pass JDito variables directly:
newWhere("PERSON.FIRSTNAME", "$field.FIRSTNAME")
Note: it is checked for vars.getString(…) != null and vars.getString(…) != undefined
If you want to check also for != "" and vars.exists(…) you have to do it by yourself explicitely.
3.4. other condition than "# = ?"
If you need something different as the default "# = ?" condition just pass a string to pCondition.
Note that the # is replaced by the Table.Column from the first param and ? is the place where the value schould be inserted.
3.5. condition like "year(#) = ?" with specific FieldType
It is also possible to use more complex sql such as "year(#) = ?" But keep in mind that this may change the SQLTYPE needed and you may have to provede it manually by the 4th param if it cannot be loaded by the table and column name.
3.6. Subquery
Subqueries are also possible by providing either another Sql condition or a prepared-statement array.
Some possible combinations:
-
where("TABLE.FIELD", aSqlBuilderContainingFullSelect)
-
where("TABLE.FIELD", aSqlBuilderContainingFullSelect, "# <> ?") → with different condition
-
where(null, aSqlBuilderContainingFullSelect, "exists ?") → without a field: note that pCondition doesn’t contain a # now.
-
where(null, aSqlBuilderContainingFullSelect, SqlBuilder.EXISTS()) → this is the same, but uses a predefined pCondition for exists.
-
where("TABLE.FIELD", aPreparedSqlArray)
-
…
aSqlBuilderContainingFullSelect or aPreparedSqlArray is the subselect.
Examples:
With SqlBuilder
var aSqlBuilderContainingFullSelect = newSelect("LASTNAME")
.from("PERSON")
.where("PERSON.FIRSTNAME", "Fritz")
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect)
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['Fritz', SQLTYPES.VARCHAR]]]
With PreparedSqlArray
var aSqlBuilderContainingFullSelect = ["select LASTNAME from PERSON where PERSON.FIRSTNAME = ?", [['Fritz', SQLTYPES.VARCHAR]]]
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect)
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['Fritz', SQLTYPES.VARCHAR]]]
3.7. IN statement with array of values
pValue can also be an array of values which is automatically transfered into a SQL prepared statement SQL like this:
["(?, ?, ?)", "Fritz", SQLTYPES.VARCHAR], ["Franz", SQLTYPES.VARCHAR], ["Max", SQLTYPES.VARCHAR]
With this it is very easy to create a in-statement:
var cond = newWhere("PERSON.FIRSTNAME", ["Fritz", "Franz", "Max"], SqlBuilder.IN()) // SqlBuilder.IN() == "in ?"
// Note also SqlBuilder.NOT_IN() is possible
Note that an empty array is threated like null or undefined: using .where throws an error, using .whereIfSet ignores the whole condition.
3.8. IN statement with subquery
You can just use a Subquery together with the pCondition SqlBuilder.IN() or SqlBuilderNOT_.IN()
3.9. common usage in condition processes with $param variables
In the conditionProcess often conditions are built based on which parameter is given. This can be done very easy by using the *IfSet methods:
// This is a example condition process containing some possible ways to use the SqlBuilder.
// Using newWhereIfSet() to create a new Condition.
// The 'IfSet' ignores the condition "CONTACT.ORGANISATION_ID" = "$param.OrgId_param" if "$param.OrgId_param" is null
var cond = newWhereIfSet("CONTACT.ORGANISATION_ID", "$param.OrgId_param")
.andIfSet("PERSON.CONTACT_ID", "$param.ContactId_param"); // same again: if "$param.ContactId_param" is null, this line is ignored
// --> so if both parameters where null (not filled) the SqlBuilder cond won't contain any condition!
// Now we add a parameter containing a json Array
// we have to parse it
var excludedContacts = JSON.parse(vars.getString("$param.ExcludedContactIds_param"));
// and add it to cond using not in --------------------------------V
cond.andIfSet("CONTACT.CONTACTID", excludedContacts, SqlBuilder.NOT_IN());
// if excludedContacts is an empty array (or null), no condition is added as we used "andIfSet()"
// if you need a different behaviour you may wrap it by an if
// Same for onlyShowContactIds but by using an in-statement
var onlyShowContactIds = JSON.parse(vars.get("$param.OnlyShowContactIds_param"));
cond.andIfSet("CONTACT.CONTACTID", onlyShowContactIds, SqlBuilder.IN());
// The conditionProcesss doesn't support prepared statements. So .build() is currently not possible.
// using .toString() resolves all conditions and builds the finished select as string.
result.string(cond.toString());