Getting unique results from a knex.js INNER JOIN operation

Two tables, metadata and view_events, each have columns for config_id and config_type. The goal is to retrieve all unique view_events based on a user's email address, distinct by config_id and config_type, ordered by timestamp in descending order, limited to the 10 most recent entries. Although the following knex.js code attempts to achieve this, it faces issues:

return dbClient<AuthenticatedUserIndexRow>(METADATA_TABLE_NAME)
    .select([
      `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.CONFIG_ID}`,
      `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.CONFIG_TYPE}`,
      `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.DESCRIPTION}`,
      `${VIEW_EVENTS_TABLE_NAME}.${VIEW_EVENTS_COLUMNS.TIMESTAMP}`,
    ])
    .innerJoin<AuthenticatedUserIndexRow>(VIEW_EVENTS_TABLE_NAME, function innerJoinOnViewEvents() {
      this.on(
        `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.STORAGE_ID}`,
        '=',
        `${VIEW_EVENTS_TABLE_NAME}.${VIEW_EVENTS_COLUMNS.CONFIG_STORAGE_ID}`,
      )
        .andOn(
          `${VIEW_EVENTS_TABLE_NAME}.${VIEW_EVENTS_COLUMNS.USER_EMAIL}`,
          '=',
          rawSql('?', [authUserEmail]),
        )
        .andOn(`${METADATA_TABLE_NAME}.${METADATA_COLUMNS.DELETED}`, '=', rawSql('?', [false]));
    })
    .distinct([
      `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.CONFIG_TYPE}`,
      `${METADATA_TABLE_NAME}.${METADATA_COLUMNS.CONFIG_ID}`,
    ])
    .limit(EVENT_LIMIT)
    .orderBy(VIEW_EVENTS_COLUMNS.TIMESTAMP, 'desc');

Consider the sample tables provided:

view_events
+-------------+-----------+--------------------------+----------------------+
| config_type | config_id |        timestamp         |        email         |
+-------------+-----------+--------------------------+----------------------+
| a           | foo       | 2020-01-23T03:08:14.618Z | johndoe@example.com   |
| a           | bar       | 2020-01-23T03:08:13.618Z | janedoe@example.com   |
| b           | foo       | 2020-01-23T03:08:12.618Z | bobdoe@example.com    |
+-------------+-----------+--------------------------+----------------------+

metadata
+-------------+-----------+---------------------------+
| config_type | config_id |        description        |
+-------------+-----------+---------------------------+
| a           | foo       | Type a config with id foo |
| a           | bar       | Type a config with id bar |
| b           | foo       | Type b config with id foo |
+-------------+-----------+---------------------------+

If the authUserEmail is johndoe@example.com, the desired output should be as follows:

+-------------+-----------+---------------------------+
| config_type | config_id |        description        |
+-------------+-----------+---------------------------+
| a           | foo       | Type a config with id foo |
| a           | bar       | Type a config with id bar |
| b           | foo       | Type b config with id foo |
+-------------+-----------+---------------------------+

While attempting this query, an issue arose due to the combination of SELECT and DISTINCT. What would be the correct approach here?

Answer №1

Do you find the following approach acceptable? I utilized the 'with' function to fetch the 10 most recent configurations (max(timestamp)..group by config) and then excluded the timestamp column in the final output. Please note that the final records may not be sorted by timestamp, as per your request, but they will represent the 10 most recent configurations. The DELETED column has not been included, but you can add it based on the code in your question.

knex.with('ordered_items', (qb) =>
          qb.table('metadata')
          .innerJoin('view_events', function() {
              this.on('metadata.config_id', '=', 'view_events.config_id')
                  .andOn('metadata.config_type', '=', 'view_events.config_type')
          })
          .where({'view_events.email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="254f4a4d4b0b56484c514d654248444c490b464a48">[email protected]</a>'})
          .select(['metadata.config_type', 'metadata.config_id',
                   'metadata.description'])
          .max('view_events.timestamp', {as: 'max_ts'})
          .groupBy(['metadata.config_id', 'metadata.config_type', 'metadata.description'])
          .orderBy('max_ts', 'desc')
          .limit(10))
    .table('ordered_items')
    .select(['config_type', 'config_id', 'description'])

My input and output:

sqlite> select * from metadata;
a|foo|Type a config with id foo
a|bar|Type a config with id bar
b|foo|Type b config with id foo
a|baz|Type a config with id baz
sqlite> select * from view_events;
a|foo|2020-01-23T03:08:14.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="016b6e696f2f726c68756941666c60686d2f626e6c">[email protected]</a>
a|foo|2020-01-23T03:08:14.500Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f298939c97dc969d97b2959f939b9edc919d9f">[email protected]</a>
a|foo|2020-01-23T03:08:13.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="eb81848385c59886829f83ab8c868a8287c5888486">[email protected]</a>
a|bar|2020-01-23T03:08:12.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="96fcf9fef8b8e5fbffe2fed6f1fbf7fffab8f5f9fb">[email protected]</a>
a|foo|2020-01-23T03:08:11.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4822272026663b25213c20082f25292124662b2725">[email protected]</a>
b|foo|2020-01-23T03:08:10.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4923262127673a24203d21092e24282025672a2624">[email protected]</a>
a|baz|2020-01-23T03:08:09.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c6aca9aea8e8b5abafb2ae86a1aba7afaae8a5a9ab">[email protected]</a>
a|foo|2020-01-23T03:08:08.618Z|<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e882878086c69b85819c80a88f85898184c68b8785">[email protected]</a>

