How do I select a subset of columns with diesel-rs? - postgresql

I am struggling for several hours now to query a subset of the available columns of a table as well as including a calculation in it. I know that it is not the best way to execute the calculation in the select query, but for now, I am just working on a prototype and it should be feasible for that.
I am using diesel-rs as a ORM for all my database actions in my back-end implementation. The data will be stored in a PostgresSQL server. The full table - as stored in the database - is created using the following query:
CREATE TABLE airports
(
id SERIAL PRIMARY KEY,
icao_code VARCHAR(4) NOT NULL UNIQUE, -- the official ICAO code of the airport
last_update TIMESTAMP NOT NULL, -- when were the information updated the last time?
country VARCHAR(2) NOT NULL, -- two letter country code
longitude REAL NOT NULL, -- with 6 decimal places
latitude REAL NOT NULL, -- with 6 decimal places
name VARCHAR NOT NULL -- just a human readable name of the airport
);
Running diesel migrations run generates the airports table definition and querying the database works without any issue.
Now I am trying to query a list of all airports (their ICAO code) with the corresponding coordinates as well as the distance to a supplied coordinate. Therefore, I created the following diesel-rs table! macro myself
table! {
airport_by_distance (icao_code) {
icao_code -> Varchar,
longitude -> Float8,
latitude -> Float8,
distance -> Float8,
}
}
as well as the struct corresponding to the diesel-rs definition:
#[derive(QueryableByName)]
#[table_name = "airport_by_distance"]
struct AirportByDistance {
icao_code: String,
longitude: f64,
latitude: f64,
distance: f64,
}
The following snipped - as of my understanding - should query the required information:
use diesel::dsl::sql_query;
let latitude = 4.000001;
let longitude = 47.000001;
let query_sql = format!("SELECT icao_code, longitude, latitude, (3959.0 * acos(cos(radians({lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians({long})) + sin(radians({lat})) * sin(radians(latitude)))) AS distance FROM airports ORDER BY distance;", lat=latitude, long=longitude);
let result = match sql_query(query_sql).load::<AirportByDistance>(database_connection) {
Ok(result) => result,
Err(error) => {
error!("{:?}", error);
return Err(());
}
};
Unfortunately, executing the load method, results in a DeserializationError(Custom { kind: UnexpectedEof, error: "failed to fill whole buffer" }) error.
The query which was executed was:
SELECT icao_code,
longitude,
latitude,
(3959.0 * acos(cos(radians(4.000001)) * cos(radians(latitude)) * cos(radians(longitude) - radians(47.000001)) +
sin(radians(4.000001)) * sin(radians(latitude)))) AS distance
FROM airports
ORDER BY distance;
I took it and executed it manually, but it worked flawlessly. I even tried removing the calculation and just selecting a subset of columns, but with no luck either.
Right now I am not sure what I am doing wrong. How can I fix this issue?
EDIT with the fixed code: For the ones who are interested how to code would look like after using the helpful advice of Rasmus:
The table! macro is completely gone and the data definition struct looks like this:
#[derive(Queryable)]
struct AirportByDistance {
icao_code: String,
longitude: f32,
latitude: f32,
distance: f64,
}
The code for the querying the database is as follows:
let result = match airports.select(
(
icao_code,
longitude,
latitude,
sql::<Double>(
&format!("(3959.0 * acos(cos(radians({lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians({long})) + sin(radians({lat})) * sin(radians(latitude)))) AS distance", lat=latitude_reference, long=longitude_reference)
)
)
).load::<AirportByDistance>(database_connection)
{
Ok(result) => result,
Err(error) => {
error!("{:?}", error);
return Err(());
}
};
for airport in result {
info!(
"AIRPORT: {} has {}nm distance",
airport.icao_code, airport.distance
);
}

