Activerecord Where with a hash value - sql

I have a City model with a cached_info field which is a serialized hash.
{:population=>20000, :more_stuff =>....}
If I make the following query in Activerecord.
City.where('cached_info[:population] > 300').count
I get back...
ActiveRecord::StatementInvalid: Mysql2::Error:
You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near '[:population] > 300)' at line 1: SELECT COUNT(*) FROM `places` WHERE `places`.`type` = 'City' AND (cached_info[:population] > 3)
Anybody have a workaround for this?

There's no easy way to query within a serialised Hash via ActiveRecord and SQL, unless you use a LIKE in your query (but this can't do comparisons like > and <).
Based on your use case you should really rethink your data model and normalise these fields into proper models/columns.

As Jits said, LIKE/ILIKE would work, e.g.:
City.where('cached_info LIKE ?', '%name: Louisiana%')

If you're using PostgreSQL with JSONB fields to store your hashes, you can drop down to SQL in the where and use something like this:
City.where("cast(cached_info ->> 'population' as int) > ?", 300)
The ->> is a Postgres JSONB access operator -- check out this article.

Related

How to annotate list and use this field in filter? Django ORM (got ProgrammingError)

I am annotate array:
queryset = queryset.annotate(
owner_names=ArrayAgg(
Case(
When(
debtor__estateobjectcharacteristics__owner_name__isnull=False,
then='debtor__estateobjectcharacteristics__owner_name',
)
)
)
)
and in field owner_names contains something like ['Name1', 'Name2'] and that what im actually need.
But after this i try to filter:
queryset.filter(
Exists(
DebtorTenantProfile.objects.filter(
debtor_id=OuterRef('debtor_id'), full_name__in=F('owner_names')
)
)
)
and i get error ProgrammingError: syntax error at or near "ARRAY_AGG"
i was tried to add output_field=ArrayField(CharField(max_length=255, blank=True, null=True)) (just in case imao) but i have the same error.
Is this possible to make this queries work? because this is exactly what i need.
Or maybe is there another way to annotate list of names and use this in filter?
I would really appreciate for any suggestions or solutions ;)
DB: PostgreSQL

Check if value exists exactly like that with SQL query (regexp)?

Example json in the socialMedia database column
[{"id":"1463dae5-1168-432e-8e55-c61820d69c49","value":"person2"},
{"id":"c61820d69c49-8e55-432e-8e55-8e55","value":"person1"}]
I want to run a query to check if "value":"person1" or "value:"person2" or something else like that exists in the json field. Is that possible to do with regexp or something?
Using JSON_SEARCH
App\Models\User::whereRaw('JSON_SEARCH(users.name, "all", "%person%")')->get();
Or using JSON_EXTRACT
App\Models\User::where(DB::raw('JSON_EXTRACT(`name`, "$.*")'), 'LIKE', '%person%')->get();

? in ActiveRecord select

In a where statement, you can use variables like:
Order.where('employee_id = ?', params[:employee_id])
I'm trying to accomplish something similar with a select, but it's not working:
Order.select('amount FROM line_items WHERE employee_id = ? AS employee_line_items', params[:employee_id])
=> ERROR: syntax error at or near "1"
=> LINE 1: ...ployee_id" = ? AS employee_line_items, 1
What's going on here? Is it possible to use ? in select statement? If not, how can you insert an escaped sql string here? I'd like to just use #{params[:employee_id]}, but this bit of code would be vulnerable to sql injection.
You have to split your query and chain it:
Order.select('amount FROM line_items').where(['WHERE employee_id = ?', params[:employee_id]])
And also based on this question, I believe you cannot use AS in WHERE clause, only when selecting fields (and you can't use them in WHERE in any case)
Check the documentation to understand how select works on ActiveRecord models

Need help converting SQL query to Ruby.

I'm new to Ruby on Rails. I'm trying to determine the proper ruby query for the following SQL query.
Select max(bid_amount) from biddings where listing_id = 1;
I need to extract the maximum value in the bid_amount column. But it has to have a dynamic listing_id.
Try:
Bidding.where('listing_id = :listing_id', listing_id: 1).maximum(:bid_amount)
Update:
To follow up on your comment: since you say you are passing in params[:id], it's best to convert that parameter to integer so that unwanted values don't go to the database. For e.g.
Bidding.where('listing_id = :listing_id', listing_id: params[:id].to_i).maximum(:bid_amount)

Django select only rows with duplicate field values

suppose we have a model in django defined as follows:
class Literal:
name = models.CharField(...)
...
Name field is not unique, and thus can have duplicate values. I need to accomplish the following task:
Select all rows from the model that have at least one duplicate value of the name field.
I know how to do it using plain SQL (may be not the best solution):
select * from literal where name IN (
select name from literal group by name having count((name)) > 1
);
So, is it possible to select this using django ORM? Or better SQL solution?
Try:
from django.db.models import Count
Literal.objects.values('name')
.annotate(Count('id'))
.order_by()
.filter(id__count__gt=1)
This is as close as you can get with Django. The problem is that this will return a ValuesQuerySet with only name and count. However, you can then use this to construct a regular QuerySet by feeding it back into another query:
dupes = Literal.objects.values('name')
.annotate(Count('id'))
.order_by()
.filter(id__count__gt=1)
Literal.objects.filter(name__in=[item['name'] for item in dupes])
This was rejected as an edit. So here it is as a better answer
dups = (
Literal.objects.values('name')
.annotate(count=Count('id'))
.values('name')
.order_by()
.filter(count__gt=1)
)
This will return a ValuesQuerySet with all of the duplicate names. However, you can then use this to construct a regular QuerySet by feeding it back into another query. The django ORM is smart enough to combine these into a single query:
Literal.objects.filter(name__in=dups)
The extra call to .values('name') after the annotate call looks a little strange. Without this, the subquery fails. The extra values tricks the ORM into only selecting the name column for the subquery.
try using aggregation
Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)
In case you use PostgreSQL, you can do something like this:
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Func, Value
duplicate_ids = (Literal.objects.values('name')
.annotate(ids=ArrayAgg('id'))
.annotate(c=Func('ids', Value(1), function='array_length'))
.filter(c__gt=1)
.annotate(ids=Func('ids', function='unnest'))
.values_list('ids', flat=True))
It results in this rather simple SQL query:
SELECT unnest(ARRAY_AGG("app_literal"."id")) AS "ids"
FROM "app_literal"
GROUP BY "app_literal"."name"
HAVING array_length(ARRAY_AGG("app_literal"."id"), 1) > 1
Ok, so for some reason none of the above worked for, it always returned <MultilingualQuerySet []>. I use the following, much easier to understand but not so elegant solution:
dupes = []
uniques = []
dupes_query = MyModel.objects.values_list('field', flat=True)
for dupe in set(dupes_query):
if not dupe in uniques:
uniques.append(dupe)
else:
dupes.append(dupe)
print(set(dupes))
If you want to result only names list but not objects, you can use the following query
repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')