[ { config_type: 'a',
    config_id: 'foo',
    description: 'Type a config with id foo' },
  { config_type: 'a',
    config_id: 'bar',
    description: 'Type a config with id bar' },
  { config_type: 'b',
    config_id: 'foo',
    description: 'Type b config with id foo' },
  { config_type: 'a',
    config_id: 'baz',
    description: 'Type a config with id baz' } ]

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Express handlebars does not support client-side javascript

This is a unique template <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>My Amazing Website</title> ...

A server that broadcasts using Javascript, Node.js, and Socket.IO

Could someone provide a simple example of how to create a Node.JS server that runs both a TCP and Socket.IO server? The main goal is to pass data from a TCP client to multiple Socket.IO clients who are interested in it. This setup is intended to facilita ...

The method firebaseApp.auth does not exist in user authentication using Firebase

Implementing user authentication with Firebase in my React webapp has been a challenge due to issues with the firebaseAuth.app() function. Despite trying various solutions such as reinstalling firebase dependencies, updating imports to SDK 9 syntax, and ad ...

Implementing intersection in doctrine dql requires utilizing the intersection functions provided by Doctrine

Can anyone help me with incorporating the intersect function in SQL into Doctrine DQL for my queries? I want to intersect the results of the following two queries: $sql1 = "SELECT distinct o1 FROM Application\Entity\Object1 o1 INNER JOIN Applic ...

Determine the number of objects in a JSON array and compile a new array containing the sum of the

I am working with a JSON array that looks like this: const arrayVal = [{ "DATE": "2020-12-1", "NAME": "JAKE", "TEAM_NO": 2, }, { "DATE": "2020-12-2"`, "NAME" ...

The v-select menu in Vuetify conceals the text-field input

How can I prevent the menu from covering the input box in Vuetify version 2.3.18? I came across a potential solution here, but it didn't work for me: https://codepen.io/jrast/pen/NwMaZE?editors=1010 I also found an issue on the Vuetify github page t ...

Expanding the capabilities of jQuery UI event handling

I am looking for a way to enhance dialog functionality by automatically destroying it when closed, without the need to add additional code to each dialog call in my current project. My idea is to override the default dialog close event. After researching ...

How can you efficiently load the materials for a THREE.BoxGeometry using THREE.LoadingManager()?

I am looking to enhance the image quality of a geometry after user interaction by initially loading low-resolution assets and then switching to high-resolution assets when the user interacts with it. When using the following standard code: var materials = ...

A guide on sending multiple input values using the same class name or id through ajax

My goal is to send multiple input values through AJAX to my PHP script. Everything works smoothly when I use getElementById. However, I have the functionality to add a child. The issue arises when it only retrieves values from the first child while iterati ...

Hide the scrollbars on the sidebar

I'm attempting to recreate a sleek scrollbar that caught my eye on the website . To achieve this, I need to have a screen larger than 955px in order to display the sidebar. However, when my sidebar overflows in the y direction, it causes an additional ...

Monitoring the flow of data between Angular JS resources and the promise responses

In my application, there is a grid consisting of cells where users can drag and drop images. Whenever an image is dropped onto a cell, a $resource action call is triggered to update the app. My goal is to display a loader in each cell while the update cal ...

Method for testing with Jest

I'm relatively new to Jest and I've been having trouble testing this particular JavaScript method: const parseData = (items) => { const data = []; const itemsCount = items.length; for (let i = 0; i < itemsCount; i += 1) { const el ...

Guide on displaying several items from Laravel to Vue through the JavaScript filter method?

I need help transferring multiple objects from laravel to vue and filling my vue objects with information retrieved from the database. This is the data received from laravel: https://i.sstatic.net/2Hnk5.png Vue objects to be populated: storeName: {}, ...

Every page on Nextjs displaying identical content across all routes

I recently deployed a Next.js app using docker on AWS infrastructure. While the index page (/) loads correctly, I've noticed that the content of the index is also being loaded for every other route, including api routes, as well as the JavaScript and ...

Mapping an array using getServerSideProps in NextJS - the ultimate guide!

I'm facing an issue while trying to utilize data fetched from the Twitch API in order to generate a list of streamers. However, when attempting to map the props obtained from getServerSideProps, I end up with a blank page. Interestingly, upon using co ...

Efficiently scheduling file downloads using WebDriver automation

I am in the process of automating a website using WebDriver, but I have encountered unique file download requirements that differ from what is readily available online. My specific scenario involves a website where I create orders. Upon clicking the &apos ...

Tips for personalizing the error message displayed on Webpack's overlay

Is there a way to personalize the error overlay output message in order to hide any references to loaders, as shown in this image: https://i.sstatic.net/ESFVc.png Any suggestions on how to remove the line similar to the one above from the overlay output? ...

Ways to access the props value within the lifecycle hooks of Vue JS

Is there a way to retrieve the value of props using lifecycle hooks such as mounted or updated, and then store that value in my v-model with additional text? I've been struggling to achieve this. I attempted using :value on the input element with bot ...

Creating a SELECT CASE statement for PostgreSQL using pg and Node.js involves incorporating conditional logic to

I've recently started working with a RESTful API using Node.js/Express and a PostgreSQL database( version 12.4). Instead of using an ORM, I'm writing raw SQL queries. One of the challenges I encountered is creating a "SELECT CASE" statement, whic ...

When the page is resized for mobile, the Bootstrap modal shifts downwards

I am encountering an issue with a modal that pops up on my webpage when clicked. It is perfectly centered in a browser view and smaller views. However, when I resize the page for phones, the modal shifts down the page and requires scrolling to see it. How ...