Skip to content

Postgres: queryBuilder makes different parameter identifiers for same parameter, causing problems with groupby #7308

@brahn

Description

@brahn

Issue Description & steps to reproduce

With postgres, if the same parameter is used multiple times in the query expression, TypeORM still produces multiple parameters $1, $2, etc. in the rendered SQL, which can causes problems with groupBy expressions.

More specifically:

  1. I'm using postgres, creating a SELECT query with a GROUP BY

  2. The expression being grouped includes a parameter. Example: I wish to group and count rows based on rounding the value in the column temperature, and I am using a parameter :decimalPlaces to set the rounding precision.

  3. I include the expression both in the select and the group by, aiming to produce

     SELECT round(temperature, :decimalPlaces), count(*)
     FROM temperature_data
     GROUP BY round(temperature, :decimalPlaces)
  4. As far as I can tell, the best way to do this with typeORM is as follows:

     const makeQueryBuilder = (
       temperatureRepository: Repository<TemperatureData>, 
       decimalPlaces: number
     ) => {
       const builder = temperatureRepository.createQueryBuilder();
       builder
         .select("round(temperature, :decimalPlaces)", "rounded_temperature")
         .addSelect("count(*)", "count")
         .groupBy("round(temperature, :decimalPlaces)", "rounded_temperature");
       builder.setParameters({ decimalPlaces });
       return builder;
     };
     
     const repository = ...
     const builder = makeQueryBuilder(repository, 2);
     const [query, parameters] = builder.getQueryAndParameters();

    Inspecting the results in the debugger reveals (adding whitespace for readability):

     // console.log(query) => 
      SELECT 
        round(temperature, $1) AS "rounded_temperature", 
        count(*) AS "count" 
      FROM "temperature_readings" 
      GROUP BY round(temperature, $2)
    
     // console.log(parameters) => 
       [2, 2]
    

    So far this seems reasonable!

  5. However, running this query fails with the error

    column "temperature_readings.temperature" must appear 
    in the GROUP BY clause or be used in an aggregate function
    

    I believe the parser is objecting because it treats the expressions round(temperature, $1) and round(temperature, $2) as potentially different. And I can get the same error by attempting the following statement in the postgres console:

     PREPARE myTemperatureQuery (int, int) AS
       SELECT 
         round(temperature, $1) AS "rounded_temperature", 
         count(*) AS "count" 
       FROM "temperature_readings" 
       GROUP BY round(temperature, $2)
    

Expected Behavior

TypeORM should render the query with a single parameter that appears more than once. E.g. as

 // console.log(query) => 
  SELECT 
    round(temperature, $1) AS "rounded_temperature", 
    count(*) AS "count" 
  FROM "temperature_readings" 
  GROUP BY round(temperature, $1)

 // console.log(parameters) => 
   [2]

I believe this will address the problem because if I enter the following into the postgres parser, the statement succeeds:

 PREPARE myTemperatureQuery (int, int) AS
   SELECT 
     round(temperature, $1) AS "rounded_temperature", 
     count(*) AS "count" 
   FROM "temperature_readings" 
   GROUP BY round(temperature, $1) -- only change is that this is now $1

My Environment

Dependency Version
MacOS 10.15.7
Node.js version v12.18.3
Typescript version v3.9.5
TypeORM version v0.2.29

Relevant Database Driver(s)

  • aurora-data-api
  • aurora-data-api-pg
  • better-sqlite3
  • cockroachdb
  • cordova
  • expo
  • mongodb
  • mysql
  • nativescript
  • oracle
  • postgres
  • react-native
  • sap
  • sqlite
  • sqlite-abstract
  • sqljs
  • sqlserver

Are you willing to resolve this issue by submitting a Pull Request?

  • Yes, I have the time, and I know how to start.
  • Yes, I have the time, but I don't know how to start. I would need guidance.
  • No, I don't have the time, although I believe I could do it if I had the time...
  • No, I don't have the time and I wouldn't even know how to start.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions