pg-promise update multiple not setting columns correctly - postgresql

I have a project where I need to do a update multiple rows at once. I have found the example on how to do this is the docs: documentation
I have done used a columnset because it is being recommended to do to in the documentations. I have set the ?feature_id so it is only used in the WHERE clause.
The error my code is generating is the following: error: column "created_on" is of type timestamp with time zone but expression is of type text. I have noticed in the query that is being generated and that seems to be in line with the example.
This code has an insert statement for the features that are new and that seems to work fine. The error is only being thrown on the update query.
const insertValues = [];
const updateValues = [];
for (let i = 0; i < features.length; i += 1) {
const feature = features[i];
if (!excistingFeaturesIds.includes(feature.id)) {
insertValues.push({
plot_id: plotId,
type: feature.type,
area: feature.area,
created_on: currendDate,
updated_on: currendDate,
geo_feature: feature.geoFeature,
});
} else {
updateValues.push({
feature_id: feature.id,
plot_id: plotId,
type: feature.type,
area: feature.area,
created_on: currendDate,
updated_on: currendDate,
geo_feature: feature.geoFeature,
});
}
}
const insertColumnSet = new pgp.helpers.ColumnSet(['plot_id', 'type', 'area', 'created_on', 'updated_on', 'geo_feature'], { table: 'features' });
const updateColumnSet = new pgp.helpers.ColumnSet(['?feature_id', 'plot_id', 'type', 'area', 'created_on', 'updated_on', 'geo_feature'], { table: 'features' });
if (insertValues && insertValues.length > 0) {
const insertQuery = pgp.helpers.insert(
insertValues, insertColumnSet,
);
await promiseDB.none(insertQuery);
}
if (updateValues && updateValues.length > 0) {
const updateQuery = `${pgp.helpers.update(
updateValues, updateColumnSet,
)} WHERE v.feature_id = t.feature_id`;
console.log(updateQuery);
await promiseDB.none(updateQuery);
}
return res.status(201).json({
message: 'Features added!',
});
} catch (err) {
console.log(err);
return res.status(400).send(err);
}
UPDATE
"features" AS t
SET
"plot_id" = v. "plot_id",
"type" = v. "type",
"area" = v. "area",
"created_on" = v. "created_on",
"updated_on" = v. "updated_on",
"geo_feature" = v. "geo_feature"
FROM (
values(1, 3, 'roof', 342.01520314642977, '2021-07-20T09:56:10.007+02:00', '2021-07-20T09:56:10.007+02:00', '{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[coords...]]]},"properties":{"UIDN":6338864,"OIDN":5290477,"VERSIE":1,"BEGINDATUM":"2015-09-23","VERSDATUM":"2015-09-23","TYPE":1,"LBLTYPE":"hoofdgebouw","OPNDATUM":"2015-08-25","BGNINV":5,"LBLBGNINV":"kadastralisatie","type":"roof","tools":"polygon","description":"D1","id":5290477,"area":342.01520314642977,"roofType":"saddle","roofGreen":"normal","database":true},"id":1}'),
(2,
3,
'roof',
181.00725895629216,
'2021-07-20T09:56:10.007+02:00',
'2021-07-20T09:56:10.007+02:00',
'{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":6338518,"OIDN":5290131,"VERSIE":1,"BEGINDATUM":"2015-09-23","VERSDATUM":"2015-09-23","TYPE":1,"LBLTYPE":"hoofdgebouw","OPNDATUM":"2015-08-25","BGNINV":5,"LBLBGNINV":"kadastralisatie","type":"roof","tools":"polygon","description":"D2","id":5290131,"area":181.00725895629216,"roofType":"flat","roofGreen":"normal","database":true},"id":2}'),
(3,
3,
'roof',
24.450163203958745,
'2021-07-20T09:56:10.007+02:00',
'2021-07-20T09:56:10.007+02:00',
'{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":5473377,"OIDN":4708120,"VERSIE":1,"BEGINDATUM":"2014-07-04","VERSDATUM":"2014-07-04","TYPE":2,"LBLTYPE":"bijgebouw","OPNDATUM":"2014-05-27","BGNINV":4,"LBLBGNINV":"bijhouding binnengebieden","type":"roof","tools":"polygon","description":"D3","id":4708120,"area":24.450163203958745,"roofType":"saddle","roofGreen":"normal","database":true},"id":3}'),
(4,
3,
'water',
57.65676046589426,
'2021-07-20T09:56:10.007+02:00',
'2021-07-20T09:56:10.007+02:00',
'{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":473256,"OIDN":199890,"VERSIE":2,"BEGINDATUM":"2017-03-08","VERSDATUM":"2021-05-06","VHAG":-9,"NAAM":"nvt","OPNDATUM":"2017-01-30","BGNINV":4,"LBLBGNINV":"bijhouding binnengebieden","type":"water","tools":"polygon","description":"W1","id":199890,"area":57.65676046589426,"waterType":"natural","database":true},"id":4}'))
AS v ("feature_id",
"plot_id",
"type",
"area",
"created_on",
"updated_on",
"geo_feature")
WHERE
v.feature_id = t.feature_id
INSERT INTO "features" ("plot_id", "type", "area", "created_on", "updated_on", "geo_feature")
values(3, 'roof', 342.01520314642977, '2021-07-20T10:17:04.565+02:00', '2021-07-20T10:17:04.565+02:00', '{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":6338864,"OIDN":5290477,"VERSIE":1,"BEGINDATUM":"2015-09-23","VERSDATUM":"2015-09-23","TYPE":1,"LBLTYPE":"hoofdgebouw","OPNDATUM":"2015-08-25","BGNINV":5,"LBLBGNINV":"kadastralisatie","type":"roof","tools":"polygon","description":"D1","id":5290477,"area":342.01520314642977,"roofType":"saddle","roofGreen":"normal","database":true},"id":1}'), (3, 'roof', 181.00725895629216, '2021-07-20T10:17:04.565+02:00', '2021-07-20T10:17:04.565+02:00', '{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":6338518,"OIDN":5290131,"VERSIE":1,"BEGINDATUM":"2015-09-23","VERSDATUM":"2015-09-23","TYPE":1,"LBLTYPE":"hoofdgebouw","OPNDATUM":"2015-08-25","BGNINV":5,"LBLBGNINV":"kadastralisatie","type":"roof","tools":"polygon","description":"D2","id":5290131,"area":181.00725895629216,"roofType":"flat","roofGreen":"normal","database":true},"id":2}'), (3, 'roof', 24.450163203958745, '2021-07-20T10:17:04.565+02:00', '2021-07-20T10:17:04.565+02:00', '{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":5473377,"OIDN":4708120,"VERSIE":1,"BEGINDATUM":"2014-07-04","VERSDATUM":"2014-07-04","TYPE":2,"LBLTYPE":"bijgebouw","OPNDATUM":"2014-05-27","BGNINV":4,"LBLBGNINV":"bijhouding binnengebieden","type":"roof","tools":"polygon","description":"D3","id":4708120,"area":24.450163203958745,"roofType":"saddle","roofGreen":"normal","database":true},"id":3}'), (3, 'water', 57.65676046589426, '2021-07-20T10:17:04.565+02:00', '2021-07-20T10:17:04.565+02:00', '{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[coords...]]]},"properties":{"UIDN":473256,"OIDN":199890,"VERSIE":2,"BEGINDATUM":"2017-03-08","VERSDATUM":"2021-05-06","VHAG":-9,"NAAM":"nvt","OPNDATUM":"2017-01-30","BGNINV":4,"LBLBGNINV":"bijhouding binnengebieden","type":"water","tools":"polygon","description":"W1","id":199890,"area":57.65676046589426,"waterType":"natural","database":true},"id":4}')

