Order hql results by specific property value - grails

I have a domain that contains a property called 'Status'.
This property can contain 'A','I','P','Pv','R'.
I have the following query:
def list = Deal.findAll('from Deal as d')
How can I order the results so that rows with status of 'P' are always returned at the top of the result set? (I don't care in what order they come after that).

Will this do what you want? Normally I'd test it before answering but I don't have an easy way to do that atm.
def list = Deal.findAll('''from Deal as d order by case d.property when 'P' then 0 else 1 end''')

You can use the sort method with a comparator:
def list = Deal.findAll('from Deal as d').sort({a,b-> (a.status== 'P' && b.status != 'P') ? 0 : 1 })

Related

Ruby - Return record with longest string in column

I just need the record with the longest string in the product_description column.
A record could have nil in the product_description column
Which is why this won't work:
Product.where(parent_product_id: 22033).pluck(:product_description).max_by(&:length)
Then I try SQL and get:
ActiveRecord::UnknownAttributeReference (Query method called with non-attribute argument(s): "max(length(product_description))")
From this query:
Product.where(parent_product_id: 22033).pluck("max(length(product_description))")
This returns the same:
Product.where(parent_product_id: 22033).order("MAX(CHAR_LENGTH(product_description)) desc").limit(1)
But product_description is definitely a column on the Products table.. that's not the issue
You can use the length function of your RDBMS to calculate the length, then order by it.
Ties
There might be many products with the same description length. In order to have consistent results, you will need a tie breaker as otherwise the order within the products with same description length is not defined. You could add an order by id clause.
NULL
Be aware that
select length(null)
will return null and not 0.
null might be sorted before actual values or after (depending on your RDBMS and its config).
If you always need a numeric value you can do
select length(coalesce(null, ''))
which will return 0.
coalesce returns the first non-null argument and therefore ensures that we always pass at least an empty string to length.
You can also use the null last option for the order clause.
You can also exclude records with a null value for the description:
products = Product.where.not(product_description: nil)
to avoid dealing with null values altogether.
If the column is not nullable, then there is no problem either.
Now if you just use this:
products = Product.all # or whatever conditions you need
products
.order("length(coalesce(product_description, '')) desc")
.order(id: :asc)
.first
then Rails might complain (depends on the Version you are using) for security reasons with something like ActiveRecord::UnknownAttributeReference: Dangerous query method
which means you need to wrap the whole thing in Arel.sql
products = Product.all # or whatever conditions you need
products
.order(Arel.sql("length(coalesce(product_description, '')) desc"))
.order(id: :asc)
.first
Index
If you have many records, you might want to add an index on the column length. See https://sqlfordevs.com/function-based-index for how to create a function based index.
You can order by length and take first like this
Product
.where(parent_product_id: 22033)
.where.not(product_description: nil)
.order("LENGTH(product_description) DESC")
.first
LENGTH is RDBMS function and depends on specific system and may differ therefore

Change search query after adding a new column to table rails

Right now I use this search to find items for certain category in my database
#items=Item.where(:category_id => #active_category_id).order(:price)
But recently I added a column to Item table called detail. It can be 0 or 1 and it is integer or it can be empty cause I added it just now and I had already some items in my db.
So now I need two searches: I need search that returns items with detail=1
And where detail is not 1.
So I do it like this:
#for items with detail = 1
#items=Item.where(:category_id => #active_category_id)
.where(:detail=> 1).order(:price)
It is working.
But now I need to find items with detail != 1
So I write
#items=Item.where(:category_id => #active_category_id)
.where.not(:detail=> 1).order(:price)
And it is not working. What do I do?
NULL values will not match an equality or inequality. You have to explicity compare to NULL. Try Item.where(detail: nil).
If you need 0 OR NULL you might need to write raw SQL: Item.where("detail = 0 OR detail IS NULL")
You might also consider backfilling your db to eliminate the NULL values, then you can just compare with 1 and 0.

Activerecord specifications with 2 different models

I need to find a way to display all Vacancies from my Vacancy model except the ones that a user already applied for.
I keep the IDs of the vacancies a certain user applied for in a seperate model AppliedVacancies.
I was thinking something line the lines of:
#applied = AppliedVacancies.where(employee_id: current_employee)
#appliedvacancies_id = []
#applied.each do |appliedvacancy|
#appliedvacancies_id << appliedvacancy.id
end
#notyetappliedvacancies = Vacancy.where("id != ?", #appliedvacancy_id)
But it does not seem to like getting an array of IDs. How would I go about fixing this?
I get following error:
PG::DatatypeMismatch: ERROR: argument of WHERE must be type boolean, not type record
LINE 1: SELECT "vacancies".* FROM "vacancies" WHERE (id != 13,14)
^
: SELECT "vacancies".* FROM "vacancies" WHERE (id != 13,14)
This is purely an SQL problem.
You cannot use != to compare a value to a set of values. You need to use the IN operator.
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)", #appliedvacancy_id)
As an aside, you can drastically improve the code you've written so far. You are needlessly instantiating complete ActiveRecord models for every record found in your applied_vacancies table, when all you need are the IDs.
A first pass at improvement would be to use pluck to skip the entire process and go straight to the list of IDs:
ids = AppliedVacancies.where(employee_id: current_employee).pluck(:id)
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)", ids)
Next, you can go a step further and eliminate the first query all together (or rather, combine it with the last query as a sub-query) by leaving it as an AREL projection which can be subbed into the second query directly:
ids = AppliedVacancies.select(:id).where(employee_id: current_employee)
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)",App)
This will generate a single query:
select * from vacancies where id not in (select id from applied_vacancies where employee_id = <value>)
Answer like #meagar, but Rails 4 way:
#notyetappliedvacancies = Vacancy.where.not(id: #appliedvacancy_id)

