Grails: "where" query with optional associations - sql

I'm trying to run a "where" query to find a domain model object that has no association with another domain model object or if it does, that domain model object has a specific property value. Here's my code:
query = Model.where({
other == null || other.something == value
})
def list = query.list()
However, the resulting list only contains objects that match the second part of the OR statement. It contains no results that match the "other == null" part. My guess is that since it's checking a value in the associated object its forcing it to only check entries that actually have this associated object. If that is the case, how do I go about creating this query and actually having it work correctly?

You have to use a LEFT JOIN in order to look for null associations. By default Grails uses inner join which will not be joined for null results. Using withCriteria as below you should get the expected results:
import org.hibernate.criterion.CriteriaSpecification
def results = Model.withCriteria {
other(CriteriaSpecification.LEFT_JOIN){
or{
isNull 'id'
eq 'something', value
}
}
}
UPDATE
I know aliasing is not possible in DetachedCritieria where one would try to specify the join as in createCriteria/withCriteria. There is an existing defect regarding adding the functionality to DetachedCriteria. Just adding the work around for where query as mentioned in defect.
Model.where {
other {
id == null || something == value
}
}.withPopulatedQuery(null, null){ query ->
query.#criteria.subcriteriaList[0].joinType = CriteriaSpecification.LEFT_JOIN
query.list()
}
I would rather use withCriteria instead of the above hack.

this might work:
query = Model.where({
isNull( other ) || other.something == value
})
If that wouldn't work, try something like:
other.id == null || other.something == value
UPDATE:
or with good'ol criteria query:
list = Pack.withCriteria{
or{
isNull 'other'
other{ eq 'something', value }
}
}

Related

Can Laravel automatically switch between column = ? and column IS NULL depending on value?

When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();

Fix "dynamic" query without throwing me the given error?

UPDATE dbo.Einkauf_Web_Upload
SET
${
updatedUpload.Menge !== null
? `Anzahl = ${`${updatedUpload.Menge}`},`
: null
},
${
updatedUpload.ENummer !== null
? `ENummer = ${`'${updatedUpload.ENummer}'`}`
: null
}
WHERE ...
This query is supposed to differentiate between updated values of the object updatedUpload which, initially, has all of its values set to null. If the value is not altered therefor not updated, the query must not affect the particular column. In its current state, the query throws this error:
Incorrect syntax near the keyword 'null'
And I know why; if you do not alter the Menge attribute, the query looks like this:
UPDATE dbo.Einkauf_Web_Upload
SET null, ENummer = "abc"
WHERE ..
Is there a workaround to this? I am using NodeJs as my backend and thought of trying to make the column references dynamic via a mapped array which contains only the altered columns of updatedUpload.
Will appreciate any help!
SET null, ... is invalid SQL syntax, you should skip the null at all. Furthermore, remember not to do the query if there is nothing to update as SET WHERE ... is also invalid syntax.
I would suggest something like:
let updates = [
updatedUpload.Menge !== null ? `Anzahl = ${`${updatedUpload.Menge}`}` : null,
updatedUpload.ENummer !== null ? `ENummer = ${`'${updatedUpload.ENummer}'`}` : null,
// ... add here
]
// Filter out null updates
updates = updates.filter(u => !!u);
// Do query only if updates are avaliable
if (updates.length > 0) {
const sql = `UPDATE dbo.Einkauf_Web_Upload SET ${updates.join(', ')} WHERE ...`;
// ... execute
}

Linq2DB can't translate a mapped column in Where clause

I'm working with a legacy Oracle database that has a column on a table which stores boolean values as 'Y' or 'N' characters.
I have mapped/converted this column out like so:
MappingSchema.Default.SetConverter<char, bool>(ConvertToBoolean);
MappingSchema.Default.SetConverter<bool, char>(ConvertToChar);
ConvertToBoolean & ConvertToChar are simply functions that map between the types.
Here's the field:
private char hasDog;
[Column("HAS_DOG")]
public bool HasDog
{
get => ConvertToBoolean(hasDog);
set => hasDog = ConvertToChar(value);
}
This has worked well for simply retrieving data, however, it seems the translation of the following:
var humanQuery = (from human in database.Humans
join vetVisit in database.VetVisits on human.Identifier equals vetVisit.Identifier
select new HumanModel(
human.Identifier
human.Name,
human.HasDog,
vetVisit.Date,
vetVisit.Year,
vetVisit.PaymentDue
));
// humanQuery is filtered by year here
var query = from vetVisits in database.VetVisits
select new VetPaymentModel(
(humanQuery).First().Year,
(humanQuery).Where(q => q.HasDog).Sum(q => q.PaymentDue), -- These 2 lines aren't correctly translated to Y/N
(humanQuery).Where(q => !q.HasDog).Sum(q => q.PaymentDue)
);
As pointed out above, the .Where clause here doesn't translate the boolean comparison of HasDog being true/false to the relevant Y/N values, but instead a 0/1 and results in the error
ORA-01722: invalid number
Is there any way to handle this case? I'd like the generated SQL to check that HAS_DOG = 'Y' for instance with the specified Where clause :)
Notes
I'm not using EntityFramework here, the application module that this query exists in doesn't use EF/EFCore
You can define new mapping schema for your particular DataConnection:
var ms = new MappingSchema();
builder = ms.GetFluentMappingBuilder();
builder.Entity<Human>()
.Property(e => e.HasDog)
.HasConversion(v => v ? 'Y' : 'N', p => p == 'Y');
Create this schema ONCE and use when creating DataConnection

