<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>postgres Archives | Mithle.sh</title>
	<atom:link href="https://mithle.sh/tag/postgres/feed/" rel="self" type="application/rss+xml" />
	<link>https://mithle.sh/tag/postgres/</link>
	<description>The Diary of a Full Stack Developer</description>
	<lastBuildDate>Wed, 20 Dec 2023 17:49:40 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.3</generator>

<image>
	<url>https://i0.wp.com/mithle.sh/wp-content/uploads/2023/03/cropped-favicon.png?fit=32%2C32&#038;ssl=1</url>
	<title>postgres Archives | Mithle.sh</title>
	<link>https://mithle.sh/tag/postgres/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">219607879</site>	<item>
		<title>How to use Drizzle ORM with NestJS</title>
		<link>https://mithle.sh/how-to-use-drizzle-orm-with-nestjs/</link>
					<comments>https://mithle.sh/how-to-use-drizzle-orm-with-nestjs/#respond</comments>
		
		<dc:creator><![CDATA[Mithlesh]]></dc:creator>
		<pubDate>Wed, 20 Dec 2023 17:49:39 +0000</pubDate>
				<category><![CDATA[NestJS]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[nestjs]]></category>
		<category><![CDATA[postgres]]></category>
		<guid isPermaLink="false">https://mithle.sh/?p=1414</guid>

					<description><![CDATA[<p>In this tutorial we will learn how to use Drizzle ORM with NestJS to connect with all the major databases and serverless database providers such...</p>
<p>The post <a href="https://mithle.sh/how-to-use-drizzle-orm-with-nestjs/">How to use Drizzle ORM with NestJS</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial we will learn how to use Drizzle ORM with NestJS to connect with all the major databases and serverless database providers such as PlanetScale, Turso.</p>



<p>To set up Drizzle ORM in NestJS with each type of databases, follow these steps:</p>



<h2 class="wp-block-heading">1. <span style="text-decoration: underline;">Install Dependencies</span></h2>



<p>Install <strong>Drizzle ORM</strong>, driver of the database that you wish to use and its NestJS <a href="https://github.com/knaadh/nestjs-drizzle" target="_blank" rel="noreferrer noopener">integration module</a> using your favorite package manager.</p>



<h4 class="wp-block-heading">PlanetScale</h4>



<pre class="wp-block-code language-bash"><code>npm install drizzle-orm
npm install @planetscale/database
npm install @knaadh/nestjs-drizzle-planetscale</code></pre>



<h4 class="wp-block-heading">Turso</h4>



<pre class="wp-block-code language-bash"><code>npm install drizzle-orm
npm install @libsql/client
npm install @knaadh/nestjs-drizzle-turso</code></pre>



<h4 class="wp-block-heading">MySQL2</h4>



<pre class="wp-block-code language-bash"><code>npm install drizzle-orm
npm install mysql2
npm install @knaadh/nestjs-drizzle-mysql2</code></pre>



<h4 class="wp-block-heading">Node-Postgres</h4>



<pre class="wp-block-code language-bash"><code>npm install drizzle-orm
npm install pg
npm install @knaadh/nestjs-drizzle-pg</code></pre>



<h4 class="wp-block-heading">Better-SQLite3</h4>



<pre class="wp-block-code language-bash"><code>npm install drizzle-orm
npm install better-sqlite3
npm install @knaadh/nestjs-drizzle-better-sqlite3</code></pre>



<h2 class="wp-block-heading">2. <span style="text-decoration: underline;">Create SQL Schema File</span></h2>



<p>In Drizzle ORM, you need to define your database schema in TypeScript. You can declare your schema in a single <code>schema.ts</code> file or group them logically in multiple files. The schema declaration includes tables, indexes, constraints, foreign keys, and enums as explained <a href="https://orm.drizzle.team/docs/sql-schema-declaration" target="_blank" rel="noreferrer noopener">here</a>. Here&#8217;s an example of declaring tables in a <code>schema.ts</code> file for each of the database types.</p>



<h4 class="wp-block-heading">MySQL</h4>



<pre class="wp-block-code language-typescript"><code>

import { mysqlTable, serial, varchar } from 'drizzle-orm/mysql-core';

export const books = mysqlTable('Books', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
});

export const authors = mysqlTable('Authors', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
});</code></pre>



