Optional column update if provided value for column is not null - sql

I have following table:
CREATE TABLE IF NOT EXISTS categories
(
id SERIAL PRIMARY KEY,
title CHARACTER VARYING(100) NOT NULL,
description CHARACTER VARYING(200) NULL,
category_type CHARACTER VARYING(100) NOT NULL
);
I am using pg-promise, and I want to provide optional update of columns:
categories.update = function (categoryTitle, toUpdateCategory) {
return this.db.oneOrNone(sql.update, [
categoryTitle,
toUpdateCategory.title, toUpdateCategory.category_type, toUpdateCategory.description,
])
}
categoryName - is required
toUpdateCategory.title - is required
toUpdateCategory.category_type - is optional (can be passed or undefined)
toUpdateCategory.description - is optional (can be passed or undefined)
I want to build UPDATE query for updating only provided columns:
UPDATE categories
SET title=$2,
// ... SET category_type=$3 if $3 is no NULL otherwise keep old category_type value
// ... SET description=$4 if $4 is no NULL otherwise keep old description value
WHERE title = $1
RETURNING *;
How can I achieve this optional column update in Postgres?

You could coalesce between the old and the new values:
UPDATE categories
SET title=$2,
category_type = COALESCE($3, category_type),
description = COALESCE($4, description) -- etc...
WHERE title = $1

The helpers syntax is best for any sort of dynamic logic with pg-promise:
/* logic for skipping columns: */
const skip = c => c.value === null || c.value === undefined;
/* reusable/static ColumnSet object: */
const cs = new pgp.helpers.ColumnSet(
[
'title',
{name: 'category_type', skip},
{name: 'description', skip}
],
{table: 'categories'});
categories.update = function(title, category) {
const condition = pgp.as.format(' WHERE title = $1', title);
const update = () => pgp.helpers.update(category, cs) + condition;
return this.db.none(update);
}
And if your optional column-properties do not even exist on the object when they are not specified, you can simplify the skip logic to just this (see Column logic):
const skip = c => !c.exists;
Used API: ColumnSet, helpers.update.
See also a very similar question: Skip update columns with pg-promise.

Related

Can Laravel automatically switch between column = ? and column IS NULL depending on value?

When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();

Fix "dynamic" query without throwing me the given error?

UPDATE dbo.Einkauf_Web_Upload
SET
${
updatedUpload.Menge !== null
? `Anzahl = ${`${updatedUpload.Menge}`},`
: null
},
${
updatedUpload.ENummer !== null
? `ENummer = ${`'${updatedUpload.ENummer}'`}`
: null
}
WHERE ...
This query is supposed to differentiate between updated values of the object updatedUpload which, initially, has all of its values set to null. If the value is not altered therefor not updated, the query must not affect the particular column. In its current state, the query throws this error:
Incorrect syntax near the keyword 'null'
And I know why; if you do not alter the Menge attribute, the query looks like this:
UPDATE dbo.Einkauf_Web_Upload
SET null, ENummer = "abc"
WHERE ..
Is there a workaround to this? I am using NodeJs as my backend and thought of trying to make the column references dynamic via a mapped array which contains only the altered columns of updatedUpload.
Will appreciate any help!
SET null, ... is invalid SQL syntax, you should skip the null at all. Furthermore, remember not to do the query if there is nothing to update as SET WHERE ... is also invalid syntax.
I would suggest something like:
let updates = [
updatedUpload.Menge !== null ? `Anzahl = ${`${updatedUpload.Menge}`}` : null,
updatedUpload.ENummer !== null ? `ENummer = ${`'${updatedUpload.ENummer}'`}` : null,
// ... add here
]
// Filter out null updates
updates = updates.filter(u => !!u);
// Do query only if updates are avaliable
if (updates.length > 0) {
const sql = `UPDATE dbo.Einkauf_Web_Upload SET ${updates.join(', ')} WHERE ...`;
// ... execute
}

Dynamic column search in multiple tables with gorm golang