Laravel Eloquent - where relationship field does not equal

I thought this would be fairly simple but it's not playing ball currently.
I have 2 tables for this question, 'applications' & 'application_call_logs'.
This query needs to return all from the applications table where the latest call log doesn't have a status of X.
Here's the current query:
$query = Application::query();
$query->where(function($query) {
$query->whereDoesntHave('call_logs');
$query->orWhereHas('latest_call_log', function($q) {
$q->where('status', '!=', 'not interested');
});
});
return $query->get();
This should return all rows that either have no call logs, or where the latest call log doesn't have the status field equaling a specific string.
This here:
$q->where('status', '!=', 'not interested');
Seems to have no affect if the call_logs has more than 1 row, even though I'm querying the latest relationship. I've also verified the latest is returning the correct latest record.
The two relationships in the Application model are:
public function call_logs()
{
return $this->hasMany('App\ApplicationCallLog', 'lead_id', 'id');
}
public function latest_call_log()
{
return $this->hasOne('App\ApplicationCallLog', 'lead_id', 'id')->latest();
}
Checked the SQL generated:
select * from `applications` where (not exists (select * from `lead_call_logs` where `applications`.`id` = `lead_call_logs`.`lead_id`) or exists (select * from `lead_call_logs` where `applications`.`id` = `lead_call_logs`.`lead_id` and `status` != ?))
there is a solution around should be good for this situation:
i think that this line has the week point of the code:
return $this->hasOne('App\ApplicationCallLog', 'lead_id', 'id')->latest();
this should be hasMany, but you use hasOne to limit the result to one.
and if you tried:
return $this->hasMany('App\ApplicationCallLog', 'lead_id', 'id')->latest()->limit(1);
it simply won't work, because the result will be limited to ApplicationCallLog for all of the results ....
will, there is a package staudenmeir/eloquent-eager-limit that is made especially for this situations:
composer require staudenmeir/eloquent-eager-limit:"^1.0"
class Application extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function latest_call_log()
{
return $this->hasMany('App\ApplicationCallLog', 'lead_id', 'id')->latest()
->limit(1);
}
}
class ApplicationCallLog extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
using this package will limit ApplicationCallLog for every result in your query not one for all of the result, and that will have the same effect for hasOne ....
with this minor enhancement, i think:
$q->where('status', '!=', 'not interested');
will work ...
more about eloquent-eager-limit package in:
https://github.com/staudenmeir/eloquent-eager-limit

In typescript, how to sort exactly like MSSQL's 'order by' clause for special characters like * , + - etc

I have a Angular5 based front-end which needs to show a list of persons. A similar list is shown at the back-end application. Both lists need to be sorted exactly the same. The back-end list is fetched via a query in MSSQL DB which uses an 'order by' clause like this :
select * from PERSON where GROUP=1 order by PERSON.LAST_NAME ASC, PERSON.INITIALS ASC, PERSON.PREFIX ASC, PERSON.FIRSTNAME ASC
On the frontend, my person model looks like :
export interface Person {
id?: number;
fullName?: string;
}
The fullName here is as per this format : [LastName, Initials Prefix FirstName] eg : Adams, Mr P John.
I am using this method to sort it :
public sortByName(aPersonArr: Person[]) {
aPersonArr.sort((p1, p2) => {
let name1 = p1.fullName;
let name2 = p2.fullName;
// If both names are blank, consider them equal, If one name is blank, place it at the last
if (!name1 && !name2) {
return 0;
} else if (name1 && !name2) {
return -1;
} else if (name2 && !name1) {
return 1;
}
name1 = name1.toLowerCase();
name2 = name2.toLowerCase();
if (name1 < name2) {
return -1;
}
if (name1 > name2) {
return 1;
}
return 0;
});
}
}
But the problem occurs when a person's fullName begins with a special character because SQL Query fetches the names in this order : (Showing only the lastNames here)
*Account
,Adams
.Alkin
+Account
-Adams
Whereas my typescript code sorts them like this : (Showing only the lastNames here)
*Account
+Account
,Adams
-Adams
.Alkin
The reason that I need to have this logic at frontend is that many a times my person list is dynamically prepared and needs to be sorted right away. Is there a way to know what exact logic is used by SQL query to compare strings, so that i can use the same in my sorting method.