SET in combination with CASE statement in cypher

I am tryin to set two different relationship properties to a count, with a case construct depending on the value of another relationship property. There is a console at http://console.neo4j.org/?id=rt1ld5
the cnt column contains the number of times r.value occurs. The two first rows of the initial query in the console indicate that the term "Car" is linked to 1 document that is considered relevant, and to two documents that are considered not relevant.
I want to SET a property on the [:INTEREST] relation between (user) and (term) with two properties, indicating how many times an interest is linked to a document that is considered relevant or not. So for (John)-[r:INTEREST]->(Car) I want r.poscnt=1 and r.negcnt=2
I.m struggling with the CASE construct. I tried various ways, this was the closest I got.
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<- [r:RELEVANCE]-(u)
WITH int, t.name, r.value, count(*) AS cnt
CASE
WHEN r.value=1 THEN SET int.poscnt=cnt
WHEN r.value=-1 THEN SET int.negcnt=cnt
END
But it's returning an error
Error: Invalid input 'A': expected 'r/R' (line 3, column 2)
"CASE"
^
This did it! Also see console at http://console.neo4j.org/?id=rq2i7j
MATCH (u:user)-[int:INTEREST]->(t:term)<-[:ISABOUT]-(d:doc)<-[r:RELEVANCE]-(u)
WITH int, t,
SUM(CASE WHEN r.value= 1 THEN 1 ELSE 0 END ) AS poscnt,
SUM(CASE WHEN r.value= -1 THEN 1 ELSE 0 END ) AS negcnt
SET int.pos=poscnt,int.neg=negcnt
RETURN t.name,int.pos,int.neg
Is it important for you to keep positive and negative count separate? It seems you could have a score property summing positive and negative values.
MATCH (u:user)-[int:INTEREST]->()<-[:ISABOUT]-()<-[r:RELEVANCE]-(u)
SET int.score = SUM(r.value)
RETURN t.name, int.score
You already seem to have found a working solution but I'll add a note about CASE as I understand it. While CASE provides branching, I think it's correct to say that it is an expression and not a statement. It resembles a ternary operator more than a conditional statement.
As the expression
a > b ? x : y;
is resolved to a value, either x or y, that can be used in a statement, so also
CASE WHEN a > b THEN x ELSE y END
resolves to a value. You can then assign this value
result = CASE WHEN a > b THEN x ELSE y END
Your original query used the CASE expression like a conditional statement
CASE WHEN a > b THEN result = x ELSE result = y END
which resembles if-else
if a > b { result = x; } else { result = y; }
Someone may want to correct the terminology, the point is that in your working query you correctly let CASE resolve to a value to be used by SUM rather than put a conditional assignment inside CASE.

ternary operator/default value in neo4j cypher

I need to implement something of a ternary operator that can help me return some default values from cypher query itself.
Scenario is -
if an employee's city is Delhi, return 5 else return 10
Something like a ternary operator.
start employee = node(5)
return employee.city == 'DELHI' ? 5 : 10 as val;
I tried things like
start employee = node(5)
return coalesce (employee.city == 'DELHI', 5)
but no luck.
Is there a way to implement such a scenario in neo4j be it Cypher or Traversal.
Unfortunately it is not supported out of the box but here is a hack to do it, using filter, head and collection literals.
The idea is to have a two element list and a filter expression that becomes true for the first element for your "true-branch" and alternatively true for the second element in the list whic represents the value of your false-branch.
see this console example: http://console.neo4j.org/r/6tig7g
start n=node(5) return head(filter( a in [5,10] : n.city = 'DELHI' OR a = 10))
so generally:
head(filter( a in [true-result,false-result] : CONDITION OR a = false-result))
I know this is a really old question, but since Google landed me here and I have an answer (Cypher in Neo4J 3.5+):
MATCH (employee:Employee)
RETURN
CASE employee.city WHEN "DELHI" THEN 5 ELSE 10 END AS val

Resources