How can I use a Sequel SQL function in an insert statement? - sql

I would like to use a SQL function when inserting a new value into a dataset. However, I keep getting TypeError: can't convert Sequel::SQL::Function into String errors. The Sequel docs explain how to select using functions, but not insert.
Ideally I would like to avoid DB.run since I am inserting dynamically and raw SQL is messy and inflexible.
This is what I'm trying to do:
INSERT INTO dataset (col1, col2, col3) VALUES ('something', 3, func(value))
This is what I have in Sequel:
x = 'something'
y = 3
func_value = Sequel.function(:func, value)
DB[:dataset].insert (:col1 => x, :col2 => y, :col3 => func_value)
Is this possible? If so, what am I missing?

Figured it out. Create a Sequel::function object, and then make it the value in your hash.
irb(main):028:0> tbl
=> #<Sequel::MySQL::Dataset: "SELECT * FROM `foo`.`bar`">
irb(main):029:0> uhex = Sequel.function(:unhex,7)
=> #<Sequel::SQL::Function #args=>[7], #f=>:unhex>
irb(main):030:0> tbl.insert_sql( {:field_name => uhex })
=> "INSERT INTO `foo`.`bar` (`field_name`) VALUES (unhex(7))"

Looks like there a few different ways syntactically of calling SQL functions in Sequel:
http://sequel.rubyforge.org/rdoc/files/doc/dataset_filtering_rdoc.html

Related

Prisma batch upsert with raw SQL

I would like to do a batch upsert for a table in postgres. Since prisma doesnt support this in its api, i have to use $executeRaw. I am a little stuck though on how to properly use Prisma.join and Prisma.sql for inserting the data into the template tag.
This post seems to indicate a possible solution: https://github.com/prisma/prisma/discussions/15452#discussioncomment-3737632, however the function signature of Prisma.sql is export declare function sqltag(strings: ReadonlyArray<string>, ...values: RawValue[]): Sql; which indicates it takes two arrays rather than a string.
This is what i have so far:
async function batchUpsertPosts(posts){
await prisma.$executeRaw`INSERT INTO Posts (
feedDomain,
postId,
title,
score,
)
VALUES (), (), ()--... and so on with posts
ON CONFLICT ("feedDomain","postId") DO UPDATE SET score = excluded.score`
}
Ok, I think I managed to figure it out.
async function batchUpsertPosts(posts) {
await prisma.$executeRaw`INSERT INTO "public"."Posts" (
"feedDomain",
"postId",
"title",
"score",
)
VALUES ${Prisma.join(
posts.map(post =>
Prisma.sql`(${post.feedDomain}, ${post.postId}, ${post.title} ,${post.score})`
)
)}
ON CONFLICT ("feedDomain","postId") DO UPDATE SET score = excluded.score`
}
So i just needed to use:
Prisma.sql``

Writing dataframe to Postgres database psycopg2

I am trying to write a pandas DataFrame to a Postgres database.
Code is as below:
dbConnection = psycopg2.connect(user = "user1", password = "user1", host = "localhost", port = "5432", database = "postgres")
dbConnection.set_isolation_level(0)
dbCursor = dbConnection.cursor()
dbCursor.execute("DROP DATABASE IF EXISTS FiguresUSA")
dbCursor.execute("CREATE DATABASE FiguresUSA")
dbCursor.execute("DROP TABLE IF EXISTS FiguresUSAByState")
dbCursor.execute("CREATE TABLE FiguresUSAByState(Index integer PRIMARY KEY, Province_State VARCHAR(50), NumberByState integer)");
for i in data_pandas.index:
query = """
INSERT into FiguresUSAByState(column1, column2, column3) values('%s',%s,%i);
""" % (data_pandas['Index'], data_pandas['Province_State'], data_pandas['NumberByState'])
dbCursor.execute(query)
When I run this, I get an error which just says : "Index". I know its somewhere in my for loop is the problem, is that % notation correct? I am new to Postgres and don't see how that could be correct syntax. I know I can use to_sql but I am trying to use different techniques.
Print out of data_pandas is as below:
One slight possible anomaly is that there an "index" in the IDE version. Could this be the problem?
If you use pd.DataFrame.to_sql, you can supply the index_label parameter to use that as a column.
data_pandas.to_sql('FiguresUSAByState', con=dbConnection, index_label='Index')
If you would prefer to stick with the custom SQL and for loop you have, you will need to reset_index first.
for row in data_pandas.reset_index().to_dict('rows'):
query = """
INSERT into FiguresUSAByState(index, Province_State, NumberByState) values(%i, '%s', %i);
""" % (row['index'], row['Province_State'], row['NumberByState'])
Note that the default name for the new column is index, uncapitalized, rather than Index.
In the insert statement:
query = """
        INSERT into FiguresUSAByState (column1, column2, column3) values ​​('%s',%s,%i);
        """% (data_pandas ['Index'], data_pandas ['Province_State'], data_pandas ['NumberByState'])
You have a '%s', I think that is the problem. So remove the quotes

Make an IN statement using two attributes in Activerecord

