3

I am using postgres-node, but I think this is a problem for anyone with javascript objects that have subarrays they want to save in SQL. I have a javascript objects with a varying amount (any-length) array of features:

{ 
  name: "Ted",
  features: ['Red Hair', 'Blue Eyes']
}

so when I have several of them, the javascript formats it like:

[
  { 
    name: "Ted",
    features: ['Red Hair', 'Blue Eyes']
  },
  { 
    name: "Ann",
    features: ['Brown Hair', 'Blue Eyes', 'Big Smile']
  }
]

Which is great! But how do I get this back from the database after normalizing? I have normalized this in my database like this:

people Table

+---+------------+
|id | Name       |
+---+------------+
| 1 | Ted        |
| 2 | Ann        |
+---+------------+

features table

+---+--------------+
|id | feature_name |
+---+--------------+
| 1 | Red Hair     |
| 2 | Blue Eyes    |
| 3 | Brown Hair   |
| 4 | Big Smile    |
+---+--------------+

and people_features junction table

+---+-----------+-------------+
|id | person_id | feature_id  |
+---+-----------+-------------+
| 1 | 1         | 1           |
| 2 | 1         | 2           |
| 3 | 2         | 2           |
| 4 | 2         | 3           |
| 5 | 2         | 4           |
+---+-----------+-------------+

If I do a join like this:

SELECT name, feature_name
FROM people
JOIN people_features ON people_features.person_id=people.id
JOIN features ON people_features.feature_id=features.id;

I get one row for every single person. Which isn't what I want.

What I get:

[
  { 
    name: "Ted",
    feature_name: 'Red Hair'
  },
  { 
    name: "Ted",
    feature_name: 'Blue Eyes'
  },
  { 
    name: "Ann",
    feature_name: 'Blue Eyes'
  },
  { 
    name: "Ann",
    feature_name: 'Brown Hair'
  },
  { 
    name: "Ann",
    feature_name: 'Big Smile'
  }
]

What I want:

[
  { 
    name: "Ted",
    features: ['Red Hair', 'Blue Eyes']
  },
  { 
    name: "Ann",
    features: ['Brown Hair', 'Blue Eyes', 'Big Smile']
  }
]

This seems awful! Now I need to loop through these and combine the identical people into a single person object. My other option seems to be making a request for the people

SELECT id, name
FROM people;

Which would return:

[
  { 
    id: 1
    name: "Ted"
  },
  { 
    id: 2
    name: "Ann"
  }
]

And then I need to loop through and make a separate SQL query for every single person?

For each person:

SELECT feature_name
FROM features
JOIN people_features ON features.id=people_features.feature_id
WHERE people_features.person_id = $1

($1 is the person's id that I am looping through)

And then I would get back (for Ted):

[
  { feature_name: 'Red Hair' },
  { feature_name: 'Blue Eyes' }
]

Then I need to remove these from their objects (to just get the string) and then add them to the object.

Is one of these the best way to do it? I feel like they are both really inefficient.

2
  • 1
    Are you looking for something like this? - stackoverflow.com/questions/39805736/… Commented Apr 18, 2017 at 7:07
  • The answer from Vao Tsun was exactly what I was looking for. Thank you for sharing that though! Commented Apr 18, 2017 at 13:43

2 Answers 2

1

This should do it:

SELECT name, array_agg(feature_name)
FROM people
JOIN people_features ON people_features.person_id=people.id
JOIN features ON people_features.feature_id=features.id
GROUP BY people.id;
Sign up to request clarification or add additional context in comments.

Comments

1

Vao Tsun's answer is perfect. For those interested, here is the minimal version of what I ended up using in my node code to get it working with node-postgres:

var pg = require('pg');
var config = {
    user: process.env.PG_USER || null, //env var: PGUSER
    password: process.env.DATABASE_SECRET || null, //env var: PGPASSWORD
    host: process.env.DATABASE_SERVER || 'localhost', // Server hosting the postgres database
    port: process.env.DATABASE_PORT || 5432, //env var: PGPORT    
    database: process.env.DATABASE_NAME || 'lukeschlangen', //env var: PGDATABASE
    max: 10, // max number of clients in the pool
    idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed
};

var pool = new pg.Pool(config)

pool.connect(function (err, client, done) {
    if (err) {
        console.log('There was an error', err);
    } else {
        client.query(
            'SELECT name, array_agg(feature_name) ' +
            'FROM people ' +
            'JOIN people_features ON people_features.person_id=people.id ' +
            'JOIN features ON people_features.feature_id=features.id ' +
            'GROUP BY people.id;',
            function (err, results) {
                done();
                console.log(results.rows); // This was exactly the array I wanted
            }
        );

    }
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.