My scenario is i have a grid with search option where user can select the column and can do the search, the grid data is coming from various tables. I have attached a sample screen of grid.
User Screen
So i'm trying to create a dynamic query for search but the problem is i can able to search only in main table (schema.Robot) not in Preload tables. whenever i trying to search data data from Preload tables let say from RobotModel table that time getting below error
pq: missing FROM-clause entry for table "robot_models"
Here is my go code
func (r *RobotsRepository) GetRobotsSummary(listParams viewmodel.ListParams, companyID uint) ([]*schema.Robot, int, error) {
mrobots := []*schema.Robot{}
var count int
var order string
if listParams.SortColumn == "" {
listParams.SortColumn = "id"
listParams.SortOrder = 1
} else {
listParams.SortColumn = util.Underscore(listParams.SortColumn)
}
if listParams.SortOrder == 0 {
order = "ASC"
} else {
order = "DESC"
}
var searchQuery string
if listParams.SearchText != "" {
switch listParams.SearchColumn {
case "Robot":
listParams.SearchColumn = "name"
case "Model":
listParams.SearchColumn = "robot_models.name"
}
searchQuery = listParams.SearchColumn +" LIKE '%"+ listParams.SearchText +"%' and Company_ID = " + fmt.Sprint(companyID)
}else{
searchQuery = "Company_ID = " + fmt.Sprint(companyID)
}
orderBy := fmt.Sprintf("%s %s", listParams.SortColumn, order)
err := r.Conn.
Preload("RobotModel", func(db *gorm.DB) *gorm.DB {
return db.Select("ID,Name")
}).
Preload("Task", func(db *gorm.DB) *gorm.DB {
return db.Where("Task_Status in ('In-Progress','Pending')").Select("ID, Task_Status")
}).
Preload("CreatedUser", func(db *gorm.DB) *gorm.DB {
return db.Select("ID,Display_Name")
}).
Preload("UpdatedUser", func(db *gorm.DB) *gorm.DB {
return db.Select("ID,Display_Name")
}).
Where(searchQuery).
Order(orderBy).
Offset(listParams.PageSize * (listParams.PageNo - 1)).
Limit(listParams.PageSize).
Find(&mrobots).Error
r.Conn.Model(&schema.Robot{}).Where(searchQuery).Count(&count)
return mrobots, count, err
}
In searchQuery variable i'm storing my dynamic query.
My question is how can i search data for preload table columns
Here is the sql query which i'm trying to achieve using gorm
SELECT robots.id,robots.name,robot_models.name as
model_name,count(tasks.task_status) as task_on_hand,
robots.updated_at,users.user_name as updated_by
FROM rfm.robots as robots
left join rfm.tasks as tasks on tasks.robot_id = robots.id and
tasks.task_status in ('In-Progress','Pending')
left join rfm.robot_models as robot_models on robot_models.id =
robots.robot_model_id
left join rfm.users as users on users.id = robots.updated_by
WHERE robot_models.name::varchar like '%RNR%' and robots.deleted_at is null
GROUP BY robots.id,robot_models.name,users.user_name
ORDER BY task_on_hand DESC LIMIT 2 OFFSET 0
and sorry for bad English!
Even though you are preloading, you are still required to explicitly use joins when filtering and ordering on columns on other tables. Preloading is used to eagerly load the data to map into your models, not to join tables.
Chain on something like this:
.Joins("LEFT JOIN rfm.robot_models AS robot_models ON robot_models.id = robots.robot_model_id")
I'm not positive if you can use the AS keyword using this technique, but if not, it should be easy enough to adjust your query accordingly.

Upsert in KnexJS