<h4 class="wp-block-heading">PostgreSQL</h4>



<pre class="wp-block-code language-typescript"><code>

import { pgTable, serial, varchar } from 'drizzle-orm/pg-core';

export const books = pgTable('Books', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
});

export const authors = pgTable('Authors', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
});</code></pre>



<h4 class="wp-block-heading">SQLite</h4>



<pre class="wp-block-code language-typescript"><code>

import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const books = sqliteTable('Books', {
  id: integer('id').primaryKey(),
  name: text('name'),
});

export const authors = sqliteTable('Authors', {
  id: integer('id').primaryKey(),
  name: text('name'),
});

</code></pre>



<h2 class="wp-block-heading">3. <span style="text-decoration: underline;">Configure the Module</span>:</h2>



<p><span class=""><span class="">Import the integration module of the respective database driver into the root <code>AppModule</code></span></span> and configure it with your credentials, schema file and injection tag as shown in the sample code below for each of the drivers.</p>



<h4 class="wp-block-heading">PlanetScale</h4>



<pre class="wp-block-code language-typescript"><code>import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as schema from '../db/schema';
import { DrizzlePlanetScaleModule } from '@knaadh/nestjs-drizzle-planetscale';

@Module({
  imports: &#91;
    DrizzlePlanetScaleModule.register({
      tag: 'DB_PROD',
      planetscale: {
        config: {
          username: 'PLANETSCALE_USERNAME',
          password: 'PLANETSCALE_PASSWORD',
          host: 'PLANETSCALE_HOST',
        },
      },
      config: { schema: { ...schema } },
    }),
  ],
  controllers: &#91;AppController],
  providers: &#91;AppService],
})
export class AppModule {}

</code></pre>



<h4 class="wp-block-heading">Turso</h4>



<pre class="wp-block-code language-typescript"><code>import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as schema from '../db/schema';
import { DrizzleTursoModule } from '@knaadh/nestjs-drizzle-turso';

@Module({
  imports: &#91;
    DrizzleTursoModule.register({
      tag: 'DB_PROD',
      turso: {
        config: {
          url: 'DATABASE_URL',
          authToken: 'DATABASE_AUTH_TOKEN',
        },
      },
      config: { schema: { ...schema } },
    }),
  ],
  controllers: &#91;AppController],
  providers: &#91;AppService],
})
export class AppModule {}</code></pre>



<h4 class="wp-block-heading">MySQL2</h4>



<pre class="wp-block-code language-typescript"><code>import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as schema from '../db/schema';
import { DrizzleMySqlModule } from '@knaadh/nestjs-drizzle-mysql2';

@Module({
  imports: &#91;
    DrizzleMySqlModule.register({
      tag: 'DB_PROD',
      mysql: {
        connection: 'client',
        config: {
          host: DATABASE_HOST,
          user: DATABASE_USER,
          password : DATABASE_PASSWORD,
          database: DATABASE_NAME,
        },
      },
      config: { schema: { ...schema }, mode: 'default' },
    }),
  ],
  controllers: &#91;AppController],
  providers: &#91;AppService],
})
export class AppModule {}</code></pre>



<h4 class="wp-block-heading">Node-Postgres</h4>



<pre class="wp-block-code language-typescript"><code>import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as schema from '../db/schema';
import { DrizzlePGModule } from '@knaadh/nestjs-drizzle-pg';

@Module({
  imports: &#91;
    DrizzlePGModule.register({
      tag: 'DB_PROD',
      pg: {
        connection: 'client',
        config: {
          connectionString: DATABASE_CONNECTION_URL,
        },
      },
      config: { schema: { ...schema } },
    }),
  ],
  controllers: &#91;AppController],
  providers: &#91;AppService],
})
export class AppModule {}</code></pre>



<h4 class="wp-block-heading">Better-SQLite3</h4>



<pre class="wp-block-code language-typescript"><code>import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import * as schema from '../db/schema';
import { DrizzleBetterSQLiteModule } from '@knaadh/nestjs-drizzle-better-sqlite3';

@Module({
  imports: &#91;
    DrizzleBetterSQLiteModule.register({
      tag: 'DB_PROD',
      sqlite3: {
        filename: DATABASE_FILE,
      },
      config: { schema: { ...schema } },
    }),
  ],
  controllers: &#91;AppController],
  providers: &#91;AppService],
})
export class AppModule {}

