Rails is escaping my query incorrectly - mysql

So I am trying to handle a fairly complex query within active record. I am using rails 4.0 and mysql. The problem is, my query requires single quotes in the WHERE statement.
This is what the sql statement (WHERE portion) needs to end up looking like:
WHERE
`location_hours`.`open_at` <=
hour(CONVERT_TZ(now(), 'US/Mountain',`locations`.`time_zone`)) * 60 * 60
AND
`location_hours`.`close_at` >=
hour(CONVERT_TZ(now(), 'US/Mountain', `locations`.`time_zone`)) * 60 * 60
The issue is with 'US/Mountain'. I have tried the following two ways to make this work:
1.
string = "hour(CONVERT_TZ(now(), 'US/Mountain', `locations`.`time_zone`)) * 60 * 60";
filtered = filtered.joins(:hours)
.where("`location_hours`.`closed` = ?", false)
.where("`location_hours`.`open_at` <= %s AND `location_hours`.`close_at` >= %s",
string, string)
2.
filtered = filtered.joins(:hours)
.where("`location_hours`.`day` = weekday(CONVERT_TZ(now(), \"US/Mountain\", `locations`.`time_zone`))")
.where("`location_hours`.`closed` = ?", false)
.where("`location_hours`.`open_at` <= hour(CONVERT_TZ(now(), \"US/Mountain\", `locations`.`time_zone`)) * 60 * 60 AND `location_hours`.`close_at` >= hour(CONVERT_TZ(now(), \"US/Mountain\", `locations`.`time_zone`)) * 60 * 60")
The sql the second option generates (copy,pasted from console) I can literally drop as a raw query into phpmyadmin, and I get results back.
For some reason Rails is doing something funny with my query. Any pointers or suggestions would be greatly appreciated!
Thanks!!