I have an upsert query in PostgreSQL like:
INSERT INTO table
(id, name)
values
(1, 'Gabbar')
ON CONFLICT (id) DO UPDATE SET
name = 'Gabbar'
WHERE
table.id = 1
I need to use knex to this upsert query. How to go about this?
So I solved this using the following suggestion from Dotnil's answer on Knex Issues Page:
var data = {id: 1, name: 'Gabbar'};
var insert = knex('table').insert(data);
var dataClone = {id: 1, name: 'Gabbar'};
delete dataClone.id;
var update = knex('table').update(dataClone).whereRaw('table.id = ' + data.id);
var query = `${ insert.toString() } ON CONFLICT (id) DO UPDATE SET ${ update.toString().replace(/^update\s.*\sset\s/i, '') }`;
return knex.raw(query)
.then(function(dbRes){
// stuff
});
Hope this helps someone.
As of knex#v0.21.10+ a new method onConflict was introduced.
Official documentation says:
Implemented for the PostgreSQL, MySQL, and SQLite databases. A
modifier for insert queries that specifies alternative behaviour in
the case of a conflict. A conflict occurs when a table has a PRIMARY
KEY or a UNIQUE index on a column (or a composite index on a set of
columns) and a row being inserted has the same value as a row which
already exists in the table in those column(s). The default behaviour
in case of conflict is to raise an error and abort the query. Using
this method you can change this behaviour to either silently ignore
the error by using .onConflict().ignore() or to update the existing
row with new data (perform an "UPSERT") by using
.onConflict().merge().
So in your case, the implementation would be:
knex('table')
.insert({
id: id,
name: name
})
.onConflict('id')
.merge()
I've created a function for doing this and described it on the knex github issues page (along with some of the gotchas for dealing with composite unique indices).
const upsert = (params) => {
const {table, object, constraint} = params;
const insert = knex(table).insert(object);
const update = knex.queryBuilder().update(object);
return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};
Example usage:
const objToUpsert = {a:1, b:2, c:3}
upsert({
table: 'test',
object: objToUpsert,
constraint: '(a, b)',
})
A note about composite nullable indices
If you have a composite index (a,b) and b is nullable, then values (1, NULL) and (1, NULL) are considered mutually unique by Postgres (I don't get it either).
Yet another approach I could think of!
exports.upsert = (t, tableName, columnsToRetain, conflictOn) => {
const insert = knex(tableName)
.insert(t)
.toString();
const update = knex(tableName)
.update(t)
.toString();
const keepValues = columnsToRetain.map((c) => `"${c}"=${tableName}."${c}"`).join(',');
const conflictColumns = conflictOn.map((c) => `"${c.toString()}"`).join(',');
let insertOrUpdateQuery = `${insert} ON CONFLICT( ${conflictColumns}) DO ${update}`;
insertOrUpdateQuery = keepValues ? `${insertOrUpdateQuery}, ${keepValues}` : insertOrUpdateQuery;
insertOrUpdateQuery = insertOrUpdateQuery.replace(`update "${tableName}"`, 'update');
insertOrUpdateQuery = insertOrUpdateQuery.replace(`"${tableName}"`, tableName);
return Promise.resolve(knex.raw(insertOrUpdateQuery));
};
very simple.
Adding onto Dorad's answer, you can choose specific columns to upsert using merge keyword.
knex('table')
.insert({
id: id,
name: name
})
.onConflict('id')
.merge(['name']); // put column names inside an array which you want to merge.

Conditions in JOINed tables shows error CakePHP

I have two tables employee_personals where all the personal record of the employee is stored and telephone_bills where the telephone bills paid to a particular employee is stored for each month. Now in my employeePersonalsController.php I have a function called api_show_employees() which is similar to below :
function api_show_employees() {
//$this->autoRender = false;
//Configure::write("debug",0);
$office_id = '';
$cond = '';
if(isset($_GET['office_id']) && trim($_GET['office_id']) != '') {
$office_id = $_GET['office_id'];
$cond['EmployeePersonal.office_id'] = $office_id;
}
if(isset($_GET['telephoneBillTo']) && isset($_GET['telephoneBillFrom']) ) {
if($_GET['telephoneBillTo'] != '' && $_GET['telephoneBillFrom'] != '') {
$cond['TelephoneBill.bill_from'] = $_GET['telephoneBillFrom'];
$cond['TelephoneBill.bill_to'] = $_GET['telephoneBillTo'];
}
}
$order = 'EmployeePersonal.name';
// $employee = $this->EmployeePersonal->find('all');
$employee = $this->EmployeePersonal->find('all',array('order' => $order,'conditions'=>$cond));
//return json_encode($employee);
}
This functions basically finds all the employees who paid bills in the given period. But I am getting an error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'TelephoneBill.bill_from' in 'where clause'
Models : EmployeePersonal.php:
var $hasMany = array(
'TelephoneBill' => array(
'className' => 'TelephoneBill',
)
);
TelephoneBill.php
public $name = 'TelephoneBill';
var $hasMany = array('EmployeePersonal');
NB: If I skip the bill_from and bill_to conditions, I am getting the results , with TelephoneBill array !
TLDR: use Joins instead.
Details/Notes:
1) it looks like you're using recursive. Don't do that. Use Containable instead.
2) You can't limit the parent model based on conditions against data from a contained/recursive-included table - instead, use Joins.
2b) Or, you could query from the other direction, and query your TelephoneBill with conditions, then contain the EmployeePersonal.