I think the problem is that the deserializer don't know the raw types of the queried columns.
Try to use typed diesel names/values as much as possible, and explicit sql strings only where needed. And I don't think the "fake" table declaration airports_by_distance helps. Maybe something like this:
use diesel::sql_types::Double;
let result = a::airports
.select((
a::icao_code,
a::longitude,
a::latitude,
sql::<Double>(&format!("(3959.0 * acos(cos(radians({lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians({long})) + sin(radians({lat})) * sin(radians(latitude)))) AS distance", lat=latitude, long=longitue)
))
.load::<AirportByDistance>(&db)?
(Using the table! macro manually is basically just telling diesel that such a table will exist in the actual database when running the program. If that is not true, you will get runtime errors.)

Related

Oracle query to Postgress query change ERROR

We are in progress to move our db to Postgres from Orcale.
There is this Oracle query (changed verbiage for privacy concerns):
SELECT * FROM (SELECT somedata.* FROM SNSN.SMS_TXN_SOMEDATA somedata
WHERE ((car= 'tesla' OR car = 'teslaX' OR car = 'teslaY') OR (buyer= 'john' OR buyer = 'rony' OR jim = 'sam'))
AND code = :code
ORDER BY somedata.datetime)
WHERE LIMIT :num
When I hit the endpoint I get this error
ERROR: argument of LIMIT must be type bigint, not type character varying
What would be a suitable/alternative variable to bind this. What can I use instead of variables :code and :num.

How to use st_Line_Locate_Point() with a MULTILINESTRING convertion in PostGIS?

I am trying to get the index position of a POINT in a MULTILINESTRING.
Here is the whole query I'm stuck with :
SELECT req.id, (dp).geom, netgeo_point_tech.id, ST_Line_Locate_Point(st_lineMerge(geom_cable), (dp).geom)
FROM (SELECT id, ST_DumpPoints(geom) as dp, geom as geom_cable FROM netgeo_cable_test ) as req
JOIN netgeo_point_tech ON ST_dwithin(netgeo_point_tech.geom, (dp).geom, 1)
ORDER BY req.id, (dp).path [ 1] ASC
The error I get is : line_locate_point : 1st arg isnt a line.
The error is due to the return of st_lineMerge() function that is returning LINESTRING but also MULTILINESTRING.
I don't get this. st_lineMerge() is supposed to return only LINESTRING.ST_LineMerge()
When I jsut try a simple query like this :
select st_astext(st_linemerge(geom)) from netgeo_cable_test
The output is :
)
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
Thanks to #JGH for the suggestion to use ST_Dump I came up with this function:
create or replace function MultiLineLocatePoint(line geometry, point geometry) returns numeric as $$
select (base + extra) / ST_Length(line)
from (
select
sum(ST_Length(l.geom)) over (order by l.path) - ST_Length(l.geom) base,
ST_LineLocatePoint(l.geom, point) * ST_Length(l.geom) extra,
ST_Distance(l.geom, point) dist
from ST_Dump(line) l
) points
order by dist
limit 1;
$$ language SQL;

PreparedStatement slower than Statement with JDBC