The easiest way to get single quotes into your SQL is to just put them in there:
.where("location_hours.open_at <= hour(CONVERT_TZ(now(), 'US/Mountain', ...
If the timezone is in a variable then use a placeholder:
.where("location_hours.open_at <= hour(CONVERT_TZ(now(), :tz, ...", :tz => time_zone_string)
Keep in mind that an SQL snippet such as
string = "hour(CONVERT_TZ(now(), 'US/Mountain', `locations`.`time_zone`)) * 60 * 60";
is not an SQL string and should not be escaped as such. That means that sending it through a %s won't do the right thing, the %s will just make a mess of the quotes in the snippet.
BTW, you don't need to backtick-quote everything, only identifiers that are keywords, case sensitive, contain whitespace, etc. Backticking everything just makes an ugly mess.

Related

How to combine like and between in same statement (laravel Query Builder/model)

Following are the HDD column from the Computer model (I know it's not a good format to store data but it's already stored like that)
HDD
4x2TBSATA2
2x2TBSATA2
8x2TBSATA2
4x1TBSATA2
2x120GBSSD
4x480GBSSD
I want to fetch the range out of HDD column where storage is in a specific range, for example, fetch storage between 120GB to 1TB should output
4x1TBSATA2
2x120GBSSD
4x480GBSSD
I was wondering if it's possible to combine like and between in the same statement?
I tried the following which doesn't work.
select * from `server_details` where `HDD` between '%120GB%' and '%10TB%'
select * from `server_details` where `HDD` between "Like '%120GB%'" and "LIKE '%10TB%'"
If you need to do just in SQL, extract the size part, convert it to a number, and compare.
select *,
cast(`HDD` as unsigned)*
cast(substr(`HDD`,LOCATE('x',`HDD`)+1) as unsigned)*
(case when`HDD` LIKE '%TB%' then 1000 else 1 end) as GB
from `server_details`
where
cast(`HDD` as unsigned)*
cast(substr(`HDD`,LOCATE('x',`HDD`)+1) as unsigned)*
(case when`HDD` LIKE '%TB%' then 1000 else 1 end)
between 120 and 10000;
You can't use between with wildcard queries. You might be able to write a regular expression to match what you need, for example:
select * from `server_details` where `HDD` regexp '1[2-9]\dGB|[2-9]\d\dGB|\dTB|10TB'
but as you can see this is a very specific expression based on what you've written and each different limit will need a different expression.
There's some python code to generate such an expression but no PHP code that I could find (with some very basic googling)
Another solution (and what I would personally recommend) is to add the capacity as a separate column:
Migrate your current table:
class AddCapacityColumnMigration extends Migration {
public function up()
{
Schema::table('computers', function (Blueprint $table) {
$table->bigInt('capacityMB')->nullable();
});
Computer::chunk(100, function ($computers) {
foreach ($computers as $computer) {
if (preg_match('/(\d+)x(\d+)(M|G|T)B/',$computer->HDD,$m) {
$capacity = $m[1];
$capacity *= $m[3] === 'M' ? 1 : ($m[3] === 'G' ? 1000 : 1000000 );
$computer->capacityMB = $capacity * $m[2];
$computer->save();
}
}
});
}
Then you might want to add a creating and updating event in your model to ensure you always set the new capacityMB column. When all this is done your query is as simple as:
select * from `server_details` where `capacityMB` between 120000 and 10000000
In the database, in HDD column, you should not store alpha-numeric values like 120GB, 10TB, you should store numeric values like 120, 10000. Please try with the following query.
$hdds = DB::table('server_details')
->whereBetween('HDD', [120, 10000])
->get();

Using Peewee ORM with MySQL date math

I'm trying to convert an application that uses MySQLdb to use Peewee instead. Most of the SELECTs and INSERTs are no problem, but one class of query has me puzzled.
The original code contains:
sql = "SELECT * FROM {tbl} WHERE tail='{tail}' AND flight="+\
"'{flight}' AND dest='{dest}' AND orig='{orig}' AND "+\
"oooi='{oooi}' AND report_time > ('{time}' - INTERVAL 2 HOUR) "+\
"AND report_time < ('{time}' + INTERVAL 2 HOUR)"
cmd = sql.format(tbl = self.table, tail=tail, flight=flight, dest=dest,
orig=orig, time = report_time, oooi=oooi)
c.execute(cmd)
return c.fetchone()
Trying to rewrite that to use Peewee I've come up with:
oooi_rec = Oooi_rec.select().where(Oooi_rec.tail == self.tail,
Oooi_rec.flight == self.flight,
Oooi_rec.dest == self.dest, Oooi_rec.orig == self.orig,
Oooi_rec.oooi=self.oooi,
Oooi_rec.report_time.between(low, high))
The bit that replaces "low" and "high" is what has me mystified for now. I'm trying to puzzle out how to use Peewee's fn() but it's slow going.
Perhaps:
low - SQL('interval 2 hour')
Also, so much sql injection in the way you had previously...yeesh.

Return array of values instead of array of objects from SQL query in Rails

I have a SQL query in a Rails model that responds with an array of objects. And each object has an attribute called points with a value.
But its preferable for query to just return an array of points like [10,15,5] instead of [object,object,object] which requires then extracting the points out into another array to be useful.
Model file
LAST_3_SELECT = "
SELECT
(
(data.ap / (data.apa * 1.0))
+
(data.vp / (data.vpa * 1.0))
)
/ 2 * 1.5 * data.level
AS points
FROM data
WHERE data.user_id = ?
GROUP BY data.id
ORDER BY data.created_at DESC
LIMIT 3
"
def self.last_3(user_id)
connection.select_all(sanitize_sql_array( [LAST_3_SELECT, user_id]), "last-3")
end
Is this possible to do in a query itself, or necessary to do in a method outside it?
I don't have much experience writing raw SQL queries into Rails methods so any guidance would be very appreciated.
You can use pluck to get the points into an array
def self.last_3(user_id)
connection.select_all(sanitize_sql_array( [LAST_3_SELECT, user_id]), "last-3").pluck(:points)
end
Here points is the column name to be plucked

Have DateDiff Show Decimals

I am using the DateDiff function, but I would like for it to give me 3 decimal places. How should my query be altered to achieve such result? -- I need this done via the query itself not a VBA function.
Date123: DateDiff('d', [startdate], [enddate])
For a line that you can just put into a query, I'd use something like the following.
Format(DateDiff("s",[DateOne],[DateTwo])/60/60/24,"#.###")
Better practice would be to create a function in a module in your db like the following. Then call it through the query. The code is more maintainable, testable and understandable.
Public Function DateDiffInFractions(DateOne As Date, DateTwo As Date) As String
Dim SecondsDiff As Double
SecondsDiff = DateDiff("S", DateOne, DateTwo) / 24 / 60 / 60 'Days/hours/minutes/seconds
DateDiffInFractions = Format(SecondsDiff, "0.000") 'Format to 3 Decimal points. Return string
End Function

Curious behavior of Access.Application.Eval()

Explain why Access.Application.Eval() (commonly abbreviated as Eval()) produces a different result than just evaluating the original expression in this case:
Debug.Print Round(.575 * 100)
57
Debug.Print Eval("Round(.575 * 100)")
58
EDIT: To address GSerg's answer, the following still returns different results:
Debug.Print Eval("Round(CSng(.575) * 100)")
57
Debug.Print Round(CSng(.575) * 100)
58
That multiplication returns a different product under Eval(), but I don't understand why.
Debug.Print (.575 * 100) < 57.5
True
Debug.Print Eval("(.575 * 100) = 57.5")
-1
In the first case, the product is less than 57.5, so Round() will round it down to 57.
In the second case, the product is equal to 57.5, so Round() will apply its standard "round to even" approach to yield 58.
Edit: You all are right that Eval() coerces the literal value to a different data type.
? TypeName(.575)
Double
? Eval("TypeName(.575)")
Decimal
? Round(CDec(.575) * 100)
58
That is because of different float precision.
In one case the constants get recognized as Doubles, in the other as Singles.
? math.round((.575! - Int(.575!)) * 100)
58
? math.round((.575# - Int(.575#)) * 100)
57
GSerg got me thinking. I'm starting to believe that Jet attempts to coerce decimal literals to the Currency type when Eval is called whereas VBA coerces decimal literals to the Double type. Case in point:
? Math.Round(.575 * 100)
57
? Math.Round(CSng(.575) * 100)
58
? Math.Round(CDbl(.575) * 100)
57
? Math.Round(CCur(.575) * 100)
58
? Eval("Round(.575 * 100)")
58
? Eval("Round(CSng(.575) * 100)")
57
? Eval("Round(CDbl(.575) * 100)")
57
? Eval("Round(CCur(.575) * 100)")
58
I don't use Access, but I would guess that the Eval evaluates the expression as an Access expression, so that the Round and Int functions may operate differently from the VBA versions?
I know from bitter experience that VBA's Round() function uses the round-to-even rounding method (aka Banker's rounding), rather than the more common round-away-from-zero-on-a-5 (aka symmetric arithmetic rounding) method.
If you run the following SQL expression in Access, you get 58:
select Round((.575 - Int(.575)) * 100) as MyValue
If you run the following statement in Access (and, for that matter any Office VBA IDE) Immediate window, you get 57:
Round((.575 - Int(.575)) * 100)
So, that leads me to believe VBA has a different way of doing Round rather than Access and, probably more applicable, JET.
Now, why is that different? Dunno...will take someone with better skills than me.