I'm trying to conditionally add a part to my SQL query using Exposed's DAO API. My goal is to have:
SELECT * FROM table
WHERE column1 = 1
AND column2 = $value
AND column3 = 3
where the existence of the AND column2 = $value part depends on a filter.
I've tried with:
TableDAO.find {
Table.column1 eq 1 and (
when (filter.value) {
null -> null // Type mismatch. Required: Expression<Boolean>. Found: Op<Boolean>?
else -> Table.column2 eq filter.value
}) and (
Table.column3 = 3
)
}.map { it.toModel() }
but I can't find a way to return an empty expression or somehow exclude that part from the query. The only solution I can make work is something like
null -> Table.column2 neq -1
but I feel like there should be a better way.
You'll have to assign your expressions into a local variable:
var expr = Table.column1 eq 1
if(filter.value) {
expr = expr and (Table.column2 eq filter.value)
}
expr = expr and (
Table.column3 = 3
)
I don't have my IDE in front of me, but this is the general idea. You can try to figure out something clever, but it would make your code unnecessarily complex.
Related
I'm making my first app in Kotlin and there is a lot of syntax I don't know, and I was wondering if there is a better way to check if a list contains at least one non null entry.
For now my solution is:
var atLeastOneValue: Boolean
var i = 0
for (x in list) {
if (x != null) atLeastOneValue = true
else i++
}
if (list.size == i) atLeastOneValue = false
return atLeastOneValue
I'm working with MutableList<String>.
You can use contains function for that:
val hasNull = list.contains(null)
contains can also be called in the operator form, it corresponds to the operator in:
val hasNull = null in list
val hasNoNull = null !in list
I'm trying to use IF THEN style AQL, but the only relevant operator I could find in the AQL documentation was the ternary operator. I tried to add IF THEN syntax to my already working AQL but it gives syntax errors no matter what I try.
LET doc = DOCUMENT('xp/a-b')
LET now = DATE_NOW()
doc == null || now - doc.last >= 45e3 ?
LET mult = (doc == null || now - doc.last >= 6e5 ? 1 : doc.multiplier)
LET gained = FLOOR((RAND() * 3 + 3) * mult)
UPSERT {_key: 'a-b'}
INSERT {
amount: gained,
total: gained,
multiplier: 1.1,
last: now
}
UPDATE {
amount: doc.amount + gained,
total: doc.total + gained,
multiplier: (mult < 4 ? FLOOR((mult + 0.1) * 10) / 10 : 4),
last: now
}
IN xp
RETURN NEW
:
RETURN null
Gives the following error message:
stacktrace: ArangoError: AQL: syntax error, unexpected identifier near 'doc == null || now - doc.last >=...' at position 1:51 (while parsing)
The ternary operator can not be used like an if/else construct in the way to tried. It is for conditional (sub-)expressions like you use to calculate mult. It can not stand by itself, there is nothing it can be returned or assigned to if you write it like an if-expression.
Moreover, it would require braces, but the actual problem is that the body contains operations like LET, UPSERT and RETURN. These are language constructs which can not be used inside of expressions.
If I understand correctly, you want to:
insert a new document if no document with key a-b exists yet in collection xb
if it does exist, then update it, but only if the last update was 45 seconds or longer ago
Does the following query work for you?
FOR id IN [ 'xp/a-b' ]
LET doc = DOCUMENT(id)
LET key = PARSE_IDENTIFIER(id).key
LET now = DATE_NOW()
FILTER doc == null || now - doc.last >= 45e3
LET mult = (doc == null || now - doc.last >= 6e5 ? 1 : doc.multiplier)
LET gained = FLOOR((RAND() * 3 + 3) * mult)
UPSERT { _key: key }
INSERT {
_key: key,
amount: gained,
total: gained,
multiplier: 1.1,
last: now
}
UPDATE {
amount: doc.amount + gained,
total: doc.total + gained,
multiplier: (mult < 4 ? FLOOR((mult + 0.1) * 10) / 10 : 4),
last: now
}
IN xp
RETURN NEW
I added _key to INSERT, otherwise the document will get an auto-generated key, which does not seem intended. Using a FOR loop and a FILTER acts like an IF construct (without ELSE). Because this is a data modification query, it is not necessary to explicitly RETURN anything and in your original query you RETURN null for the ELSE case anyway. While yours would result in [ null ], mine produces [ ] (truly empty result) if you try execute the query in quick succession and nothing gets updated or inserted.
Note that it is necessary to use PARSE_IDENTIFIER() to get the key from the document ID string and do INSERT { _key: key }. With INSERT { _key: doc._key } you would run into an invalid document key error in the insert case, because if there is no document xp/a-b, DOCUMENT() returns null and doc._key is therefore also null, leading to _key: null - which is invalid.
I am really not sure how to name the title, hence I am going to explain it as best as I can:
val a = b ?: ({
val temp = c.evaluate()
store(temp)
temp // returns temp to the lambda, which will set `a` to `temp` if `b` is null
})()
1: what works, what I currently use
This works fine, but what I ideally want to do is just using the code block and not passing the lambda to a function (({})) and then evaluating it. In my imagination, it would look something like this:
val a = b ?: {
val temp = c.evaluate()
store(temp)
temp // returns temp to the lambda, which will set `a` to `temp` if `b` is null
}
2: what I would like to have
The above does not work. I am actually just looking for a better way to write 1.
You can use the run function:
val a = b ?: run {
val temp = c.evaluate()
store(temp)
temp
}
You can use the also function:
val a = b ?: c.evaluate().also { store(it) }
This allows you to do something with a value while passing that value as the result of the also
"first part" &&&& fun _ ->
let ident
"second part" &&&& fun _ ->
ident ....
I need to use variable "ident".
I just need to pass value of variable from first part of test to second one...
I want to ask you if there is any easy way how to define and use global variable or even if you have better (and easy) idea of doing that
Keep in mind, please, that I am a beginner, so I would prefer easier ones.
Global variables will often make your code difficult to work with - particularly if they are mutable.
Instead, consider returning the values you need to keep track of as composite values. An easy data type to start with would be a tuple:
let ``first part`` id =
let someOtherValue = "Foo"
someOtherValue, id + 1
This function takes an int (the current ID) as input, and returns string * int (a tuple where the first element is a string, and the second element and int) as output.
You can call it like this:
> let other, newId = ``first part`` 42;;
val other : string = "Foo"
val newId : int = 43
Notice that you can use pattern matching to immediately destructure the values into two named symbols: other and newId.
Your second function could also take an ID as input:
let ``second part`` id otherArgument =
// use id here, if you need it
"Bar"
You can call it like this, with the newId value from above:
> let result = ``second part`` newId "Baz";;
val result : string = "Bar"
If you find yourself doing this a lot, you can define a record for the purpose:
type Identifiable<'a> = { Id : int; Value : 'a }
Now you can begin to define higher-order functions to deal with such a type, such as e.g. a map function:
module Identifiable =
let map f x = { Id = x.Id; Value = f x.Value }
// Other functions go here...
This is a function that maps the Value of an Identifiable from one value to another, but preserves the identity.
Here's a simple example of using it:
> let original = { Id = 42; Value = "1337" };;
val original : Identifiable<string> = {Id = 42;
Value = "1337";}
> let result' = original |> Identifiable.map System.Int32.Parse;;
val result' : Identifiable<int> = {Id = 42;
Value = 1337;}
As you can see, it preserves the value 42, but changes the Value from a string to an int.
You can still change the ID explicitly, if you want to do that:
> let result'' = { result' with Id = 7 };;
val result'' : Identifiable<int> = {Id = 7;
Value = 1337;}
Since this was getting out of hand for comments this is how I would do it for an example
let mutable t = 0
let first =
t <- 1 + 1
//other stuff
let second =
//can use t here and it will have a value of 2
In some cases you have to use a ref:
let t = ref 0
let first =
t := 1 + 1
//other stuff
let second =
//can use t here and it will have a value of 2 -
// you use "!t" to get the value
If you define ident at the top of your file like this :
let ident = "foo"
// rest of your code using ident
ident are global and you can use in the next part of your file.
EDIT :
If ident wil change in the next part of your code, use this :
let ident = ref "foo"
What is the best way to have a select query which has an argument which can be NULL depending on some variable in the program?
I can think of two solutions (pseudocode):
bool valueIsNull;
int value;
query = "SELECT * FROM table WHERE field ";
if (valueIsNull)
{
query += "IS NULL";
}
else
{
query += "= ?";
}
statement st = sql.prepare(query);
if (!valueIsNull)
{
st.bind(0, value);
}
or
bool valueIsNull;
int value;
query = "SELECT * FROM table WHERE field = ? OR (? IS NULL AND field IS NULL)";
statement st = sql.prepare(query);
if (valueIsNull)
{
st.bindNull(0);
st.bindNull(1);
}
else
{
st.bind(0, value);
st.bind(1, value);
}
It is a lot of code for just a simple SELECT statement and I find it just ugly and unclear.
The cleanest way would be something like:
bool valueIsNull;
int value;
query = "SELECT * FROM table WHERE field = ?"; // <-- this does not work
statement st = sql.prepare(query);
st.bind(0, value, valueIsNull); // <-- this works
Obviously this does not work. But is there a clean way to handle this?
I do not think it matter much but I am using C++, cppdb and postgresql.
With Postgresql (but I believe not standard) you can use
SELECT * from some_table where field IS NOT DISTINCT FROM ?;
IS NOT DISTINCT FROM, unlike plain =, is true when both sides are NULL.
As you've noted, the main problem with this:
SELECT * FROM table WHERE field = ? OR (? IS NULL AND field IS NULL)
is that it is actually two parameters which need to be bound.
You can get around that with a (should be fairly portable) construct like:
SELECT *
FROM table
INNER JOIN (SELECT ? AS param1 /* FROM DUAL */) AS params
ON 1 = 1
WHERE field = param1
OR COALESCE(param1, field) IS NULL