13

Currently I have the following code to get the results of two queries

dbro.many("SELECT geoname_id, country_name FROM paises WHERE locale_code=$1 LIMIT 10",data.lang)
   .then(function(countriesData){
      data.countries=countriesData;
      dbro.many("SELECT * FROM categorias")
       .then(function(categoriesData){
         data.categories=(categoriesData)
         console.log(data);
         res.render('layout', data);
         res.end();
      })
       .catch(function(err){
        console.log("error while fetching categories data");
      })
    })
    .catch(function(err){
      console.log("error while fetching countries data",err);
    });

Somehow I think this is not right. What if I need to get the results of many queries before returning the callback? The nesting of several then/catch becomes hideous. The objective is to have all the data ready before rendering a page (in Express)

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
David Noriega
  • 196
  • 1
  • 1
  • 7

1 Answers1

33

pg-promise documentation has plenty of examples of how to execute multiple queries.

Initialization

const pgp = require('pg-promise')(/* initialization options */);
const db = pgp('postgres://username:password@host:port/database');

When queries depend on each other we should use a task:

db.task('get-user-events', async t => {
        const user = await t.one('select * from users where id = $1', 123);
        return t.any('select * from events where login = $1', user.name);
})
    .then(data => {
        // data = result from the last query;
    })
    .catch(error => {
        // error
    });

When queries have no dependencies between them:

db.task('get-everything', async t => {
    const users = await t.any('select * from users');
    const count = await t.one('select count(*) from events', [], a => +a.count);
    return {users, count};
})
    .then({users, count} => {

    })
    .catch(error => {
        // error
    });

And when the queries change the data, we should replace task with tx for transaction.

Note that I emphasized each statement with "should", as you can execute everything outside of tasks or transactions, but it is not recommended, due to the way database connections are managed.

You should only execute queries on the root protocol (db object) when you need to execute a single query per HTTP request. Multiple queries at once should always be executed within tasks/transactions.

See also Chaining Queries, with its main point at the bottom there:

If you do not follow the advised approach, your application will perform better under a small load, due to more connections allocated in parallel, but under a heavy load it will quickly deplete the connection pool, crippling performance and scalability of your application.

UPDATE

Starting from pg-promise v7.0.0 we can pull results from multiple independent queries in a single command, which is much more efficient than all of the previous solutions:

const {users, count} = await db.multi('SELECT * FROM users;SELECT count(*) FROM events');

The library implements helpers.concat, to format and concatenate multiple queries.

See also methods: multi and multiResult.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • Thanks this is what I was looking for. I did read the documentation but I missed the crucial part (at least for me): // data[0] = result from the first query; // data[1] = result from the second query; (result data as an array) – David Noriega Mar 02 '16 at 16:05
  • @vitaly-t Thanks for your examples. However, how would you go if you had to mix both ? In my task I run 5 independent queries. Each one return the latest insert id. Once this task is done, I need to run a last query, which would use all the ids produces by the task. I haven't managed to mix both so far. Do you have any idea. Thanks for your help. – Standaa - Remember Monica Mar 30 '16 at 13:27
  • @Stanislasdrg what you are asking is more about basic use of promises rather than this library. You do `return t.batch([...your 5 inserts...]).then(data=>t.query(...query based on data...))`. – vitaly-t Mar 30 '16 at 13:42
  • @vitaly-t Thanks for your example. How will you do a more complicated chain? for example "get all users named john" and then "get their children's name" and return as `[{ firstname:"John", lastname:"dep", children:[{name:...},{...}] } ,{ ... ]` – Parham Apr 12 '16 at 04:06
  • @Parham are you talking about a simple one-to-many request? Anyhow, this message board is a poor place for such discussion. Ask a separate question on StackOverflow, I will answer it ;) – vitaly-t Apr 12 '16 at 05:00
  • @vitaly-t Many to Many is more of what I have in mind. I just posted http://stackoverflow.com/questions/36586486/best-way-to-query-a-many-to-many-relationship-using-pg-promise . Thanks – Parham Apr 13 '16 at 00:36
  • @DavidNoriega The answer has received an update for the latest version of the library. – vitaly-t Oct 16 '17 at 04:24
  • @vitaly-t When/why would one use batch instead of multi if the latter is better? – Code Aug 31 '18 at 06:16
  • @Code Actually, it is neither. In practice, you would need to format queries a lot, and so if they do not have dependency, then you can use [helpers.concat](http://vitaly-t.github.io/pg-promise/helpers.html#.concat), and only then [multi](http://vitaly-t.github.io/pg-promise/Database.html#multi). Other than that, approach with [multi](http://vitaly-t.github.io/pg-promise/Database.html#multi) is simply newer. Method `batch` allows for some dependency, as it can execute functions that return queries. – vitaly-t Aug 31 '18 at 11:44
  • I see @vitaly-t answer, I upvote :) curious question, if there is an error in await t.one wont it trigger an unhandledpromiserejection? – PirateApp Jul 28 '21 at 07:26
  • 1
    @PirateApp No, it will be handled by `catch` on the task. – vitaly-t Jul 28 '21 at 07:42