</code></pre>



<h2 class="wp-block-heading">4. <span style="text-decoration: underline;">Inject Drizzle Service</span>:</h2>



<p>You can inject the Drizzle instance anywhere using the <code>tag</code> specified in the module configuration of its respective database driver.</p>



<h4 class="wp-block-heading">PlanetScale</h4>



<pre class="wp-block-code language-bash"><code>import { Inject, Injectable } from '@nestjs/common';
import * as schema from '../db/schema';
import { PlanetScaleDatabase } from 'drizzle-orm/planetscale-serverless';
@Injectable()
export class AppService {
  constructor(
    @Inject('DB_PROD') private drizzleProd: PlanetScaleDatabase&lt;typeof schema>
  ) {}
  async getData() {
    const authors = await this.drizzleProd.query.authors.findMany();
    return authors;
  }
}</code></pre>



<h4 class="wp-block-heading">Turso</h4>



<pre class="wp-block-code language-bash"><code>import { Inject, Injectable } from '@nestjs/common';
import * as schema from '../db/schema';
import { LibSQLDatabase } from 'drizzle-orm/libsql';
@Injectable()
export class AppService {
  constructor(
    @Inject('DB_PROD') private drizzleProd: LibSQLDatabase&lt;typeof schema>
  ) {}
  async getData() {
    const authors = await this.drizzleProd.query.authors.findMany();
    return authors;
  }
}</code></pre>



<h4 class="wp-block-heading">MySQL2</h4>



<pre class="wp-block-code language-bash"><code>import { Inject, Injectable } from '@nestjs/common';
import * as schema from '../db/schema';
import { MySql2Database } from 'drizzle-orm/mysql2';
@Injectable()
export class AppService {
  constructor(
    @Inject('DB_PROD') private drizzleProd: MySql2Database&lt;typeof schema>
  ) {}
  async getData() {
    const authors = await this.drizzleProd.query.authors.findMany();
    return authors;
  }
}</code></pre>



<h4 class="wp-block-heading">Node-Postgres</h4>



<pre class="wp-block-code language-bash"><code>import { Inject, Injectable } from '@nestjs/common';
import * as schema from '../db/schema';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
@Injectable()
export class AppService {
  constructor(
    @Inject('DB_PROD') private drizzleProd: NodePgDatabase&lt;typeof schema>
  ) {}
  async getData() {
    const authors = await this.drizzleProd.query.authors.findMany();
    return authors;
  }
}</code></pre>



<h4 class="wp-block-heading">Better-SQLite3</h4>



<pre class="wp-block-code language-bash"><code>import { Inject, Injectable } from '@nestjs/common';
import * as schema from '../db/schema';
import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
@Injectable()
export class AppService {
  constructor(
    @Inject('DB_PROD') private drizzleProd: BetterSQLite3Database&lt;typeof schema>
  ) {}
  async getData() {
    const authors = await this.drizzleProd.query.authors.findMany();
    return authors;
  }
}</code></pre>



