using node-pg I'm trying to lookup the existence of a string inside a JSON object.
Example (part of) row:
{ viq_id: '801583',
title: 'Blank, security key, lock system, and production method',
applicants: [ [Object], [Object] ],
cpc: [ [Object], [Object] ],
abstract: { value: [Object], language: 'en' } }
abstract is of type JSONB.
When querying for this
var query = 'SELECT viq_id, title, applicants, cpc, abstract ->> "value"' +
' FROM epo_patents' +
' WHERE title ILIKE $1';
or for this
var query = 'SELECT viq_id, title, applicants, cpc, abstract' +
' FROM epo_patents' +
' WHERE title ILIKE $1 OR abstract ->> "value" = $1';
or for this
var query = 'SELECT viq_id, title, applicants, cpc, abstract' +
' FROM epo_patents' +
' WHERE abstract.value = $1';
the answer is "errorMissingColumn", or in the latter case errorMissingRTE
How do I properly query against JSON in node pg?
change var query = 'SELECT viq_id, title, applicants, cpc, abstract ->> "value"' to var query = "SELECT viq_id, title, applicants, cpc, abstract ->> 'value'" , because double quotes used for db objects (table,column,etc) name...
look at Postgres JSON syntax
Related
Right now I use this cumbersome approach when I want to add a row whose data is in a JS Object
Adding a row to a table:
const mysql = require('mysql')
var db = mysql.createConnection(DBInfo)
var databaseObj = {val1: '1', name: 'John', age: 40} // row to insert
var query = 'INSERT INTO my_table ('
var databaseKeys = Object.keys(databaseObj)
for (let i = 0; i < databaseKeys.length; i++) {
query += databaseKeys[i] + (i !== databaseKeys.length - 1 ? ', ' : ')')
}
query += ' ' + 'VALUES('
for (let i = 0; i < databaseKeys.length; i++) {
query += '\'' + databaseObj[databaseKeys[i]] + '\'' + (i !== databaseKeys.length - 1 ? ', ' : ')')
}
db.query(query, function (err, results, fields) {...
Is there any simpler or neater way to add a row into a table, where such row data is in a JS Object? The examples I see around use an array of arrays, but in my case the info is in a Object
I should use the INSERT into table SET because they are equivalent
var db = mysql.createConnection(DBInfo)
var databaseObj = {val1: '1', name: 'John', age: 40}
var query = 'INSERT INTO my_table SET ' + db.escape(databaseObj)
db.query(query, function (err, results, fields) {...
I have a lessons table that contains the following fields:
id title type language level
The user through the interface can select witch lesson he wants to open.
He will start selecting the language, then the type and finally the level.
During this process I want to query the database using a single sql statement, but of course the first query will have only the language field. I came up with this syntax but it does not work:
function queryLessonList (language, type, level){
const values = [language, type, level];
const sql = "SELECT * FROM lessons WHERE (language=?) AND (? is null OR type=?) AND (? is null OR level=?)";
return query(sql, values);
}
How can I make it work?
To reduce the complexity of checking variables and building out the query, instead you can pass the function an object to match, what you want and the columns you want returning etc (as * is not ideal).
So something like:
function queryLessonList (where = {}, columns = ['*']) {
let keys = Object.keys(where)
let values = Object.values(where)
columns = !columns.length || columns[0] === '*' ?
'*': columns.map(e => '`'+e+'`').join(',')
let sql = `
SELECT ${columns}
FROM lessons
${keys.length ? 'WHERE \`'+keys.join('` = ? AND `')+'\` = ?' : ''}
`
return query(sql, values)
}
/*
SELECT *
FROM lessons
WHERE `language` = ? AND `type` = ?
*/
queryLessonList({
language: 'en',
type: 'foo'
}, [])
/*
SELECT `id`
FROM lessons
*/
queryLessonList({}, ['id'])
/*
SELECT *
FROM lessons
*/
queryLessonList()
I have a front end with a singular input to search names however my database separated them into 2 fields. What I want to do is run a concat and like statement alone the lines of:
SELECT * FROM users WHERE (CONCAT(first_name, ' ', last_name)) LIKE '%John Do%' AND permission_id = 1 AND user_status_id = 1;
now I've gotten to this:
let name = req.params.name;
let userId = req.user;
debugger;
db.user.findAll({
where: {
attributes: [db.sequelize.fn('concat', db.Sequelize.col('first_name'), ' ', db.Sequelize.col('last_name'), 'full_name'), {
like: `%${name}%`
}],
},
include : [
db.client
]
})
but can't think where to include the like or the concat
Note : I have not shared database schema as I am mainly looking for a help only w.r.t. last step which is 'left outer join' on 2 sub-queries.
select *
from
(select id
from Action
where id = 3) AS act1
left Outer Join
(select Action.name,
completed_At as completedAt,
deadline, notes,
ActionAssignedTo.action_Id as actionId,
from Action
inner join Employee
on Action.created_By_Id = Employee.id
and Employee.vendor_Id = 2
inner join ActionAssignedTo
on Action.id = ActionAssignedTo.action_Id
and ActionAssignedTo.action_Id = 3
where Action.created_By_Id = 7
group by Action.id
limit 2) AS act2
on act1.id = act2.actionId
I need to write this above query using Bookshelf
let options = {columns: [ 'Action.name', 'completed_At as completedAt',
'deadline', 'notes',
'ActionAssignedTo.action_Id as actionId',
]};
let action2 = new Action();
action2.query().innerJoin('Employee', function () {
this.on('Action.created_By_Id', 'Employee.id')
.andOn('Employee.vendor_Id', bookshelf.knex.raw(1));
});
action2.query().innerJoin('ActionAssignedTo', function () {
this.on('Action.id', 'ActionAssignedTo.action_Id')
.andOn('ActionAssignedTo.action_Id', bookshelf.knex.raw(5));
});
action2.query().where(function() {
this.where('Action.created_By_Id', empId)
});
action2.query().groupBy('Action.id');
action2.query().limit(2);
action2.query().columns(options.columns);
let action1;
action1 = Action.where('id', actionId);
action1.query().columns('id');
return bookshelf.knex.raw('select * from '
+ '(' + action1.query().toString() + ') AS act1'
+ ' left Outer Join '
+ '(' + action2.query().toString() + ') AS act2'
+ ' on act1.id = act2.actionId');
I am not keen on using bookshelf.knex.raw for using the left Outer Join as the output given by knex.raw and bookshelf differ.
Is there a way I can do the 'left Outer Join' directly using bookshelf library.
I looked into the code but it seems leftOuterJoin only takes table name as the first parameter and what I need is a query.
I think your main problem is that you're using Bookshelf like you would be using knex. Bookshelf is meant to be used with models you would define and then query on them.
Here is an example of what you should have as model
// Adding registry to avoid circular references
// Adding camelcase to get your columns names converted to camelCase
bookshelf.plugin(['bookshelf-camelcase', 'registry']);
// Reference: https://github.com/brianc/node-pg-types
// These two lines convert all bigint values coming from Postgres from JS string to JS integer.
// Removing these lines will mess up with Bookshelf count() methods and bigserial values
pg.types.setTypeParser(20, 'text', parseInt);
const Action = db.bookshelf.Model.extend({
tableName: 'Action',
createdBy: function createdBy() {
return this.belongsTo(Employee, 'id', 'created_By_Id');
},
assignedTo: function assignedTo() {
return this.hasMany(ActionAssignedTo, 'action_id');
},
});
const Employee = db.bookshelf.Model.extend({
tableName: 'Employee',
createdActions: function createdActions() {
return this.hasMany(Action, 'created_By_Id');
},
});
const ActionAssignedTo = db.bookshelf.Model.extend({
tableName: 'ActionAssignedTo',
action: function action() {
return this.belongsTo(Action, 'id', 'action_Id');
},
employee: function employee() {
return this.belongsTo(Employee, 'id', 'employee_Id');
},
});
module.exports = {
Action: db.bookshelf.model('Action', Action),
Employee: db.bookshelf.model('Employee', Employee),
ActionAssignedTo: db.bookshelf.model('ActionAssignedTo', ActionAssignedTo),
db,
};
You would then be able to fetch your results with a query like this
const Model = require('model.js');
Model.Action
.where({ id: 3 })
.fetchAll({ withRelated: ['createdBy', 'assignedTo', 'assignedTo.employee'] })
.then(data => {
// Do what you have to do
});
What your want to achieve is not possible with only one query in Bookshelf. You probably need to do a first query using knex to get a list of Action ids and then give them to Bookshelf.js
db.bookshelf.knex.raw(`
select ActionAssignedTo.action_Id as actionId,
from Action
inner join Employee
on Action.created_By_Id = Employee.id
and Employee.vendor_Id = ?
inner join ActionAssignedTo
on Action.id = ActionAssignedTo.action_Id
and ActionAssignedTo.action_Id = ?
where Action.created_By_Id = ?
group by Action.id
limit ?`,
[2, 3, 7, 2]
)
.then(result => {
const rows = result.rows;
// Do what you have to do
})
And then use the recovered Ids to get your Bookshelf query like this
Model.Action
.query(qb => {
qb.whereIn('id', rows);
})
.fetchAll({
withRelated: [{
'createdBy': qb => {
qb.columns(['id', 'firstname', 'lastname']);
},
'assignedTo': qb => {
qb.columns(['action_Id', 'employee_Id']);
},
'assignedTo.employee': qb => {
qb.columns(['id', 'firstname', 'lastname']);
},
}],
columns: ['id', 'name', 'completed_At', 'deadline', 'notes']
})
.fetchAll(data => {
// Do what you have to do
});
Note that the columns used for joins MUST BE in the columns list for each table. If you omit the columns, all the columns will be selected.
By default, Bookshelf will retrieve all columns and all root objects. The default is kind of LEFT OUTER JOIN.
I have a table in mariadb in this structure,
CREATE TABLE `items` (
`id` char(36), `route` varchar(255), `value` text,
PRIMARY KEY (`id`),
UNIQUE KEY `route` (`route`)
)
I use the route column to get user-friendy urls, such as http://www.examle.com/this-is-the-route-of-an-item
When a user creates a new item, apart from omitting spaces and illegal characters, I would like to "catch" cases where the route chosen for the new item is in use, and generate a valid route.
For example, if route-of-an-item is already in use, i would fallback to route-of-an-item-a, or route-of-an-item-b, etc.
The naive solution could be querying db in a loop, for example (kind of pseudo code):
var additionalChars = "";
while (db.query("select count * from `items` where `route`='" + route + "-" + additionalChars + "'"))
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;
Since this involves querying the db many times I thought of another solution.
var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%'");
while(usedRoutes.contains(route + '-' + additionalChars))
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;
Is there any better way to approach this kind of a problem?
Am I correct that the second solution would perform better?
If I use the second solution, should I add a fulltext index to route field?
You could sort your query by route descending and only retrieve and check one item. In your pseudo code this would look like:
var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%' order by `route` desc limit 1");
if(usedRoutes.route is already in use)
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;
Ok, so after consulting with a colleague I ended up using solution 2, here is the code (node.js), in case anyone faces this problem:
db access
var route = req.body.route || 'noname';
route = route.replace(/[\s_]/g, '-').toLowerCase().replace(/[^0-9a-z\u0591-\u05F4\u0621-\u064A\-_\s]/g, "").replace(/_+/g, ' ').trim().replace(/[\s_]+/g, '-');
var isArabic = (/[\u0621-\u064A]/g).test(route),
isHebrew = (/[\u0591-\u05F4]/g).test(route),
lang = isArabic ? 'ar' : (isHebrew ? 'he' : 'en');
Items.findAll({ where: { route: { $like: route + '%' } }, attributes: ['route'] })
.then((items) => {
var routes = _.keyBy(items, 'route'),
prefix = '';
while (routes[route + (prefix ? '-' + prefix : '')])
prefix = charactersCount(prefix, lang);
Items.create({ route: route + (prefix ? '-' + prefix : ''), value: req.body.value })
.then(function(item){
res.send({ item: _.pick(item, ['id', 'route', 'author_id', 'created_at']) })
})
.catch(function(){ res.sendStatus(500)});
})
.catch(function(){ res.sendStatus(500) });
generate additional characters
var chars = {
ar: { val: "اﻻبتثجحخدذرزسشصضطظعغفقكلمنهةوىي", len: 0 },
he: { val: "אבגדהוזחטיכלמנסעפצקרשת", len: 0 },
en: { val: "abcdefghijklmnopqrstuvwxyz", len: 0 }
};
_.forEach(chars, (c) => { c.len = c.val.length });
function charactersCount (current, lang) => {
if (!current) return chars[lang].val[0];
lang = lang || 'en';
var curr = current.split(''),
len = curr.length,
pointer = len,
lastIndex;
while ((lastIndex = chars[lang].val.indexOf(curr[--pointer]) + 1) >= chars[lang].len) curr[pointer] = chars[lang].val[0];
if (pointer < 0) { curr.unshift(''); pointer++; }
curr[pointer] = chars[lang].val[lastIndex];
return curr.join('');
}
so I end up with one select query and one inset query, and prevent the clashes in node's side.
and a fulltext index is not needed since the % apears only at the end of the like operater