I am currently working on weather monitoring.
For example a record of temperature has a date and a location (coordinates).
All of the coordinates are already in the database, what I need to add is time and the value of the temperature. Values and metadata are in a CSV file.
Basically what I'm doing is:
Get time through the file's name
Insert time into DB, and keep the primary key
Reading file, get the value and coordinates
Select query to get the id of the coordinates
Insert weather value with foreign keys (time and coordinates)
The issue is that the
"SELECT id FROM location WHERE latitude = ... AND longitude = ..."
is too slow. I have got 230k files and currently one file takes more than 2 minutes to be processed... Edit: by changing the index, it now takes 25 seconds and is still too slow. Moreover, the PreparedStatement is also still slower and I cannot figure out why.
private static void putFileIntoDB(String variableName, ArrayList<String[]> matrix, File file, PreparedStatement prepWeather, PreparedStatement prepLoc, PreparedStatement prepTime, Connection conn){
try {
int col = matrix.size();
int row = matrix.get(0).length;
String ts = getTimestamp(file);
Time time = getTime(ts);
// INSERT INTO takes 14ms
prepTime.setInt(1, time.year);
prepTime.setInt(2, time.month);
prepTime.setInt(3, time.day);
prepTime.setInt(4, time.hour);
ResultSet rs = prepTime.executeQuery();
rs.next();
int id_time = rs.getInt(1);
//for each column (longitude)
for(int i = 1 ; i < col ; ++i){
// for each row (latitude)
for(int j = 1 ; j < row ; ++j){
try {
String lon = matrix.get(i)[0];
String lat = matrix.get(0)[j];
String var = matrix.get(i)[j];
lat = lat.substring(1, lat.length()-1);
lon = lon.substring(1, lon.length()-1);
double latitude = Double.parseDouble(lat);
double longitude = Double.parseDouble(lon);
double value = Double.parseDouble(var);
// With this prepared statement, instruction needs 16ms to be executed
prepLoc.setDouble(1, latitude);
prepLoc.setDouble(2, longitude);
ResultSet rsLoc = prepLoc.executeQuery();
rsLoc.next();
int id_loc = rsLoc.getInt(1);
// Whereas this block takes 1ms
Statement stm = conn.createStatement();
ResultSet rsLoc = stm.executeQuery("SELECT id from location WHERE latitude = " + latitude + " AND longitude =" + longitude + ";" );
rsLoc.next();
int id_loc = rsLoc.getInt(1);
// INSERT INTO takes 1ms
prepWeather.setObject(1, id_time);
prepWeather.setObject(2, id_loc);
prepWeather.setObject(3, value);
prepWeather.execute();
} catch (SQLException ex) {
Logger.getLogger(ECMWFHelper.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
} catch (SQLException ex) {
Logger.getLogger(ECMWFHelper.class.getName()).log(Level.SEVERE, null, ex);
}
}
What I already did:
Set two B-tree index on table location on columns latitude and longitude
Drop foreign keys constraints
PreparedStatements in parameters are :
// Prepare selection for weather_radar foreign key
PreparedStatement prepLoc = conn.prepareStatement("SELECT id from location WHERE latitude = ? AND longitude = ?;");
PreparedStatement prepTime = conn.prepareStatement("INSERT INTO time(dataSetID, year, month, day, hour) " +
"VALUES(" + dataSetID +", ?, ? , ?, ?)" +
" RETURNING id;");
// PrepareStatement for weather_radar table
PreparedStatement prepWeather = conn.prepareStatement("INSERT INTO weather_radar(dataSetID, id_1, id_2, " + variableName + ")"
+ "VALUES(" + dataSetID + ", ?, ?, ?)");
Any idea to get things go quicker?
Ubuntu 16.04 LTS 64-bits
15.5 Gio
Intel® Core™ i7-6500U CPU # 2.50GHz × 4
PostgreSQL 9.5.11 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609, 64-bit
Netbeans IDE 8.2
JDK 1.8
postgresql-42.2.0.jar
The key issue you have here is you miss ResultSet.close() and Statement.close() kind of calls.
As you resolve that (add relevant close calls) you might find that having SINGLE con.prepareStatement call (before both for loops) would improve the performance even further (of course, you will not need to close the statement in a loop, however you still would need to close resultsets in a loop).
Then you might apply batch SQL
Using EXPLAIN, the point where query becomes latent could be figured out.
One of the situation where I have encountered case alike being:
Compound queries e.g. parameterized similar date ranges, from different tables and then joining them on some indexed value. Even if the date in the above serve as index still the query produced in preparedStatement, could not hit the indexes and ended up doing a scan over the joining data.

Hibernate: StoredProcedure with recursive depthsearch: Mapping/Output Problems

I searching for help. I have to map my Postgres 9.4 Database (DB) with Hibernate 5.2, of course it's an study task. The biggest Problem is, that I'm no brain in Hibernate, Java and coding itself XD
It's an SozialNetwork DB. To map the DB with Hibernate doing fine.
Now I should map a stored produce. This Produce should find the shortest friendship path between two persons. In Postgres the produce working fine.
That are the relevant DB-Tables:
For Person:
CREATE TABLE Person (
PID bigint NOT NULL,
firstName varchar(50) DEFAULT NULL,
lastName varchar(50) DEFAULT NULL,
(some more...)
PRIMARY KEY (PID)
);
And for the Relationship between to Persons:
CREATE TABLE Person_knows_Person (
ApID bigint NOT NULL,
BpID bigint REFERENCES Person (PID) (..)
knowsCreationDate timestamp,
PRIMARY KEY (ApID,BpID));
And that is the Stored Produce in short:
CREATE OR REPLACE FUNCTION ShortFriendshipPath(pid bigint, pid2 bigint)
RETURNS TABLE (a_pid bigint, b_pid bigint, depth integer, path2 bigint[], cycle2 boolean)
AS $$
BEGIN
RETURN QUERY
SELECT * FROM (
WITH RECURSIVE FriendshipPath(apid, bpid, depth, path, cycle) AS(
SELECT pkp.apid, pkp.bpid,1,
ARRAY[pkp.apid], false
FROM person_knows_person pkp
WHERE apid=$1 --OR bpid=$1
UNION ALL
SELECT pkp.apid, pkp.bpid, fp.depth+1, path || pkp.apid,
pkp.apid = ANY(path)
FROM person_knows_person pkp, FriendshipPath fp
WHERE pkp.apid = fp.bpid AND NOT cycle)
SELECT *
FROM FriendshipPath WHERE bpid=$2) AS OKOK
UNION
SELECT * FROM (
WITH RECURSIVE FriendshipPath(apid, bpid, depth, path, cycle) AS(
SELECT pkp.apid, pkp.bpid,1,
ARRAY[pkp.apid], false
FROM person_knows_person pkp
WHERE apid=$2 --OR bpid=$1
UNION ALL
SELECT pkp.apid, pkp.bpid, fp.depth+1, path || pkp.apid,
pkp.apid = ANY(path)
FROM person_knows_person pkp, FriendshipPath fp
WHERE pkp.apid = fp.bpid AND NOT cycle)
SELECT *
FROM FriendshipPath WHERE bpid=$1) AS YOLO
ORDER BY depth ASC LIMIT 1;
END;
$$ LANGUAGE 'plpgsql' ;
(Sorry for so much code, but it's for both directions, and before I post some copy+reduce misttakes^^)
The Call in Postgre for example:
SELECT * FROM ShortFriendshipPath(10995116277764, 94);
gives me this Output:
enter image description here
I use the internet for help and find 3 solutions for calling:
direct SQL call
call with NamedQuery and
map via XML
(fav found here)
I faild with all of them XD
I favorite the 1. solution with this call in session:
Session session = HibernateUtility.getSessionfactory().openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
System.out.println("Please insert a second PID:");
Scanner scanner = new Scanner(System.in);
long pid2 = Long.parseLong(scanner.nextLine());
// **Insert of second ID*/
Query query2 = session.createQuery("FROM " + Person.class.getName() + " WHERE pid = :pid ");
query2.setParameter("pid", pid2);
List<Person> listB = ((org.hibernate.Query) query2).list();
int cnt1 = 0;
while (cnt1 < listB.size()) {
Person pers1 = listB.get(cnt1++);
pid2 = pers1.getPid();
}
// Query call directly:
Query querySP = session.createSQLQuery("SELECT a_pid,path2 FROM ShortFriendshipPath(" + pid + "," + pid2 + ")");
List <Object[]> list = ((org.hibernate.Query) querySP).list();
for (int i=0; i<list.size();i++){
Personknowsperson friendship = (Personknowsperson)result.get(i);
}
} catch (Exception e) { (bla..)}
} finally { (bla....) }
Than I get following Error:
javax.persistence.PersistenceException:
org.hibernate.MappingException: No Dialect mapping for JDBC type: 2003
(..blabla...)
I understand why. Because my output is not of type Personknowsperson. I found an answer: that I have to say Hibernate what is the correct formate. And should use 'UserType'. So I try to find some explanations for how I create my UserType. But I found nothing, that I understand. Second Problem: I'm not sure what I should use for the bigint[] (path2). You see I'm expert -.-
Than I got the idea to try the 3.solution. But the first problem I had was where should I write the xml stuff. Because my Output is no table. So I try in the .cfg.xml but than Hibernate say that
Caused by: java.lang.IllegalArgumentException: org.hibernate.internal.util.config.ConfigurationException: Unable to perform unmarshalling at line number -1 and column -1 in RESOURCE hibernate.cfg.xml. Message: cvc-complex-type.2.4.a: Ungültiger Content wurde beginnend mit Element 'sql-query' gefunden. '{some links}' wird erwartet.
translation:
invalid content found starts with 'sql-query'
Now I'm a nervous wreck. And ask you.
Could someone explain what I have to do and what I did wrong (for dummies please). If more code need (java classes or something else) please tell me. Critic for coding also welcome, cause I want improve =)
Ok, I'm not an expert in postgressql, not hibernate, nor java. (I'm working with C#, SQL Server, NHibernate so ...) I still try to give you some hints.
You probably can set the types of the columns using addXyz methods:
Query querySP = session
.createSQLQuery("SELECT * FROM ShortFriendshipPath(...)")
.addScalar("a_pid", LongType.INSTANCE)
...
// add user type?
You need to create a user type for the array. I don't know how and if you can add it to the query. See this answer here.
You can also add the whole entity:
Query querySP = session
.createSQLQuery("SELECT * FROM ShortFriendshipPath(...)")
.addEntity(Personknowsperson.class)
...;
I hope it takes the mapping definition of the corresponding mapping file, where you can specify the user type.
Usually it's much easier to get a flat list of values, I mean a separate row for each different value in the array. Like this:
Instead of
1 | 2 | (3, 4, 5) | false
You would get:
1 | 2 | 3 | false
1 | 2 | 4 | false
1 | 2 | 5 | false
Which seems denormalized, but is actually the way how you build relational data.
In general: use parameters when passing stuff like ids to queries.
Query querySP = session
.createSQLQuery("SELECT * FROM ShortFriendshipPath(:pid1, :pid2)")
.setParameter("pid1", pid1)
.setParameter("pid2", pid2)
...

Re-Write complex query (COS, SIN, RADIANS, ACOS) into Entity-to-SQL

The following store procedure retrives nearest 500 addresses for the given latitude and longitude. Many applications use it, and it is one of the useful query.
Is it possible to rewrite with Entity-to-SQL? If so, could you please point me to the right direction (I am not new to Entity-to-SQL)? Thanks in advance.
DECLARE #CntXAxis FLOAT
DECLARE #CntYAxis FLOAT
DECLARE #CntZAxis FLOAT
SET #CntXAxis = COS(RADIANS(-118.4104684)) * COS(RADIANS(34.1030032))
SET #CntYAxis = COS(RADIANS(-118.4104684)) * SIN(RADIANS(34.1030032))
SET #CntZAxis = SIN(RADIANS(-118.4104684))
SELECT
500 *,
ProxDistance = 3961 * ACOS( dbo.XAxis(LAT, LONG)*#CntXAxis + dbo.YAxis(LAT, LONG)*#CntYAxis + dbo.ZAxis(LAT)*#CntZAxis)
FROM
tbl_ProviderLocation
WHERE
(3961 * ACOS( dbo.XAxis(LAT, LONG)*#CntXAxis + dbo.YAxis(LAT, LONG)*#CntYAxis + dbo.ZAxis(LAT)*#CntZAxis) <= 10)
ORDER BY
ProxDistance ASC
If you are using Ms Sql Server, you can use SqlClient functions with Entity SQL
http://msdn.microsoft.com/en-us/library/bb399586.aspx
According to this those functions are available for LINQ queries aswell. I couldn't find an example but it seems straightforward.
var qry = from r in mytable
select new {Acos = SqlFunctions.ACos(r.mycloumn)};