6.4 plisqin-lib/strict
(require plisqin-lib/strict) | package: plisqin |
syntax
(val x [type])
Here [type] denotes an optional argument, not literal brackets.A variant of (%%val x [type]). This variant protects against SQL injection by requiring x to be a literal string, number, or boolean.Examples:
procedure
(?? token-1 [token-N ...+] [/fallback])
token-1 : Token?
token-N : Token?
/fallback : fallback? This procedure is typically used to attach a fallback, but it can also be used as a synonym for coalesce if no fallback is given. More precisely, this procedure has 3 cases:You may need to check the documentation on coalesce to know the return type of cases 2 and 3.
6.4.1 Clauses
If a query or join has no select clauses, then all the columns of its queryable are returned. Otherwise, each select clause adds a column to the result set
> (aw:show-table (from pc ProductCategory (select (ProductCategoryID pc)) (select (Name pc)) (select (val "hello world"))))
Show TableShow SQL
select pc.ProductCategoryID as ProductCategoryID
, pc.Name as Name
, 'hello world'
from ProductCategory pc
ProductCategoryID
Name
'hello world'
1
Bikes
hello world
2
Components
hello world
3
Clothing
hello world
4
Accessories
hello world
Eliminates rows from the result set that do not satisfy the given condition.
> (aw:show-table (from pc ProductCategory (where (.= (Name pc) (val "Bikes")))))
Show TableShow SQL
select pc.*
from ProductCategory pc
where (pc.Name = 'Bikes')
ProductCategoryID
Name
rowguid
ModifiedDate
1
Bikes
{CFBDA25C-DF71-47A7-B81B-64EE161AA37C}
2008-04-30 00:00:00
Adds a grouping key to the result set. See example on having or read the Aggregates section for more complete details.
Can only be used when group-by clauses are also present. Eliminates rows from the result set that do not satisfy the given condition.
> (aw:show-table (from subcat ProductSubcategory (group-by (ProductCategoryID subcat)) (having (.> (count subcat) (val 10))) (select (ProductCategoryID subcat)) (select (>> (count subcat) #:as 'NumSubcategories))))
Show TableShow SQL
select subcat.ProductCategoryID as ProductCategoryID
, count(*) as NumSubcategories
from ProductSubcategory subcat
group by subcat.ProductCategoryID
having (count(*) > 10)
ProductCategoryID
NumSubcategories
2
14
4
12
Note that where is applied to individual rows, while having is applied to groups. You can imagine that your database engine first applies any where clauses, then applies any group-by clauses, then applies any having clauses.
procedure
order-by :
(token-constructor [(or/c 'asc 'desc) Scalar? -> OrderBy?] [Scalar? -> OrderBy?])
Specifies how the result set should be ordered. If any previous order-by clauses exist, this one will be used to break any ties. If the first argument is 'asc or 'desc, it specifies ascending or descending; otherwise the default is ascending.
> (aw:show-table (from p Product (limit 5) (order-by 'desc (Color p)) (order-by 'desc (ProductID p))))
Show TableShow SQL
select p.*
from Product p
order by p.Color desc, p.ProductID desc
limit 5
ProductID
Name
ProductNumber
MakeFlag
FinishedGoodsFlag
Color
SafetyStockLevel
ReorderPoint
StandardCost
ListPrice
Size
SizeUnitMeasureCode
WeightUnitMeasureCode
Weight
DaysToManufacture
ProductLine
Class
Style
ProductSubcategoryID
ProductModelID
SellStartDate
SellEndDate
DiscontinuedDate
rowguid
ModifiedDate
976
Road-350-W Yellow, 48
BK-R79Y-48
1
1
Yellow
100
75
1082.51
1700.99
48
CM
LB
16.42
4
R
M
W
2
27
2013-05-30 00:00:00
#<sql-null>
#<sql-null>
{EC4284DC-85FA-44A8-89EC-77FC9B71720A}
2014-02-08 10:01:36.827000000
975
Road-350-W Yellow, 44
BK-R79Y-44
1
1
Yellow
100
75
1082.51
1700.99
44
CM
LB
16.13
4
R
M
W
2
27
2013-05-30 00:00:00
#<sql-null>
#<sql-null>
{0C61E8AF-003D-4E4B-B5B7-02F01A26BE26}
2014-02-08 10:01:36.827000000
974
Road-350-W Yellow, 42
BK-R79Y-42
1
1
Yellow
100
75
1082.51
1700.99
42
CM
LB
15.77
4
R
M
W
2
27
2013-05-30 00:00:00
#<sql-null>
#<sql-null>
{80BD3F8B-42C7-43D8-91F5-9FB6175287AF}
2014-02-08 10:01:36.827000000
973
Road-350-W Yellow, 40
BK-R79Y-40
1
1
Yellow
100
75
1082.51
1700.99
40
CM
LB
15.35
4
R
M
W
2
27
2013-05-30 00:00:00
#<sql-null>
#<sql-null>
{237B16D9-53F2-4FD4-BEFE-48209E57AEC3}
2014-02-08 10:01:36.827000000
965
Touring-3000 Yellow, 62
BK-T18Y-62
1
1
Yellow
100
75
461.4448
742.35
62
CM
LB
30.00
4
T
L
U
3
36
2013-05-30 00:00:00
#<sql-null>
#<sql-null>
{D28B3872-5173-40A4-B12F-655524386CC7}
2014-02-08 10:01:36.827000000
This clause can only be used inside join; not from. Specifies how rows from this join should be matched to the parent query.
> (aw:show-table (from subcat ProductSubcategory (join cat ProductCategory (join-on (.= (ProductCategoryID cat) (ProductCategoryID subcat)))) (%%select subcat".Name as SubcategoryName") (%%select cat".Name as CategoryName") (limit 5)))
Show TableShow SQL
select subcat.Name as SubcategoryName
, cat.Name as CategoryName
from ProductSubcategory subcat
inner join ProductCategory cat
on (cat.ProductCategoryID = subcat.ProductCategoryID)
limit 5
SubcategoryName
CategoryName
Mountain Bikes
Bikes
Road Bikes
Bikes
Touring Bikes
Bikes
Handlebars
Components
Bottom Brackets
Components
6.4.2 Aggregates
Produces the average of the given expression, relative to each group. The following example uses traditional aggregation, but every aggregate in Plisqin also works with grouped join aggregation. Read Aggregates for complete understanding.
> (aw:show-table (from p Product (group-by (Color p)) (select (Color p)) (select (count p)) (select (avg (ListPrice p))) (select (min (ListPrice p))) (select (max (ListPrice p))) (select (sum (ListPrice p)))))
Show TableShow SQL
select p.Color as Color
, count(*)
, avg(p.ListPrice)
, min(p.ListPrice)
, max(p.ListPrice)
, sum(p.ListPrice)
from Product p
group by p.Color
Color
count(*)
avg(p.ListPrice)
min(p.ListPrice)
max(p.ListPrice)
sum(p.ListPrice)
#<sql-null>
248
16.864193548387078
0.0
229.49
4182.319999999995
Black
93
725.1210752688169
0.0
3374.99
67436.25999999997
Blue
26
923.6792307692305
34.99
2384.07
24015.659999999993
Grey
1
125.0
125.0
125.0
125.0
Multi
8
59.865
8.99
89.99
478.92
Red
38
1401.9499999999991
34.99
3578.27
53274.09999999997
Silver
43
850.305348837209
0.0
3399.99
36563.12999999998
Silver/Black
7
64.01857142857143
40.49
80.99
448.13
White
4
9.245000000000001
8.99
9.5
36.980000000000004
Yellow
36
959.0913888888887
53.99
2384.07
34527.28999999999
procedure
min :
(token-constructor [String? -> String?] [Datetime? -> Datetime?] [Number? -> Number?])
procedure
max :
(token-constructor [String? -> String?] [Datetime? -> Datetime?] [Number? -> Number?])
procedure
count :
(token-constructor [instance? -> Number?] [Scalar? -> Number?] [(or/c 'distinct) Scalar? -> Number?])
When given an instance?, counts how many rows are in each group. An example of this can be seen on avg.
When given a Scalar?, counts how many non-dbnull values are in each group. When the scalar is preceded by 'distinct, counts how many unique non-dbnull values are in each group.
In this example, (count (ProductID p)) is equivalent to (count p) because the ProductID is non-nullable.
> (aw:show-table (from p Product (select (>> (count p) #:as 'Num_Products)) (select (>> (count (ProductID p)) #:as 'Num_Product_IDs)) (select (>> (count (ProductSubcategoryID p)) #:as 'Num_Products_With_Subcategories)) (select (>> (count 'distinct (ProductSubcategoryID p)) #:as 'Num_Unique_Subcategories))))
Show TableShow SQL
select count(*) as Num_Products
, count(p.ProductID) as Num_Product_IDs
, count(p.ProductSubcategoryID) as Num_Products_With_Subcategories
, count(distinct p.ProductSubcategoryID) as Num_Unique_Subcategories
from Product p
Num_Products
Num_Product_IDs
Num_Products_With_Subcategories
Num_Unique_Subcategories
504
504
295
37
6.4.3 Misc
Produces true when the given argument has any rows, false otherwise. The following where clause filters out Products which do not have any corresponding rows in the SalesOrderDetail table.
> (aw:show-table (from prd Product (limit 5) (select (ProductName prd)) (select (ProductNumber prd)) (where (exists (from dtl SalesOrderDetail (where (.= (ProductID dtl) (ProductID prd))))))))
Show TableShow SQL
select prd.Name as ProductName
, prd.ProductNumber as ProductNumber
from Product prd
where exists (
select dtl.*
from SalesOrderDetail dtl
where (dtl.ProductID = prd.ProductID)
)
limit 5
ProductName
ProductNumber
Sport-100 Helmet, Red
HL-U509-R
Sport-100 Helmet, Black
HL-U509
Mountain Bike Socks, M
SO-B909-M
Mountain Bike Socks, L
SO-B909-L
Sport-100 Helmet, Blue
HL-U509-B
Represents a subquery.
This strict variant is really only useful for blocking the appending behavior of from. That is, if q is a query?, then (from x q ....) will append more clauses to q. If you want to treat q as a subquery instead, you need to wrap it: (from x (subquery q) ....)
The unsafe variant, %%subquery, is probably more useful. A common pattern is to create a subquery with exactly one row, as follows:
> (aw:show-table (from x (%%subquery "select 1 as one") (select (val "hello")) (select (val "world"))))
Show TableShow SQL
select 'hello'
, 'world'
from (select 1 as one) x
'hello'
'world'
hello
world
procedure
coalesce :
(token-constructor [String? String? ...+ -> String?] [Datetime? Datetime? ...+ -> Datetime?] [Number? Number? ...+ -> Number?])
Produces the first non-dbnull value from the given arguments. If the nullability of any argument is no, then the nullability of the returned token is also no.
The following example uses coalesce to define maxDiscount as "the maximum DiscountPct from the group of Special Offers, or zero when there are no Special Offers (and the maximum is dbnull)."
> (aw:show-table (from p Product (%%where (ProductID p)" in (514, 680, 707, 725)") (select (ProductName p)) (select (ListPrice p)) (join offersG (SpecialOffersG p)) (define maxDiscount (coalesce (max (DiscountPct offersG)) (val 0))) (select (>> maxDiscount #:as 'BestDiscount)) (select (>> (.* (ListPrice p) (.- (val 1) maxDiscount)) #:as 'BestPrice))))
Show TableShow SQL
select p.Name as ProductName
, p.ListPrice as ListPrice
, coalesce(offer.__INJECT1, 0) as BestDiscount
, (p.ListPrice * (1 - coalesce(offer.__INJECT1, 0))) as BestPrice
from Product p
left join (
select sop.ProductID as __INJECT0
, max(offer.DiscountPct) as __INJECT1
from SpecialOffer offer
inner join SpecialOfferProduct sop
on (sop.SpecialOfferID = offer.SpecialOfferID)
group by sop.ProductID
) offer
on (offer.__INJECT0 = p.ProductID)
where p.ProductID in (514, 680, 707, 725)
ProductName
ListPrice
BestDiscount
BestPrice
LL Mountain Seat Assembly
133.34
0
133.34
HL Road Frame - Black, 58
1431.5
0.0
1431.5
Sport-100 Helmet, Red
34.99
0.15
29.741500000000002
LL Road Frame - Red, 44
337.22
0.35
219.193
If you do not use coalesce, dbnull will propogate through the expressions built using maxDiscount:
> (aw:show-table (from p Product (%%where (ProductID p)" in (514, 680, 707, 725)") (select (ProductName p)) (select (ListPrice p)) (join offersG (SpecialOffersG p)) (define maxDiscount (max (DiscountPct offersG))) (select (>> maxDiscount #:as 'BestDiscount)) (select (>> (.* (ListPrice p) (.- (val 1) maxDiscount)) #:as 'BestPrice))))
Show TableShow SQL
select p.Name as ProductName
, p.ListPrice as ListPrice
, offer.__INJECT1 as BestDiscount
, (p.ListPrice * (1 - offer.__INJECT2)) as BestPrice
from Product p
left join (
select sop.ProductID as __INJECT0
, max(offer.DiscountPct) as __INJECT1
, max(offer.DiscountPct) as __INJECT2
from SpecialOffer offer
inner join SpecialOfferProduct sop
on (sop.SpecialOfferID = offer.SpecialOfferID)
group by sop.ProductID
) offer
on (offer.__INJECT0 = p.ProductID)
where p.ProductID in (514, 680, 707, 725)
ProductName
ListPrice
BestDiscount
BestPrice
LL Mountain Seat Assembly
133.34
#<sql-null>
#<sql-null>
HL Road Frame - Black, 58
1431.5
0.0
1431.5
Sport-100 Helmet, Red
34.99
0.15
29.741500000000002
LL Road Frame - Red, 44
337.22
0.35
219.193
The first argument is a Number? to be rounded. The second argument specifies how many decimal digits should be retained; this defaults to zero.
> (aw:show-table (from x (%%subquery "select 1 as one") (select (round (val 12.3456))) (select (round (val 12.3456) 2))))
Show TableShow SQL
select round(12.3456, 0)
, round(12.3456, 2)
from (select 1 as one) x
round(12.3456, 0)
round(12.3456, 2)
12.0
12.35
6.4.4 Date Math
Adds each interval to the given datetime. The following example shows that the order of the intervals matters:
> (aw:show-table (from x (%%subquery "select 1 as one") (define feb-01 (val "2019-02-01" Datetime?)) (select (date+ feb-01 (months 1) (days 29))) (select (date+ feb-01 (days 29) (months 1)))))
Show TableShow SQL
select datetime('2019-02-01', '+1 month', '+29 day')
, datetime('2019-02-01', '+29 day', '+1 month')
from (select 1 as one) x
datetime('2019-02-01', '+1 month', '+29 day')
datetime('2019-02-01', '+29 day', '+1 month')
2019-03-30 00:00:00
2019-04-02 00:00:00
For this reason, Plisqin provides no way to add intervals to each other. One possible implementation would preserve the sequence of intervals; another would eagerly combine them into one interval.
Note also that intervals may be dynamic, as in the following query:
> (aw:show-table (from pc ProductCategory (select (ProductCategoryID pc)) (select (ModifiedDate pc)) (select (>> (date+ (ModifiedDate pc) (years (ProductCategoryID pc))) #:as 'AddSomeYears))))
Show TableShow SQL
select pc.ProductCategoryID as ProductCategoryID
, pc.ModifiedDate as ModifiedDate
, datetime(pc.ModifiedDate, +(pc.ProductCategoryID) || ' year') as AddSomeYears
from ProductCategory pc
ProductCategoryID
ModifiedDate
AddSomeYears
1
2008-04-30 00:00:00
2009-04-30 00:00:00
2
2008-04-30 00:00:00
2010-04-30 00:00:00
3
2008-04-30 00:00:00
2011-04-30 00:00:00
4
2008-04-30 00:00:00
2012-04-30 00:00:00
Equivalent to calling date+ with each interval negated.
6.4.5 Operators
(require plisqin-lib/strict/operators) | package: plisqin |
Boolean "and" – Produces true when all arguments are true. Usually prefixed .and.
Boolean "or" – Produces true when any argument is true. Usually prefixed .or.
Boolean "not" – Produces true when the argument is false. Usually prefixed .not.
procedure
= :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Equals" – Produces true when its arguments are equal. Usually prefixed .=.
procedure
<> :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Does not Equal" – Produces true when its arguments are not equal. Usually prefixed .<>.
procedure
< :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Less Than" – Produces true when the first argument is less than the second. Usually prefixed .<.
procedure
<= :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Less Than or Equal To" – Produces true when the first argument is less than or equal to the second. Usually prefixed .<=.
procedure
> :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Greater Than" – Produces true when the first argument is greater than the second. Usually prefixed .>.
procedure
>= :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
"Greater Than or Equal To" – Produces true when the first argument is greater than or equal to the second. Usually prefixed .>=.
procedure
like :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
Usually prefixed .like. Returns true if the first argument matches the second. The use of % to match zero or more characters is guaranteed to be supported. Other matching patterns may exist; see "like" in your database’s documentation for more details.
> (aw:show-table (from p Product (where (.like (ProductNumber p) (val "CS-%"))) (select (ProductName p)) (select (ProductNumber p))))
Show TableShow SQL
select p.Name as ProductName
, p.ProductNumber as ProductNumber
from Product p
where (p.ProductNumber like 'CS-%')
ProductName
ProductNumber
Chain Stays
CS-2812
LL Crankset
CS-4759
ML Crankset
CS-6583
HL Crankset
CS-9183
procedure
not-like :
(token-constructor [Number? Number? -> Bool?] [String? String? -> Bool?] [Datetime? Datetime? -> Bool?])
Inverse of .like. Returns true if the first argument does not match the second. Usually prefixed .not-like.
procedure
is :
(token-constructor [(or/c 'null Number?) (or/c 'null Number?) -> Bool?] [(or/c 'null String?) (or/c 'null String?) -> Bool?] [(or/c 'null Datetime?) (or/c 'null Datetime?) -> Bool?])
An equality test in which dbnull is considered equal to dbnull. The value 'null can be used as a constant representing dbnull. Assuming that foo and bar are both Scalar?s, the truth table is:Unlike other comparisons, .is ignores any fallbacks because the comparison behavior of dbnull is already completely specified.
> (aw:show-table (from p Product (limit 3) (select (ProductName p)) (select (ProductNumber p)) (select (Color p)) (where (.is (Color p) 'null))))
Show TableShow SQL
select p.Name as ProductName
, p.ProductNumber as ProductNumber
, p.Color as Color
from Product p
where (p.Color is null)
limit 3
ProductName
ProductNumber
Color
Adjustable Race
AR-5381
#<sql-null>
Bearing Ball
BA-8327
#<sql-null>
BB Ball Bearing
BE-2349
#<sql-null>
> (aw:show-table (from p Product (limit 3) (select (ProductName p)) (select (ProductNumber p)) (select (Color p)) (where (.is (Color p) (val "Silver")))))
Show TableShow SQL
select p.Name as ProductName
, p.ProductNumber as ProductNumber
, p.Color as Color
from Product p
where (p.Color is not null and (p.Color = 'Silver'))
limit 3
ProductName
ProductNumber
Color
Chainring Bolts
CB-2903
Silver
Chainring Nut
CN-6137
Silver
Freewheel
FH-2981
Silver
procedure
:
(token-constructor [(or/c 'null Number?) (or/c 'null Number?) -> Bool?] [(or/c 'null String?) (or/c 'null String?) -> Bool?] [(or/c 'null Datetime?) (or/c 'null Datetime?) -> Bool?])
Numeric addition. Usually prefixed .+.
Numeric subtraction. Usually prefixed .-.
Numeric multiplication. Usually prefixed .*
Numeric division. Usually prefixed ./
> (aw:show-table (from pc ProductCategory (define pcid (ProductCategoryID pc)) (select pcid) (select (.+ pcid (val 1))) (select (.- pcid (val 1))) (select (.* pcid (val 10))) (select (./ pcid (val 2)))))
Show TableShow SQL
select pc.ProductCategoryID as ProductCategoryID
, (pc.ProductCategoryID + 1)
, (pc.ProductCategoryID - 1)
, (pc.ProductCategoryID * 10)
, (pc.ProductCategoryID / 2)
from ProductCategory pc
ProductCategoryID
(pc.ProductCategoryID + 1)
(pc.ProductCategoryID - 1)
(pc.ProductCategoryID * 10)
(pc.ProductCategoryID / 2)
1
2
0
10
0
2
3
1
20
1
3
4
2
30
1
4
5
3
40
2