79761025

Date: 2025-09-10 15:41:06
Score: 1
Natty:
Report link

I know this is super late, but hopefully this helps anyone else needing a workaround for such function. I had a similar requirement. Permit me to tale my requirement for better context and to those who have similar problem. Except that in my case it wasn't a 5th or 30th record. It's expected to be dynamic.

On a stocks market analysis project, each day has a market record, but days aren't sequential there are gaps e.g weekends, public holidays etc. Depending on user's input the program can compute or compare across a dynamic timeline e.g 5 market days = 1 week, 2W, 3W, 52W comparison etc. Calendar isn't reliable here. Since data is tied to trading days, not calendar days. In my case it became expedient to leverage row number.

E.g. if date is 2024-08-05 and row_number 53,505. I can look up 25 market days or 300 records away to compute growth etc.

Back to the Answer.

I used Django's annotate() function with a subquery that leverages PostgreSQL's window function to filter the queryset. The answer q = list(qs) above would suffice in cases where data isn't much. I wanted to avoid materializing a large queryset into a list, which would be inefficient.

PostgreSQL's ROW_NUMBER() window function. The SQL query looked something like this:

SELECT subquery.row_num FROM (SELECT id, ROW_NUMBER() OVER (ORDER BY id ASC) as row_num FROM {table_name}) subquery WHERE subquery.id = {table_name}.id

Here's how I implemented it in my Django workflow:

from django.db.models.expressions import RawSQL

class YourModel
...

@classmethod
    def get_offset_record(cls, record_id, offset):
        """
        Returns X number of market record (days) ago
        """
        table_name = cls._meta.db_table
        qs = (
            cls.objects.all()
            .annotate(
                row_number=RawSQL(
                    f"(SELECT subquery.row_num FROM (SELECT id, ROW_NUMBER() OVER (ORDER BY id ASC) as row_num FROM {table_name}) subquery WHERE subquery.id = {table_name}.id)",
                    [],
                    output_field=models.IntegerField(),
                )
            )
            .order_by("id")
        )

        try:
            current_row = qs.filter(pk=record_id).first()
            target_row_number = current_row.row_number - offset
            return qs.get(row_number=target_row_number)
        except cls.DoesNotExist:
            return None

i'm aware there's a from django.db.models.functions import RowNumber But i find the raw sql easier to use.

I hope this helps someone, Cheers!

Reasons:
  • Blacklisted phrase (1): Cheers
  • Whitelisted phrase (-1): hope this helps
  • Long answer (-1):
  • Has code block (-0.5):
  • Me too answer (2.5): have similar problem
Posted by: Olamigoke Philip