Django postgres constraint EXCLUDE WITH GIST - django

I created a time field as follows:
start_date = models.DateField()
end_date = models.DateField()
When trying to create a constraint on the table with
ALTER TABLE analytics
EXCLUDE USING gist (campaign WITH =, tstzrange(start_date, end_date) WITH &&)
I get an error
ERROR: functions in index expression must be marked IMMUTABLE
Does anyone know how to fix this issue?

You are casting date to timestamp with timezone and that function is not immutable, but rather stable. It is like that because it will not always give the same result for the same argument passed.
I see 2 options here:
1) Change constraint to use daterange (or timestamp without timezone):
EXCLUDE USING gist (campaign WITH =, daterange(start_date, end_date) WITH &&)
2) Change type of those fields in table to timestamptz

Related

Update multiple Postgres timestamps

I am attempting to update timestamps for 100s of objects in Postgres 12 using the following query:
UPDATE foo_bar AS c SET
created_at = c2.created_at
FROM (VALUES
(101, '2021-09-27 14:54:00.0+00'),
(153, '2021-06-02 14:54:00.0+00')
) as c2(id, created_at)
WHERE c.id = c2.id;
Where created_at represents a dateTimeField:
created_at = models.DateTimeField(auto_now_add=True)
I am receiving the following error:
ERROR: column "created_at" is of type timestamp with time zone but expression is of type text
I have tried many variations of the created_at values to no avail. Any idea why this is not working?
Thank you to #a_horse_with_no_name for the answer. I had to cast the values as timestamps with timezones as shown below:
UPDATE foo_bar AS c SET
created_at = c2.created_at
FROM (VALUES
(101, '2021-09-27 14:54:00.0+00::timestamptz'),
(153, '2021-06-02 14:54:00.0+00::timestamptz')
) as c2(id, created_at)
WHERE c.id = c2.id;
Postgres docs explaining how casting works (link):
A cast specifies how to perform a conversion between two data types
By default, a cast can be invoked only by an explicit cast request, that is an explicit CAST(x AS typename) or x::typename construct.
Postgres docs explaining timestamptz (link):
timestamptz is accepted as an abbreviation for timestamp with time zone

Django Filter all objects that contain latest date

I'm performing two queries to obtain all of the latest matches as shown below. Is there a more efficient way to retrieve all match objects that have the same latest date?
class Match(models.Model):
team = models.CharField()
round_date = models.DateField()
date = Match.objects.latest('round_date').round_date
Match.objects.filter(round_date=date)
You can use a Subquery [Django-doc] here:
from django.db.models import Subquery
maximal = Match.objects.order_by('-round_date').values('round_date')[:1]
Match.objects.filter(round_date=Subquery(maximal))
which will perform a single query:
SELECT match.id, match.round_date
FROM match
WHERE match.round_date = (
SELECT U0.round_date
FROM match U0
ORDER BY U0.round_date DESC
LIMIT 1
)
The maximal query is thus - unless you would of course "consume" it - not evaluated, but is used to construct a subquery.
In case the field on which you order can be NULLable, this is not the case here, I advise to use an .order_by(F('round_date').desc(nulls_last=True)).

Get information from a model using unrelated field

