How can the following postgresql query be written using the npm pg-promise package?
update schedule
set student_id = 'a1ef71bc6d02124977d4'
where teacher_id = '6b33092f503a3ddcc34' and (start_day_of_week, start_time) in (VALUES ('M', (cast('17:00:00' as time))), ('T', (cast('19:00:00' as time))));
I didn't see anything in the formatter namespace that can help accomplish this. https://vitaly-t.github.io/pg-promise/formatting.html
I cannot inject the 'cast' piece into the '17:00:00' value without it being considered part of the time string itself.
The first piece of the query is easy. It's the part after VALUES that i can't figure out.
First piece:
var query = `update schedule
set student_id = $1
where teacher_id = $2 and (start_day_of_week, start_time) in (VALUES $3)`;
var inserts = [studentId, teacherId, values];
I'm using this messiness right now for $3 (not working yet), but it completely bypasses all escaping/security built into pg-promise:
const buildPreparedParams = function(arr, colNames){
let newArr = [];
let rowNumber = 0
arr.forEach((row) => {
const rowVal = (rowNumber > 0 ? ', ' : '') +
`('${row.startDayOfWeek}', (cast('${row.startTime}' as time)))`;
newArr.push(rowVal);
});
return newArr;
};
The structure I am trying to convert into this sql query is:
[{
"startTime":"17:00:00",
"startDayOfWeek":"U"
},
{
"startTime":"16:00:00",
"startDayOfWeek":"T"
}]
Use CSV Filter for the last part: IN (VALUES $3:csv).
And to make each item in the array format itself correctly, apply Custom Type Formatting:
const data = [{
startTime: '17:00:00',
startDayOfWeek: 'U'
},
{
startTime: '16:00:00',
startDayOfWeek: 'T'
}];
const values = data.map(d => ({
toPostgres: () => pgp.as.format('(${startDayOfWeek},(cast(${startTime} as time))', d),
rawType: true
}));
Now passing in values for $3:csv will format your values correctly:
('U',(cast('17:00:00' as time)),('T',(cast('16:00:00' as time))
Related
I'm new to postgresql function, wondering how to we access the value from a customized type inside postgresql?
For example the following example, BreakoutMatchingsDecrementProps is the customized type and I'm not able to use record.communityId to get the value...
CREATE TYPE BreakoutMatchingsDecrementProps AS (
weekId int,
communityId varchar,
timeSlot int,
groupSize breakoutsize,
day weekday
);
create or replace function Test (obj BreakoutMatchingsDecrementProps[])
returns void as
$$
DECLARE
record BreakoutMatchingsDecrementProps;
BEGIN
FOREACH record IN ARRAY obj LOOP
update breakout_matching
set number_of_registration = number_of_registration - 1
where week_id = 1
AND community_id = record.communityId
AND time_slot = 1
AND group_size = 'Two'
AND day = 'Monday'
AND number_of_registration > 0;
END
END
$$
language plpgsql;
I'm using Supabase and client side looks like:
const { data, error } = await adminClient.rpc(
"test",
{
obj:
{
timeSlot: 1,
groupSize: "Two",
day: "Monday",
communityId: "dshjdfdfak",
weekId: 1,
},
}
);
When I changed the record.communityId to "dshjdfdfak" then it works...
Wondering how do we access customized type object in plpgsql? Thanks!
I am trying to build a sql statement dynamically and this is one variation of the result.
sqlQuery {
name: 'fetch-products',
text: 'select * from products where category =
$1 and designer in $2',
values: [ 'WOMENSCLOTHING', "('Adjavon', 'ALC', 'Adele', 'Bagley')" ]
}
I build the sql with the following code segment:
const {
category,
designers,
} = JSON.parse(filters);
let values = [category];
let text = 'select * from products where category = $1';
if(designers) {
text = text + ' and designer in $2';
values.push(designers);
}
I execute it in the following segment:
try {
const allProducts = await pool.query(sqlQuery);
res.status(200).json(allProducts.rows);
} catch (error) {
console.error(error);
return res.status(500).send('Problems gettting products by category.')
}
And get the following error:
error: syntax error at or near "$2"
I am thinking the error may be the double quotes placed around designer when it is pushed on the values array:
values: [ 'WOMENSCLOTHING', "('Adjavon', 'ALC', 'Adele', 'Bagley')" ]
I don't know what library you are using exactly, but the values property looks highly suspicious.
sqlQuery {
name: 'fetch-products',
text: 'select * from products where category =
$1 and designer in $2',
values: [ 'WOMENSCLOTHING', "('Adjavon', 'ALC', 'Adele', 'Bagley')" ]
}
If your drivr/library supports this, the second element in the array should be an actual array and not a string like '("foo", "bat")'. How is the driver supposed to know this is meant as a list and not a single string that has this value?
I guess in a nutshell you have to bring the query in this form:
const query = 'select * from products where category = $1 and designer in ($2, $3, $4, $5)'
const values = [ 'WOMENSCLOTHING', 'Adjavon', 'ALC', 'Adele', 'Bagley' ]
That requires some extra work on the backend to map the values and bring them into the right shape.
I guess you could get that done with something like this:
const category = 'icecream'
const designers = ['jim', 'maria']
let values = [category];
let text = 'select * from products where category = $1';
if (designers) {
text += ` and designer in (${designers.map((d, i) => `$${i+2}`).join(', ')})`;
values = [...values, ...designers];
}
console.log(text);
console.log(values);
I'm to insert a record with embedded field in OrientDB and then to query for that record using filter:
insert into MyClass set embeddedField = {'#type': 'd', 'id': 1}
works well, and
select from MyClass
returns the record I've added. But when I add where with filter for embeddedField, I get no results:
select from MyClass where embdeddedField = {'#type': 'd', 'id': 1}
I thought that it could happen because Orient adds #version field into embedded document, so I tried to search with version:
select from MyClass where embdeddedField = {'#type': 'd', '#version': 0, 'id': 1}
But still got no results.
Question: Any idea how to filter embedded field by entire document? Without the need to filter explicitly by each field of embedded document:
select from MyClass
where embdeddedField.id = 1
and embdeddedField.field2 = val2
and embeddedField.field3 = val3
Because for several reasons I would like to pass the entire object as single query parameter:
select from MyClass where embdeddedField = ?
Thats because the provided Hash is transformed to an embedded class.
Perhaps take a look at the ActiveOrientWiki https://github.com/topofocus/active-orient/wiki/Relations
To explain:
Take a class base, there a document with two properties (schemaless)
and add existing documents to it
( 0..9 ).each do | b |
base_record= Base.create label: b, first_list: []
( 0..9 ).each {|c| base_record.first_list << FirstList.create( label: c ) }
end
INFO->CREATE VERTEX base CONTENT {"label":0,"first_list":[]}
INFO->CREATE VERTEX first_list CONTENT {"label":0}
INFO->update #136:0 set first_list = first_list || [#147:0] return after #this
then links are embedded and the record looks like
=> #<Base:0x00000000041a60e0 #metadata={:type=>"d", :class=>"base", :version=>11, :fieldTypes=>"first_list=z", :cluster=>129, :record=>1},
#attributes={:first_list=>["#149:1", "#150:1", "#151:1", "#152:1", "#145:2", "#146:2", "#147:2", "#148:2", "#149:2", "#150:2"], :label=>"1"}>
if you expand the list items, you can query for "#type"
If you add a document without rid, the same thing happens
( 0..9 ).each {|c| base_record.first_list << FirstList.new( label: c ) }
20.04.(12:53:59) INFO->update #130:2 set first_list = first_list || [{ #type: 'd' ,#class: 'first_list' ,label: 0 }] return after #this
INFO->CREATE VERTEX base CONTENT {"label":"c","first_list":[]}
'#130:2'.expand
=> #<Base:0x00000000043927a0 #metadata={:type=>"d", :class=>"base", :version=>11, :fieldTypes=>"first_list=z", :cluster=>130, :record=>2},
#attributes={:first_list=>["#151:12", "#152:12", "#145:13", "#146:13", "#147:13", "#148:13", "#149:13", "#150:13", "#151:13", "#152:13"], :label=>"c"}>
If you omit the class-name, only the scope of the rid's changes
INFO-> update #132:2 set first_list = first_list || { #type: 'd' ,label: 0 } return after #this
=> [#<Base:0x0000000003a26fb8 #metadata={:type=>"d", :class=>"base", :version=>3, :fieldTypes=>"first_list=z", :cluster=>132, :record=>2},
#attributes={:first_list=>["#3:0"], :label=>"d"}>]
In any case, a query for #type failes
var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction")
join diary in repository.GetQuery<OnlineDiary.Internal.Model.OnlineDiary>()
on entry.DiaryId equals diary.Id
group entry
by diary
into diaryEntriesGroup
select new { Diary = diaryEntriesGroup.Key,
DiaryEntry = diaryEntriesGroup.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault(),
});
This query does not include "DiaryEntryGradeChangeLog" and "DiaryEntryAction" navigation properties, what is wrong in this query?
I have removed join from the query and corrected as per below, and still it populates nothing
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog").Include("DiaryEntryAction")
.Where(e => 1 == 1)
group entry
by entry.OnlineDiary
into diaryEntryGroups
select
new { DiaryEntry = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault() };
It will not. Include works only if the shape of the query does not change (by design). If you use this query it will work because the shape of the query is still same (OnlineDiary.Internal.Model.DiaryEntry):
var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction");
But once you use manual join, grouping or projection (select new { }) you have changed the shape of the query and all Include calls are skipped.
Edit:
You must use something like this (untested) to get related data:
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
group entry by entry.OnlineDiary into diaryEntryGroups
let data = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault()
select new {
DiaryEntry = data,
GradeChangeLog = data.DiaryEntryGradeChangeLog,
Action = data.DiaryEntryAction
};
or any similar query where you manually populate property for relation in projection to anonymous or unmapped type.
I've created my select:
$select = $zdb->select()
->from(array("b" => "blogs"),
array("id", "active", "updated_by", "title", "synopsis", "create_date", "body"))
->join(array("u" => "users"),
'b.updated_by = u.id',
array("first_name", "last_name"))
->where("u.blogger = ?", 1)
->where("b.publish_date > ?", '2020-01-01')
->where("b.active = ?", 1)
->group("b.id")
->order("b.publish_date DESC")
->limit(5);
and I want to pull the data back a row at a time:
$stmt = $db->query($select);
while ($asset = $stmt->fetch()) {
// do stuff
}
How can I check to make sure that there are rows, without returning the entire resultset?
Using the select you already have, something like that should help you parse every entry
$rows = $zdb->fetchAll($select);
foreach($rows as $row){
...
}
To get the values you just have to do $row['fieldName'] where fieldName is the name of the field in your database