I've been trying this for a while, and can't seem to get it right in Activerecord.
Given an array of asset_id and asset_type pairs, query a class that has both those attributes, only where both asset_id and asset_type match.
So given the array
[[4,"Logo"],[1,"Image"]]
I want to generate the SQL
SELECT "asset_attachments".* FROM "asset_attachments" WHERE ((asset_id,asset_type) IN ((4,'Logo'),(1,'Image')))
I can do this by manually entering a string using where like this:
AssetAttachment.where("(asset_id,asset_type) IN ((4,'Logo'),(1,'Image'))")
But I'm trying to use it with an array of any length and asset type/id.
So far I've tried
AssetAttachment.where([:asset_id, :asset_type] => [[4,"Logo"],[1,"Image"]])
NoMethodError: undefined method `to_sym' for [:asset_id, :asset_type]:Array
and
AssetAttachment.where("(asset_id,asset_type)" => [[4,"Logo"],[1,"Image"]])
ActiveRecord::StatementInvalid: PG::Error: ERROR: column asset_attachments.(asset_id,asset_type) does not exist
and
AssetAttachment.where("(asset_id,asset_type) IN (?,?)",[[4,"Logo"],[1,"Image"]])
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 2) in: (asset_id,asset_type) IN (?,?)
Does anyone know how to do this? Thanks in advance
set vs. array
The core of the problem is: you are mixing sets and arrays in an impossible way.
elem IN (...) .. expects a set.
elem = ANY(...) .. expects an array.
You can use unnest() to transform an array to a set.
You can use the aggregate function array_agg() to transform a set to an array.
Errors
Here, you are trying to form an array from (asset_id, asset_type):
AssetAttachment.where([:asset_id, :asset_type] => [[4,"Logo"],[1,"Image"]])
.. which is impossible, since arrays have to consist of identical types, while we obviously deal with a numeric and a string constant (you kept the actual types a secret).
Here, you force "(asset_id, asset_type)" as single column name by double-quoting it:
AssetAttachment.where("(asset_id,asset_type)" => [[4,"Logo"],[1,"Image"]])
And finally, here you try provide a single bind variable for two ?:
AssetAttachment.where("(asset_id,asset_type) IN (?,?)",[[4,"Logo"],[1,"Image"]])
Valid SQL
In pure SQL, either of these work:
SELECT * FROM asset_attachments
WHERE (asset_id, asset_type) IN ((4, 'Logo'), (1, 'Image'));
SELECT * FROM asset_attachments
WHERE (asset_id, asset_type) IN (VALUES(4, 'Logo'), (1, 'Image'));
SELECT * FROM asset_attachments
WHERE (asset_id, asset_type) = ANY (ARRAY[(4, 'Logo'), (1, 'Image')]);
If you have a long list of possible matches, an explicit JOIN would prove faster:
SELECT *
FROM asset_attachments
JOIN (VALUES(4, 'Logo'), (1, 'Image')) AS v(asset_id, asset_type)
USING (asset_id, asset_type)
Valid syntax for AR
I am an expert with Postgres, with AR not so much. This simple form might work:
AssetAttachment.where("(asset_id,asset_type) IN ((?,?),(?,?))", 4,"Logo",1,"Image")
Not sure if this could work, not sure about single or double quotes either:
AssetAttachment.where((:asset_id, :asset_type) => [(4,'Logo'),(1,'Image')])

SQL::Abstract - Matching Value Against Column Operation

Using SQL::Abstract, how would I build up this query:
SELECT * FROM mytable WHERE col1*col2 = 4;
Can't for the life of me find any examples on how to make the left side of the operation something custom rather than just a column.
Best I can do is get SQL::Abstract to pass the col1*col2 with quotes around it, but then that breaks the multiplication.
Edit: The best solution would be one that would take any kind of operation on the left side, not just the multiplication example. For example, if I wanted to do something slightly more complicated: (col1*col2)-col3.
Does this do what you need?
use v5.16;
use warnings;
use SQL::Abstract;
my $sqlab = SQL::Abstract->new;
my ($stmt, #bind) = $sqlab->select('mytable', '*', { '(col1*col2)-col3' => 4 });
use Data::Dump;
dd $stmt;
dd #bind;
output
"SELECT * FROM mytable WHERE ( (col1*col2)-col3 = ? )"
4
The doc leads me to think this is how you do it, but I haven't tried:
'col1' => { '*' => { -ident => 'col2' } }
but I'm not sure how you then compare that to 4.

Concatenate (glue) where conditions by OR or AND (Arel, Rails3)

I have several complex queries (using subqueries, etc...) and want to glue them together with OR or AND statement.
For example:
where1=table.where(...)
where2=table.where(...)
I would like something like
where3=where1.or where2
Next example doesn't work for me:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
because of I have several where(..) queries and I want to concatenate them.
In other words
I have 3 methods: first return first where, second-second, third - OR concatenation.
I must have able to use all 3 methods in my application and save DRY code
are you looking for the form:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
docs: https://github.com/rails/arel
users.where(users[:name].eq('bob').or(users[:age].lt(25))) is close, but you need to get the arel_table to specify the columns, e.g.
t = User.arel_table
User.where(t[:name].eq('bob').or(t[:age].lt(25)))
I know that for AND concatenation you can do:
users = User.where(:name => 'jack')
users = users.where(:job => 'developer')
and you get a concatenation with AND in SQL (try it with #to_sql at the end)
Other than that you can do:
where1=table.where(...)
where2=table.where(...)
where1 & where2
example:
(User.where(:name => 'jack') & User.where(:job => 'dev')).to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`name` = 'jack' AND `users`.`job` = 'dev'"