I have these two models:
class A(models.Model):
name=models.CharField(max_length=10)
class D(models.Model):
code=models.IntegerField()
the code field can have a number that exists in model A but it cant be related due to other factors. But what I want know is to list items from A whose value is the same with code
items=D.objects.values('code__name')
would work but since they are not related nor can be related, how can I handle that?
You can use Subquery() expressions in Django 1.11 or newer.
from django.db.models import OuterRef, Subquery
code_subquery = A.objects.filter(id=OuterRef('code'))
qs = D.objects.annotate(code_name=Subquery(code_subquery.values('name')))
The output of qs is a queryset of objects D with an added field code_name.
Footnotes:
It is compiled to a very similar SQL (like the Bear Brown's solution with "extra" method, but without disadvantages of his solution, see there):
SELECT app_d.id, app_d.code,
(SELECT U0.name FROM app_a U0 WHERE U0.id = (app_d.code)) AS code_name
FROM app_d
If a dictionary output is required it can be converted by .values() finally. It can work like a left join i.e. if the pseudo related field allows null (code = models.IntegerField(none=True)) then the objects D are not restricted and the output code_name value could be None. A feature of Subquery is that it returns only one field expression must be eventually repeated for another fields. (That is similar to extra(select={...: "SELECT ..."}), but thanks to object syntax it can be more readable customized than an explicit SQL.)
you can use django extra, replace YOUAPP on your real app name
D.objects.extra(select={'a_name': 'select name from YOUAPP_a where id=code'}).values('a_name')
# Replace YOUAPP^^^^^

heroku, postgreSQL, django, comments, tastypie: No operator matches the given name and argument type(s). You might need to add explicit type casts

I have a simple query on django's built in comments model and getting the error below with heroku's postgreSQL database:
DatabaseError: operator does not exist: integer = text LINE 1:
... INNER JOIN "django_comments" ON ("pi ns_pin"."id" = "django_...
^
HINT: No operator matches the given name and argument type(s).
You might need to add explicit type casts.
After googling around it seems this error has been addressed many times before in django, but I'm still getting it (all related issues were closed 3-5 years ago) . I am using django version 1.4 and the latest build of tastypie.
The query is made under orm filters and works perfectly with my development database (sqlite3):
class MyResource(ModelResource):
comments = fields.ToManyField('my.api.api.CmntResource', 'comments', full=True, null=True)
def build_filters(self, filters=None):
if filters is None:
filters = {}
orm_filters = super(MyResource, self).build_filters(filters)
if 'cmnts' in filters:
orm_filters['comments__user__id__exact'] = filters['cmnts']
class CmntResource(ModelResource):
user = fields.ToOneField('my.api.api.UserResource', 'user', full=True)
site_id = fields.CharField(attribute = 'site_id')
content_object = GenericForeignKeyField({
My: MyResource,
}, 'content_object')
username = fields.CharField(attribute = 'user__username', null=True)
user_id = fields.CharField(attribute = 'user__id', null=True)
Anybody have any experience with getting around this error without writing raw SQL?
PostgreSQL is "strongly typed" - that is, every value in every query has a particular type, either defined explicitly (e.g. the type of a column in a table) or implicitly (e.g. the values input into a WHERE clause). All functions and operators, including =, have to be defined as accepting specific types - so, for instance there is an operator for VarChar = VarChar, and a different one for int = int.
In your case, you have a column which is explicitly defined as type int, but you are comparing it against a value which PostgreSQL has interpreted as type text.
SQLite, on the other hand, is "weakly typed" - values are freely treated as being of whatever type best suits the action being performed. So in your dev SQLite database the operation '42' = 42 can be computed just fine, where PostgreSQL would need a specific definition of VarChar = int (or text = int, text being the type for unbounded strings in PostgreSQL).
Now, PostgreSQL will sometimes be helpful and automatically "cast" your values to make the types match a known operator, but more often, as the hint says, you need to do it explicitly. If you were writing the SQL yourself, an explicit type case could look like WHERE id = CAST('42' AS INT) (or WHERE CAST(id AS text) = '42').
Since you're not, you need to ensure that the input you give to the query generator is an actual integer, not just a string which happens to consist of digits. I suspect this is as simple as using fields.IntegerField rather than fields.CharField, but I don't actually know Django, or even Python, so I thought I'd give you the background in the hope you can take it from there.
Building on IMSoP's answer: This is a limitation of django's ORM layer when a Generic foreign key uses a text field for the object_id and the object's id field is not a text field. Django does not want to make any assumptions or cast the object's id as something it's not. I found an excellent article on this http://charlesleifer.com/blog/working-around-django-s-orm-to-do-interesting-things-with-gfks/.
The author of the article, Charles Leifer came up with a very cool solution for query's that are affected by this and will be very useful in dealing with this issue moving forward.
Alternatively, i managed to get my query to work as follows:
if 'cmnts' in filters:
comments = Comment.objects.filter(user__id=filters['cmnts'], content_type__name = 'my', site_id=settings.SITE_ID ).values_list('object_pk', flat=True)
comments = [int(c) for c in comments]
orm_filters['pk__in'] = comments
Originally i was searching for a way to modify the SQL similar to what Charles has done, but it turns out all i had to do was break the query out into two parts and convert the str(id)'s to int(id)'s.
To do not hack you ORM and external software postgres allow you register your own casts and compare operations. Please look example in similar question.

Django annotate and values(): extra field in 'group by' causes unexpected results

I must be missing something obvious, as the behavior is not as expected for this simple requirement. Here is my model class:
class Encounter(models.Model):
activity_type = models.CharField(max_length=2,
choices=(('ip','ip'), ('op','op'), ('ae', 'ae')))
cost = models.DecimalField(max_digits=8, decimal_places=2)
I want to find the total cost for each activity type. My query is:
>>> Encounter.objects.values('activity_type').annotate(Sum('cost'))
Which yields:
>>> [{'cost__sum': Decimal("140.00"), 'activity_type': u'ip'},
{'cost__sum': Decimal("100.00"), 'activity_type': u'op'},
{'cost__sum': Decimal("0.00"), 'activity_type': u'ip'}]
In the result set there are 2 'ip' type encounters. This is because it is not grouped by only activity_type but by activity_type AND cost which does not give the intended result. The generated SQL query for this is:
SELECT "encounter_encounter"."activity_type",
SUM("encounter_encounter"."total_cost") AS "total_cost__sum"
FROM "encounter_encounter"
GROUP BY "encounter_encounter"."activity_type",
"encounter_encounter"."total_cost" <<<< THIS MESSES THINGS
ORDER BY "encounter_encounter"."total_cost" DESC
How can I make this query work as expected (and as implied by the docs if I am not getting it wrong) and make it only do a group by on activity_type?
As #Skirmantas correctly pointed, the problem was related to order_by. Although it is not explicitly stated in the query, the default ordering in the model's Meta class is added to the query, which is then added to the group by clause because SQL requires so.
The solution is either to remove the default ordering or add an empty order_by() to reset ordering:
>>> Encounter.objects.values('activity_type').annotate(Sum('cost')).order_by()