Your columns created_on and updated_on need SQL type casting, hence the error.
And there is no need re-creting the same list of table -> columns.
In all, your column-sets can be created like this:
const insertColumnSet = new pgp.helpers.ColumnSet([
'plot_id',
'type',
'area',
{name: 'created_on', cast: 'timestamptz'},
{name: 'updated_on', cast: 'timestamptz'},
'geo_feature'
], { table: 'features' });
const updateColumnSet = insertColumnSet.extend(['?feature_id']};
See Column full syntax, plus extend method.
UPDATE
Note that strictly speaking, only the UPDATE needs the type casting, while your INSERT can infer the type automatically, which you can reflect in the column-sets like this:
const insertColumnSet = new pgp.helpers.ColumnSet(['plot_id', 'type', 'area',
'created_on', 'updated_on', 'geo_feature'], { table: 'features' });
const updateColumnSet = insertColumnSet.merge([
{name: 'feature_id', cnd: true}, // or just '?feature_id'
{name: 'created_on', cast: 'timestamptz'},
{name: 'updated_on', cast: 'timestamptz'}
]};
Both approaches will work fine in your case though ;)
See merge method.

Related

How to find google charts (Sankey) select events selection data - including tooltip column

I am creating a Sankey chart via react-google-charts. Each time when clicked on the link between the nodes I am printing the data which has been working fine until recently.
Assume my Sankey diagram to be like this and I clicked on the link between A & P:
[A] ----> [P] ------> [X]
[B] ----> [Q] ------> [Y]
[C] ----> [R] ------> [Z]
let myOptions = {
sankey: {
link: {
interactivity: true
}
}
}
...
...
<Chart
chartType='Sankey'
data={
[
['From', 'To', 'Weight', {role: 'tooltip', type: 'string'}],
['A', 'P', 1, 'i111'],
['B', 'Q', 1, 'j333'],
['C', 'R', 1, 'k444'],
['P', 'X', 1, 'l555'],
['Q', 'Y', 1, 'l666'],
['R', 'Z', 1, 'n999']
]
}
columns
options={myOptions}
chartEvents={[
{
eventName: 'select',
callback: ({chartWrapper}) => {
const chart = chartWrapper.getChart()
const selection = chart.getSelection()
if (selection.length === 1) {
const [selectedItem] = selection
const {row} = selectedItem
// below line was working until recently, but not anymore
console.log(chartWrapper.getDataTable().Vf[row].c)
// updated the property key after which it works
console.log(chartWrapper.getDataTable().Wf[row].c)
// returns [{v: 'A'}, {v: 'P'}, {v: 1}, {v: 'i111'}]
}
}
}
]}
/>
I can also get the selection data like this but it does not give me the final column value i.e., tooltip in this case.
console.log(chartWrapper.getDataTable().cache[row])
// returns [{Me: 'A'}, {Me: 'P'}, {Me: '1'}]
Is there any other way for me to get the data apart from what I have done above? Especially the line
chartWrapper.getDataTable().Wf[row].c
Having a property value hardcoded has broken my UI thrice in recent times and I would like to avoid doing so.
to my knowledge, the sankey chart will only allow you to select the nodes,
not the links between the nodes.
and this is only allowed after setting the interactivity option.
options: {
sankey: {
node: {
interactivity: true
}
}
}
the selection returns the name of the node selected,
which can appear in the data table multiple times.
in the following example, I've added an additional "P" node to demonstrate.
when the select event fires, you can get the name of the node selected from the chart's selection.
then you must search through the rows in the data table to find the row with the matching node name.
once you've found the data table row for the selected node name,
you can use data table method getValue to retrieve the values for that row.
see following working snippet...
google.charts.load('current', {
packages: ['controls', 'sankey']
}).then(function () {
var chartWrapper = new google.visualization.ChartWrapper({
chartType: 'Sankey',
containerId: 'chart',
dataTable: google.visualization.arrayToDataTable([
['From', 'To', 'Weight', {role: 'tooltip', type: 'string'}],
['A', 'P', 1, 'i111'],
['B', 'Q', 1, 'j333'],
['C', 'R', 1, 'k444'],
['P', 'X', 1, 'l555'],
['P', 'Y', 2, 'l555'],
['Q', 'Y', 1, 'l666'],
['R', 'Z', 1, 'n999']
]),
options: {
sankey: {
node: {
interactivity: true
}
}
}
});
google.visualization.events.addListener(chartWrapper, 'ready', function () {
google.visualization.events.addListener(chartWrapper.getChart(), 'select', selectEvent);
});
chartWrapper.draw();
function selectEvent() {
var chart = chartWrapper.getChart();
var data = chartWrapper.getDataTable();
var selection = chart.getSelection();
if (selection.length > 0) {
// find data table rows for selected node name
var nodeName = selection[0].name;
var nodeRows = data.getFilteredRows([{
column: 0,
value: nodeName
}]);
// find row values for selected node name
nodeRows.forEach(function (row) {
var valFrom = data.getValue(row, 0);
var valTo = data.getValue(row, 1);
var valWeight = data.getValue(row, 2);
var valTooltip = data.getValue(row, 3);
console.log(valFrom, valTo, valWeight, valTooltip);
});
}
}
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart"></div>

applyTransaction remove not working with id

I'm using ag-grid in Angular9 project. I'm using Transactions to do CRUD operations in grid when my backend request resolve. I need to provide RowNodeId myself, i dont want to use object-references as i have large data set.
Thing is, i've provided the ID and i can add/update item in the grid but i'm unable to delete the item. In Doc it mentions, you only need to provide id to remove the item but i'm getting the following error.
Here's the code.
class HostAppListPage
{
#ViewChild('agGrid', {static: true}) grid:AgGridAngular;
constructor()
{
}
ngOnInit()
{
this.grid.getRowNodeId = (data) => {
return data.entityId;
};
this.columns = [
{headerName: 'App Name', field: 'name', rowDrag: true, headerCheckboxSelection: true, checkboxSelection: true},
{headerName: 'App Id', field: 'id'},
{headerName: 'Compatibility', field: COMPATIBILITY'},
{headerName: 'Creation', field: 'createdAtToString'},
{headerName: 'Last Update', field: 'updatedAtToString'}
];
}
deleteRow()
{
let ids = this.gridApi.getSelectedNodes()
// .map((row) => {
// return {id: row.entityId}
// return row.entityId;
// });
console.log(ids);
this.grid.api.applyTransaction({remove: ids});
}
I tried both with and without map statement, nothing worked
but my Add and Update works fine.
Replace map with following code.
.map((row) => {
return {entityId: row.data.entityId};
});
it should be the the same field (entityId) which i set in getRowNodeId function.
In a typical situation, where one does not define a getRowNodeId, one should be able to do:
const removeData: any[] = [{id: rowNode0.id}, {id: rowNode1.id}, ...];
applyTransaction({remove: removeData});
where rowNode0, rowNode1, etc. are the nodes you want to remove.
However when you provide your own getRowNodeId callback, ag-grid will fetch the id's by applying your callback on the data you provided. Therefore, the name(s) in the data must match those used in your callback. That's why return {id: row.entityId} doesn't work, but return {entityId: row.entityId} does.
In other words, if one defines:
this.grid.getRowNodeId = (data) => {
return data.column1 + data.column5 + data.column2;
};
Then one would need to provide
const removeData: any[] = [
{column1: 'a1', column2: 'b1', column5: 'c1'},
{column1: 'a2', column2: 'b2', column5: 'c2'},
{column1: 'a3', column2: 'b3', column5: 'c3'},
];
so that ag-grid would have all the names it needs to find the id's via the given getRowNodeId.

Google Chart: How do I sort by category filter with chronological order (by month)?

I have a category filter that populates month name with alphabetical order. I would like to display the months by chronological order (January, February, March, etc.) and also I would like to set current month name as default in the dropdown. I can not tweak the SQL by ORDER BY field, instead, I would like to do it from category filter.
Code:
var filterFrequencyData = new google.visualization.ControlWrapper(
{
'controlType': 'CategoryFilter',
'containerId': 'filterFrequencyDataHtml',
'options':
{
'filterColumnIndex': '5',
'ui':
{
'label': '',
'labelSeparator': ':',
'labelStacking': 'vertical',
'allowTyping': false,
'allowNone': false,
'allowMultiple': false,
'sortValues': false
}
}
});
When using sortValues: false on a CategoryFilter, the values will be sorted as they appear in the data.
In order to get the month names to sort in chronological order (January, February, March, etc...), you need to use a column type other than 'string' and sort that column, 'number' or 'date', for instance.
Then set the formatted value of the cell to the month name. For example:
{v: 0, f: 'January'}
or
{v: new Date(2016, 0, 1), f: 'January'}
You can also use the setFormattedValue method, if the cell already has a value:
data.setFormattedValue(0, 0, 'January');
once in place, the table can be sorted according to the 'number' or 'date':
data.sort({column: 0});
See the following working snippet, a 'date' column is used to sort the month names:
google.charts.load('current', {
callback: function () {
var data = new google.visualization.DataTable({
cols: [{
label: 'Month',
type: 'date'
}]
});
// load months in reverse
var formatDate = new google.visualization.DateFormat({pattern: 'MMMM'});
var today = new Date();
var monthCount = 12;
var selectedRow;
var rowIndex;
while (monthCount--) {
// get row values
var monthDate = new Date(today.getFullYear(), monthCount, 1);
var monthName = formatDate.formatValue(monthDate);
// use object notation when setting value
rowIndex = data.addRow([{
// value
v: monthDate,
// formatted value
f: monthName
}]);
// set selected row
if (monthName === formatDate.formatValue(today)) {
selectedRow = rowIndex;
}
}
// sort data
data.sort({column: 0});
var dash = new google.visualization.Dashboard(document.getElementById('dashboard'));
var control = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'control_div',
options: {
filterColumnIndex: 0,
ui: {
allowMultiple: false,
allowNone: false,
allowTyping: false,
label: '',
labelStacking: 'vertical',
sortValues: false
},
// use month name
useFormattedValue: true
},
// state needs formatted value
state: {
selectedValues: [data.getFormattedValue(selectedRow, 0)]
}
});
// or set state here -- just need month name
control.setState({selectedValues: [formatDate.formatValue(today)]});
var chart = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'chart_div',
options:{
allowHtml: true
}
});
dash.bind(control, chart);
dash.draw(data);
},
packages: ['controls', 'corechart', 'table']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="dashboard">
<div id="control_div"></div>
<div id="chart_div"></div>
</div>

Google chart table formatting cell as percentage

I am trying to format a cell in a google chart table as a percentage field.
For a column it works with :
var flow_format2 = new google.visualization.NumberFormat( {suffix: '%', negativeColor: 'red', negativeParens: true, fractionDigits: 0} );
But as far as I can read there is no possibility for a row, therefore I would like to do it on cell level - is that possible?
Is it with setProperty I need to do it and what is the formatting syntax.
you can use the formatValue method of NumberFormat to get the formatted string
rather than applying to the entire column
then you can manually setFormattedValue on the DataTable cell
to change the color, use setProperty to change the cell's 'style' property
the chart must be drawn afterwards
--or--
when the chart's 'ready' event fires, you can change the cell value using the DOM
the Table chart produces a normal set of html <table> tags
following is a working snippet, demonstrating both approaches...
google.charts.load('current', {
callback: function () {
var dataTable = new google.visualization.DataTable({
cols: [
{label: 'Name', type: 'string'},
{label: 'Amount', type: 'number'},
],
rows: [
{c:[{v: 'Adam'}, {v: -1201}]},
{c:[{v: 'Mike'}, {v: 2235}]},
{c:[{v: 'Stephen'}, {v: -5222}]},
{c:[{v: 'Victor'}, {v: 1288}]},
{c:[{v: 'Wes'}, {v: -6753}]}
]
});
var container = document.getElementById('chart_div');
var tableChart = new google.visualization.Table(container);
var patternFormat = {
suffix: '%',
negativeColor: '#FF0000',
negativeParens: true,
fractionDigits: 0
};
// create the formatter
var formatter = new google.visualization.NumberFormat(patternFormat);
// format cell - first row
dataTable.setFormattedValue(0, 1, formatter.formatValue(dataTable.getValue(0, 1)));
if (dataTable.getValue(0, 1) < 0) {
dataTable.setProperty(0, 1, 'style', 'color: ' + patternFormat.negativeColor + ';');
}
google.visualization.events.addOneTimeListener(tableChart, 'ready', function () {
// format cell via DOM - third row
var tableCell = container.getElementsByTagName('TR')[3].cells[1];
tableCell.innerHTML = formatter.formatValue(dataTable.getValue(2, 1));
if (dataTable.getValue(2, 1) < 0) {
tableCell.style.color = patternFormat.negativeColor;
}
});
tableChart.draw(dataTable, {
allowHtml: true
});
},
packages: ['table']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Map Reduce Mongodb Node JS native driver

I'm working with the Mongodb native driver using a Map reduce function. Basically I have a mediaId as a key and want to count how many medias loaded and started per mediaId.
So what I've done was:
var map = function(){
emit(this.media.id, {
count: 1,
played: 0,
ph: this.project.id,
title: this.media.title,
media: this.media.id,
origin: this.origin,
thumbnail: this.media.thumbnail,
mediaDuration: this.media.mediaDuration,
state: this.state
});
};
var reduce = function(k, vals) {
result = {
count: 0,
played: 0,
ph: '',
title: '',
media: '',
origin: '',
thumbnail: '',
mediaDuration: 0,
state: ''
};
vals.forEach(function(doc){
result.count += doc.count;
result.ph = doc.ph;
result.title = doc.title;
result.media = doc.media;
result.thumbnail = doc.thumbnail;
result.mediaDuration = doc.mediaDuration;
result.state = doc.state;
result.origin = doc.origin;
if(doc.state === "started") {
result.played += 1;
}
});
return result;
};
In my test collection I have 2 different mediaIds. One with 553 objects and another one with just 1 object. I've putted all in the "started" state to test this so basically the number of count should be equal to the number of played.
When I run the Map/Reduce function it returns to me ( I used the "toArray" function of the mongodb native driver):
[ { _id: '12398asdsa9802193810asd120',
value:
{ count: 1,
played: 0,
ph: '123213ased12231',
title: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
media: '1xxxxxxxxxxxxxxxxxxxxxxxxxxx1',
origin: 'http://www.google.com',
thumbnail: 'http://cache.ohinternet.com/images/0/0e/Forever_Alone.png',
mediaDuration: 12321321,
state: 'started' } },
{ _id: '2c9f94b42f5b5114012f5b92ea430066',
value:
{ count: 553,
played: 155,
ph: '316',
title: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
media: '2xxxxxxxxxxxxxxxxxxxxxxxxxxx2',
origin: 'http://localhost:9000/views/index.html',
thumbnail: null,
mediaDuration: null,
state: 'started' } } ]
It seems that one I have just one object the reduce function isn't called ( I did some tests with another collection with more than 100 mediaIds and the behavior was identical. Does anyone have an idea of what is wrong with that?
Thanks A LOT for your time,
Cheers.
I sort of solved the "issue".
I did the filter on the Map Function and not on the Reduce function. Something like this:
var map = function(){
if(this.media.state==="started") {
var played = 1;
}else{var played = 0;}
emit(this.media.id, {
count: 1,
played: played,
ph: this.project.id,
title: this.media.title,
media: this.media.id,
origin: this.origin,
thumbnail: this.media.thumbnail,
mediaDuration: this.media.mediaDuration,
state: this.state
});
};
Hope it helps anyone that is having the same "problem"