<p><strong>Note:</strong> You can connect as many databases as you want, provided the <code>tag</code> should always be different for each database module.</p>
<p>The post <a href="https://mithle.sh/how-to-use-drizzle-orm-with-nestjs/">How to use Drizzle ORM with NestJS</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mithle.sh/how-to-use-drizzle-orm-with-nestjs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1414</post-id>	</item>
		<item>
		<title>The Pagination Dilemma &#8211; Offset vs Cursor (Part II)</title>
		<link>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-2/</link>
					<comments>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-2/#respond</comments>
		
		<dc:creator><![CDATA[Mithlesh]]></dc:creator>
		<pubDate>Tue, 14 Mar 2023 11:01:04 +0000</pubDate>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[knex]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[postgres]]></category>
		<guid isPermaLink="false">https://blog.mithle.sh/?p=1228</guid>

					<description><![CDATA[<p>In the previous article, we learned how to implement offset and cursor pagination using SQL and Knex. Now, we will benchmark these paging techniques to...</p>
<p>The post <a href="https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-2/">The Pagination Dilemma &#8211; Offset vs Cursor (Part II)</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In the previous <a href="https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1" target="_blank" rel="noreferrer noopener">article</a>, we learned how to implement offset and cursor pagination using SQL and Knex. Now, we will benchmark these paging techniques to find out which one is better and discuss their pros and cons.</p>



<h2 class="wp-block-heading" id="benchmarks">Benchmarks</h2>



<p>For benchmarking, we are using the <code>artist</code> table from the <a href="https://musicbrainz.org/doc/MusicBrainz_Database" target="_blank" rel="noreferrer noopener">MusicBrainz </a>database. We are using <a href="https://www.npmjs.com/package/@mithleshjs/knex-nest" target="_blank" rel="noreferrer noopener">Knex-Nest</a> &#8211; pagination included <a href="https://knexjs.org/" target="_blank" rel="noreferrer noopener">Knex </a>module for <a href="https://nestjs.com/" target="_blank" rel="noreferrer noopener">NestJS</a> &#8211; for benchmarking.</p>



<h3 class="wp-block-heading" id="offset-pagination">Offset Pagination</h3>



<pre class="wp-block-code line-numbers language-typescript"><code> async getOffsetPagingBenchmarks(
    from = 1,
    to = 10,
    perPage = 50,
    total?: number
  ) {
    let avgQueryTimeOffset = 0;
    for (let index = from; index &lt;= to; index += 1) {
      const tag = `Page ${index}`;
      const start = hrtime.bigint();
      const query = this.knexPg('artist')
        .select('id', 'name')
        .orderBy('id', 'asc');
      await KnexPagination.offsetPaginate({
        query: query,
        perPage: perPage,
        goToPage: index,
        count: total,
      });
      const end = hrtime.bigint();
      const latency = round(Number(end - start) / 1000000);
      avgQueryTimeOffset += latency;
      console.log(`${tag}: ${latency} ms`);
    }
    avgQueryTimeOffset = round(avgQueryTimeOffset / 10);
    console.log(`Average (offset): ${avgQueryTimeOffset} ms`);
    return avgQueryTimeOffset;
  }
</code></pre>



<h3 class="wp-block-heading" id="cursor-pagination">Cursor Pagination</h3>



<pre class="wp-block-code line-numbers language-typescript"><code> async getCursorPagingBenchmarks(
    from = 'start',
    numOfPages = 10,
    perPage = 50
  ) {
    let avgQueryTimeCursor = 0;
    let cursor = null;
    let index = 0;
    while (numOfPages &gt; 0) {
      const tag = `Page ${index++}`;
      const start = hrtime.bigint();
      const query = this.knexPg('artist').select('id', 'name');
      const result = await KnexPagination.cursorPaginate({
        query: query,
        perPage: perPage,
        cursor: {
          key: 'id',
          order: 'asc',
          value: cursor,
          direction: from === 'start' ? 'next' : 'prev',
        },
      });
      if (result.pagination.cursor) {
        cursor = result.pagination.cursor.next_cursor;
      }
      const end = hrtime.bigint();
      const latency = round(Number(end - start) / 1000000);
      avgQueryTimeCursor += latency;
      console.log(`${tag}: ${latency} ms`);
      numOfPages -= 1;
    }
    avgQueryTimeCursor = round(avgQueryTimeCursor / 10);
    console.log(`Average (cursor): ${avgQueryTimeCursor} ms`);
    return avgQueryTimeCursor;
  }
</code></pre>



<p>Let us fetch the first 10 pages with 50 records per page and see what results we get:</p>



<pre class="wp-block-code line-numbers language-typescript"><code>async getBenchmarks() {
    return {
      'Average Time (Offset)': (await this.getOffsetPagingBenchmarks()) + ' ms',
      'Average Time (Cursor)': (await this.getCursorPagingBenchmarks()) + ' ms',
    };
  }
</code></pre>



<pre class="wp-block-code line-numbers language-json"><code>{
    "Average Time (Offset)": "96.49 ms",
    "Average Time (Cursor)": "0.7 ms"
}
</code></pre>



<p>As you can see, offset pagination is almost 137x times slower than cursor pagination even when we are just fetching the first ten pages. The reason is that for each page a <code>count query</code> is executed and its cost is proportional to the number of records. As there are around 1.9 million entries in <code>artist table,</code> so we are seeing a lot of performance differences.</p>



<p>For a fair comparison, we need to fix that performance issue with offset pagination first. We can either store the row count in a separate table and update it whenever a row is added or deleted, or we can cache it somewhere. Some databases maintain row count internally, so refer to their docs for that.</p>



<pre class="wp-block-code line-numbers language-typescript"><code>async getBenchmarks() {
    const total = getArtistsCount();
    return {
      'Average Time (Offset)':
        (await this.getOffsetPagingBenchmarks(1, 10, 50, total)) + ' ms',
      'Average Time (Cursor)':
        (await this.getCursorPagingBenchmarks('start', 10, 50)) + ' ms',
    };
}
</code></pre>



<pre class="wp-block-code line-numbers language-json"><code>{
    "Average Time (Offset)": "0.79 ms",
    "Average Time (Cursor)": "0.76 ms"
}
</code></pre>



<p>Now, the performance difference is almost negligible after optimisation. For a very small table, you can ignore this optimisation but otherwise, you should always maintain a <code>row count</code> if you ever plan to use offset pagination.</p>



<p>We fetch the last 10 pages with 50 records per page</p>



<pre class="wp-block-code line-numbers language-typescript"><code>async getBenchmarks() {
    const total = getArtistsCount();
    return {
      'Average Time (Offset)':
        (await this.getOffsetPagingBenchmarks(38837, 38847, 50, total)) + ' ms',
      'Average Time (Cursor)':
        (await this.getCursorPagingBenchmarks('end', 10, 50)) + ' ms',
    };
}
</code></pre>



<pre class="wp-block-code line-numbers language-json"><code>{
    "Average Time (Offset)": "1734.32 ms",
    "Average Time (Cursor)": "0.78 ms"
}
</code></pre>



<ol>
<li>The time taken by cursor pagination remained the same, but the performance of the offset pagination dropped to a point where it became impractical and inefficient to use. As the page number increases, so does the time it takes to fetch that page because the <code>OFFSET</code> clause has to traverse through all the preceding pages to reach the current page. In the above example, when the last page was requested, the offset pagination traversed through all the 1.9 million records to get the last 50 records while the cursor pagination got the last 50 records directly. That&#8217;s the main reason why cursor pagination outperformed offset pagination by a huge margin.</li>
</ol>



<h2 class="wp-block-heading" id="pros-and-cons">Pros and Cons</h2>



<h4 class="wp-block-heading" id="offset-pagination">Offset Pagination</h4>



<ul>
<li>Pros
<ul>
<li>Easier to implement</li>



<li>Can jump to a specific page</li>



<li>Can sort data by any column(s)</li>
</ul>
</li>



<li>Cons
<ul>
<li>Not scalable</li>



<li>Not suitable for frequently changing data as page positions may shift when a row is added or deleted from the table</li>
</ul>
</li>
</ul>



<h4 class="wp-block-heading" id="cursor-pagination">Cursor Pagination</h4>



<ul>
<li>Pros
<ul>
<li>Scalable to any database size</li>



<li>Works very well with frequency changing data</li>
</ul>
</li>



<li>Cons
<ul>
<li>Can&#8217;t jump to a specific page</li>



<li>Sorting is very limited as it needs a unique sequential column as a cursor</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p>As a rule &#8211; <em>always use cursor pagination.</em> It is scalable and efficient for any database size. But if you must use offset pagination then use it only when the dataset is very small such as <em>search results, blog posts etc </em>otherwise it would lead to degraded performance and slower page loads.</p>
<p>The post <a href="https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-2/">The Pagination Dilemma &#8211; Offset vs Cursor (Part II)</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1228</post-id>	</item>
		<item>
		<title>The Pagination Dilemma &#8211; Offset vs Cursor (Part I)</title>
		<link>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1/</link>
					<comments>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1/#respond</comments>
		
		<dc:creator><![CDATA[Mithlesh]]></dc:creator>
		<pubDate>Mon, 13 Mar 2023 20:17:22 +0000</pubDate>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[knex]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[postgres]]></category>
		<guid isPermaLink="false">https://blog.mithle.sh/?p=1125</guid>

					<description><![CDATA[<p>In this part one of the two-article series on pagination, we will learn how to properly implement offset paging and cursor paging using SQL or...</p>
<p>The post <a href="https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1/">The Pagination Dilemma &#8211; Offset vs Cursor (Part I)</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this part one of the two-article series on pagination, we will learn how to properly implement offset paging and cursor paging using <a href="https://www.w3schools.com/sql/" target="_blank" rel="noreferrer noopener">SQL</a> or <a href="https://github.com/mithleshjs/knex-nest" target="_blank" rel="noreferrer noopener">Knex</a></p>



<h2 class="wp-block-heading" id="offset">Offset</h2>



<p>It is the most popular and easy-to-implement paging technique. The offset-pagination is used when we want to give users an option to jump to specific pages.</p>



<p>To implement offset pagination we need to write two queries &#8211; one to fetch records and the other to get the total count. We also require two inputs from the user &#8211; the number of records per page and the page number.</p>



<p>Suppose, we want to fetch <code>artists</code> from the <a href="https://musicbrainz.org/doc/MusicBrainz_Database">MusicBrainz</a> database and paginate the data. &nbsp;To achieve that we would use <code><strong>LIMIT</strong></code> and <code><strong>OFFSET</strong></code> clause as shown in the steps below.</p>



<ul>
<li>The first step would be to fetch the specific page using <code>perPage</code> and <code>page</code> parameters provided by the user. For example, with <code>page = 4</code> and <code>perPage = 10</code>, we need to skip the first 30 records and then fetch the subsequent 10 records to get the fourth page of results.
<pre class="language-sql line-numbers"><code>
SELECT * FROM musicbrainz.artist 
ORDER BY ID asc OFFSET 30 LIMIT 10;
</code>
</pre>
<pre class="language-typescript line-numbers"><code>const artists = this.knexPg('artist')
  .select('id', 'name')
  .orderBy('id', 'asc')
  .limit(perPage)
  .offset((page - 1) * perPage);</code></pre>
</li>



<li>We then count all of the <code>artists</code> to determine the total number of pages.
<pre class="wp-block-code line-numbers language-sql"><code>SELECT COUNT(*) FROM musicbrainz.artist</code></pre>
<pre class="wp-block-code language-typescript line-numbers"><code>
const count = await this.knexPg('artist')
  .count({ total: '*' })
  .first();
const total = count.total;
const totalPages = Math.ceil(total / perPage);
</code>
</pre>
</li>



<li>Here&#8217;s the full code for the offset pagination function and a sample response:
<pre class="wp-block-code line-numbers language-typescript"><code>
async getArtists(perPage, page) {
  const artists = await this.knexPg('artist')
    .select('id', 'name')
    .orderBy('id', 'asc')
    .limit(perPage)
    .offset((page - 1) * perPage);
  const count = await this.knexPg('artist').count({ total: '*'}).first();
  const total = parseInt(count.total);
  const totalPages = Math.ceil(total / perPage);
  return {
    artists: artists,
    pagination: {
      perPage: perPage,
      currentPage: page,
      totalPages: totalPages,
      total: total,
   },
 };
}</code></pre>
<pre class="wp-block-code line-numbers language-json"><code>
{
  "artists": [...],
  "pagination": {
    "perPage": 10,
    "currentPage": 2,
    "totalPages": 194233,
    "total": 1942322
   }
}
</code>
</pre>
</li>
</ul>



<h2 class="wp-block-heading" id="cursor">Cursor</h2>



<p>This paging technique works by making use of <code><strong>WHERE</strong></code> and <code><strong>ORDER BY</strong></code> clauses on a unique, sequential column(s). We fetch data from the table and then return a cursor that the user can send in the next request to fetch the subsequent data.</p>



<p>Unlike offset pagination, we can&#8217;t jump to a specific page as there is no such concept in this technique. We can only fetch the first page, the previous page, the next page, and the last page.</p>



<p>In this method, we require the following four inputs from the user:</p>



<ul>
<li>no of records per page</li>



<li>the value of the cursor</li>



<li>the sort order of the cursor &#8211; <code>asc | desc</code></li>



<li>direction &#8211; <em>next</em> to fetch items after the cursor or <em>prev</em> to fetch items before the cursor</li>
</ul>



<p>Now let us fetch the <code>artists</code> again from the <a href="https://musicbrainz.org/doc/MusicBrainz_Database">MusicBrainz</a> database but this time using cursor pagination. To begin with cursor pagination, we need to first choose a cursor &#8211; a column that is both <code>sequential</code> and <code>unique</code>. For this example, we would choose <code><strong>ID</strong></code> column as the cursor.</p>



<p>The <code>ID</code> column can sort the data in <em>ascending</em> or <em>descending </em>order and the user can request the <em>next page</em> or the <em>previous page</em>. So there are a total of four scenarios that we need to handle.</p>



<ul>
<li>order by is <code>asc</code> and direction is <code>next</code></li>



<li>order by is <code>asc</code> and direction is <code>prev</code></li>



<li>order by is <code>desc</code> and direction is <code>next</code></li>



<li>order by is <code>desc</code> and direction is <code>prev</code></li>
</ul>



<h3 class="wp-block-heading">Let us handle each case one by one:</h3>



<h4 class="has-text-align-left wp-block-heading">1. Order &#8211; <code>ASC</code> and Direction &#8211; <code>NEXT</code></h4>



<p>This is pretty straightforward, for example with <code>cursor = 10,</code> <code>perPage = 10,</code> we would write the following query to fetch the next ten records.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>In all the four cases, we set <code>LIMIT = perPage + 1</code> to check if there is a next/prev page.</p>
</blockquote>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &gt; 10 
ORDER BY ID asc 
LIMIT 11;
</code></pre>



<p>If <code>cursor = null,</code> it means the user is requesting the first page, so the query would be:</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
ORDER BY ID asc 
LIMIT 11;
</code></pre>



<ul>
<li>If <code>result.length = perPage</code> or <code>result.length &lt; perPage,</code> it means there is no next page.</li>



<li>If <code>result.length &gt; perPage,</code> it means there is a next page and there is an extra item in the result.</li>
</ul>



<p>The <code>ID</code> of the last item from the result will be returned to the user as <code>next_cursor</code> but if there is a next page, we need to first remove that extra last item from the result.</p>



<h4 class="wp-block-heading">2. Order &#8211; <code>ASC</code> and Direction &#8211; <code>PREV</code></h4>



<p>You must be thinking that flipping the comparison operator in the previous query is all we need to traverse in the opposite direction. Let us try that to see if it works.</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &lt; 11 
ORDER BY ID asc 
LIMIT 11;
</code></pre>



<p>We should the get records from <code>1-10</code>. It did work but is it right? Now, let us assume the <code>cursor = 21</code> and then run the query.</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &lt; 21 
ORDER BY ID asc 
LIMIT 11;
</code></pre>



<p>The query should have output records <code>10-20</code> but instead, we get records <code>1-11</code>, <em>but why?</em> Because we forgot that the data is in ascending order and begins with <code>ID = 1</code> and it is also less than <code>21</code>.</p>



<p>So, to get the correct records, we need to flip the <code>ORDER</code> of the cursor.</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &lt; 21 
ORDER BY ID desc 
LIMIT 11;
</code></pre>



<pre class="wp-block-code line-numbers language-json"><code>results = &#91;
    { id: 20 } ... { id:10 }
]
</code></pre>



<p>The data is correct but the order is incorrect. To fix this, just reverse the results array.</p>



<ul>
<li>If <code>cursor = null,</code> it means the user is requesting the last page, so the query would be: <code>SELECT * FROM musicbrainz.artist ORDER BY ID desc LIMIT 11;</code></li>
</ul>



<ul>
<li>If <code>result.length = perPage</code> or <code>result.length &lt; perPage</code>, it means there is no prev page.</li>



<li>If <code>result.length &gt; perPage,</code> it means there is a prev page and there is an extra item in the result.</li>
</ul>



<p>The <code>ID</code> of the first item in the result will be returned to the user as the <code>prev_cursor</code> but if there is a prev page, we need to first remove that extra item from the top.</p>



<h4 class="wp-block-heading">3. Order &#8211; <code>DESC</code> and Direction &#8211; <code>NEXT</code></h4>



<p>The only difference between this case and the first case is the <code>order</code>. So, we just need to change the comparison operator from <code>&gt;</code> to <code>&lt;</code> and <code>ORDER BY</code> from <code>asc</code> to <code>desc</code> and the rest is exactly the same.</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &lt; 10 
ORDER BY ID desc 
LIMIT 11;
</code></pre>



<h4 class="wp-block-heading">4. Order &#8211; <code>DESC</code> and Direction &#8211; <code>PREV</code></h4>



<p>Change the comparison operator from <code>&lt;</code> to <code>&gt;</code> and flip the <code>ORDER BY</code> from &#8216;desc&#8217; to &#8216;asc&#8217; from the third case and follow the rest.</p>



<pre class="wp-block-code line-numbers language-sql"><code>SELECT * FROM musicbrainz.artist 
WHERE ID &gt; 91 
ORDER BY ID asc 
LIMIT 11;
</code></pre>



<p>Here&#8217;s the full code for the offset pagination function and a sample response</p>



<pre class="wp-block-code line-numbers language-typescript"><code> public static async cursorPaginate(params: ICursorPaginateParams) {
    const { query, cursor, perPage = 10, dataKey = 'data' } = params;
    const cursorMeta = {
      hasNextPage: false,
      next_cursor: null,
      hasPrevPage: false,
      prev_cursor: null,
    };
    const whereOperator = this.getWhereOperator(cursor.order, cursor.direction);
    // if cursor is null, we need to get the first/last page
    if (cursor.value) {
      query.where(cursor.key, whereOperator, cursor.value);
    }
    // if direction is prev, we need to reverse the order
    const order =
      cursor.direction === 'next'
        ? cursor.order
        : cursor.order === 'asc'
        ? 'desc'
        : 'asc';
    // add +1 to the limit to determine if there is a next/prev page
    const result = await query.orderBy(cursor.key, order).limit(perPage + 1);
    // if direction is prev, we need to reverse the result
    if (order !== cursor.order) {
      result.reverse();
    }
    // if we have more than perPage results, we have a next/prev page
    if (result.length &gt; perPage) {
      if (cursor.direction === 'next') {
        result.pop();
        cursorMeta.hasNextPage = true;
        if (cursor.value) {
          cursorMeta.hasPrevPage = true;
          cursorMeta.prev_cursor = result&#91;0]&#91;cursor.key];
        }
        cursorMeta.next_cursor = result&#91;result.length - 1]&#91;cursor.key];
      } else {
        result.shift();
        cursorMeta.hasPrevPage = true;
        if (cursor.value) {
          cursorMeta.hasNextPage = true;
          cursorMeta.next_cursor = result&#91;result.length - 1]&#91;cursor.key];
        }
        cursorMeta.prev_cursor = result&#91;0]&#91;cursor.key];
      }
    } else if (result.length &gt; 0 &amp;&amp; result.length &lt;= perPage) {
      if (cursor.direction === 'next') {
        if (cursor.value) {
          cursorMeta.hasPrevPage = true;
          cursorMeta.prev_cursor = result&#91;0]&#91;cursor.key];
        }
      } else {
        if (cursor.value) {
          cursorMeta.hasNextPage = true;
          cursorMeta.next_cursor = result&#91;result.length - 1]&#91;cursor.key];
        }
      }
    }

    return {
      &#91;dataKey]: result,
      pagination: {
        cursor: cursorMeta,
        per_page: perPage,
      },
    };
  }

  private static getWhereOperator(order, direction) {
    if (order === 'asc') {
      if (direction === 'next') {
        return '&gt;';
      } else {
        return '&lt;';
      }
    } else {
      if (direction === 'prev') {
        return '&gt;';
      } else {
        return '&lt;';
      }
    }
  }
</code></pre>



<pre class="wp-block-code line-numbers language-json"><code>{
    "artists": &#91;...],
    "pagination": {
        "cursor": {
            "hasNextPage": true,
            "next_cursor": 45,
            "hasPrevPage": true,
            "prev_cursor": 41
        },
        "per_page": 10
    }
}
</code></pre>



<p>In the next post of this series, we will discuss their pros and cons and benchmark both of these pagination methods to compare their performances using the <a href="https://musicbrainz.org/doc/MusicBrainz_Database">MusicBrainz</a> database.</p>
<p>The post <a href="https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1/">The Pagination Dilemma &#8211; Offset vs Cursor (Part I)</a> appeared first on <a href="https://mithle.sh">Mithle.sh</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mithle.sh/the-pagination-dilemma-offset-vs-cursor-part-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1125</post-id>	</item>
	</channel>
</rss>
