postgresqltypeormquerying

How to retrieve test with highest WPM and accuracy, without failing if a test with higher WPM exists?


I'm trying to retrieve the test from a user that has the highest WPM and accuracy with a time of 60 seconds. I've managed to get the test with the highest WPM and accuracy. If there are two tests with the same wpm and accuracy then the createdAt date is used to pick a test, so the test with the older createdAt date is picked. The problem is that if there are two tests with the same WPM and accuracy, and there's a third test with a higher WPM, the query fails to return any test.

Here is the query builder:

const qb = ctx.em
      .createQueryBuilder(Test, 'test')
      .innerJoin(
        (subQuery) =>
          subQuery
            .select('MAX(t.wpm)', 'max_wpm')
            .addSelect('t.creatorId', 'creatorId')
            .addSelect('MAX(t.accuracy)', 'max_accuracy')
            .from(Test, 't')
            .where('t.time = :time', { time: '60' })
            .groupBy('t.creatorId'),
        'max_tests',
        'max_tests.max_wpm = test.wpm AND max_tests."creatorId" = test."creatorId" AND max_tests.max_accuracy = test.accuracy'
      )
      .leftJoinAndSelect('test.creator', 'creator')
      .where('test.time = :time', { time: '60' })
      .andWhere((qb) => {
        const subQuery = qb
          .subQuery()
          .select('MIN(t2.createdAt)')
          .from(Test, 't2')
          .where('t2.creatorId = test.creatorId')
          .andWhere('t2.wpm = test.wpm')
          .andWhere('t2.accuracy = test.accuracy')
          .getQuery();
        return `test."createdAt" = (${subQuery})`;
      })
      .orderBy('test.wpm', 'DESC')
      .addOrderBy('test.accuracy', 'DESC')

And here are three example tests:

[
  {
    "id": 1,
    "wpm": 96,
    "accuracy": 96.7,
    "time": "60",
    "createdAt": "2023-05-09T11:26:42.003917Z",
    "creatorId": 1
  },
  {
    "id": 2,
    "wpm": 96,
    "accuracy": 96.7,
    "time": "60",
    "createdAt": "2023-05-09T12:58:48.956275Z",
    "creatorId": 1
  },
  {
    "id": 3,
    "wpm": 97,
    "accuracy": 97,
    "time": "60",
    "createdAt": "2023-05-09T13:18:21.991219Z",
    "creatorId": 1
  }
]

The expected result is to get test #3, but my current code fails to return anything.

How can I modify my query?

Database: postgreSQL orm: typeorm


Solution

  • I figured out that the issue was that I was comparing both wpm and accuracy at the same time, so if two tests have the same wpm and accuracy and if the third test has a higher wpm but lower accuracy then the query fails which was what happened in this case. So I modified the queryBuilder to first check for highest wpm and if there's two tests with the same wpm then check for accuracy and if there is a duplication then check for createdAt date.

    Here is the final qb:

    const qb = ctx.em
          .createQueryBuilder(Test, 'test')
          .innerJoin(
            (subQuery) =>
              subQuery
                .select('MAX(t.wpm)', 'max_wpm')
                .addSelect('t.creatorId', 'creatorId')
                .from(Test, 't')
                .where('t.time = :time', { time: '60' })
                .groupBy('t.creatorId'),
            'max_tests',
            'max_tests.max_wpm = test.wpm AND max_tests."creatorId" = test."creatorId"'
          )
          .where('test.time = :time', { time: '60' })
          .andWhere((qb) => {
            const subQuery = qb
              .subQuery()
              .select('MAX(t2.accuracy)')
              .from(Test, 't2')
              .where('t2.time = :time')
              .andWhere('t2.wpm = test.wpm')
              .andWhere('t2.creatorId = test.creatorId')
              .setParameter('time', '60')
              .getQuery();
            return `test.accuracy = (${subQuery})`;
          })
          .andWhere((qb) => {
            const subQuery = qb
              .subQuery()
              .select('MIN(t3.createdAt)')
              .from(Test, 't3')
              .where('t3.creatorId = test.creatorId')
              .andWhere('t3.wpm = test.wpm')
              .andWhere('t3.accuracy = test.accuracy')
              .setParameter('time', '60')
              .getQuery();
            return `test."createdAt" = (${subQuery})`;
          })
          .leftJoinAndSelect('test.creator', 'creator')
          .orderBy('test.wpm', 'DESC')
          .addOrderBy('test.accuracy', 'DESC')
          .addOrderBy('test.createdAt', 'ASC');