How to use raw sql query string that is created dynamically? - laravel

I've seen similar posts here, however they're for older Laravel versions, and also my query string is created dynamically.
I get an array of arrays that represent the values I need to use when I construct the sql query string. For example:
[
["a" => "a1", "b" => "b1", "c" => "c1"],
["a" => "a2", "b" => "b2", "c" => "c2", "d" => "d2"]
]
Then I need to create some complex query that's impossible to write with Laravel's query builder and uses the dynamic data from above:
SELECT
...
WHERE (a="a1", b="b1", c="c1")
OR WHERE (a="a2", b="b2", c="c2", d="d2")
...
From older posts I've seen here, it was mentioned I can use
$result = DB::select($query_string):
Or even with DB::statement.
But, I'm not sure it's still a good way in Laravel 8 and above because it's not in the docs.
But even if yes, it means I'll put the string as is without taking care of binding the values to prevent sql injection.
So how can I do it in the case?

I would go with Closure and foreach, because you can use array with string keys to define where/orWhere conditions.
$conditions = [
["a" => "a1", "b" => "b1", "c" => "c1"],
["a" => "a2", "b" => "b2", "c" => "c2", "d" => "d2"]
];
DB::table('your_table')->where(function($query) use ($conditions) {
foreach($conditions as $condition) {
$query->orWhere($condition);
}
})->selectRaw($query_string)->get();
If you really need to go with raw SQL, just add the raw functions like orderByRaw(), groupByRaw, etc... https://laravel.com/api/9.x/Illuminate/Database/Query/Builder.html
And about sql injection, you need to validate your data. https://laravel.com/docs/9.x/validation

Related

Elasticsearch/Kibana [ 7.17 ] how to add custom autogenerated field

we got an ES/Kibana [7.17] running, everything works fine so far but we want to autotranslate a field based on a static table how is this possible? I remember it was possible over custom formats in older Kibana versions but I cannot find how to do it in this one.
e.g.
1 => HR Department
2 => IT Department
3 => Production
etc.
Data is:
Max Muster 3
Data should be
Max Muster 3 Production
P.S. I tried adding a runtime field to the template but it always complains that the syntax is wrong
filter {
translate {
source => "[dep]
target => "[department]"
dictionary => {
"1" => "HR Dep"
"2" => "IT Dep"
"3" => "Production"
}
}
}
}

Is that good solution? [duplicate]

I have a record set that includes a date field, and want to determine how many unique dates are represented in the record set.
Something like:
Record.find(:all).date.unique.count
but of course, that doesn't seem to work.
This has changed slightly in rails 4 and above :distinct => true is now deprecated. Use:
Record.distinct.count('date')
Or if you want the date and the number:
Record.group(:date).distinct.count(:date)
What you're going for is the following SQL:
SELECT COUNT(DISTINCT date) FROM records
ActiveRecord has this built in:
Record.count('date', :distinct => true)
Outside of SQL:
Record.find(:all).group_by(&:date).count
ActiveSupport's Enumerable#group_by is indispensable.
the latest #count on rails source code only accept 1 parameter.
see: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-count
so I achieved the requirement by
Record.count('DISTINCT date')
Detailing the answer:
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 2, :created_on => '2010-09-29')
Post.create(:user_id => null, :created_on => '2010-09-29')
Post.group(:created_on).count
# => {'2010-09-29' => 4}
Post.group(:created_on).count(:user_id)
# => {'2010-09-29' => 3}
Post.group(:created_on).count(:user_id, :distinct => true) # Rails <= 3
Post.group(:created_on).distinct.count(:user_id) # Rails = 4
# => {'2010-09-29' => 2}
As I mentioned here, in Rails 4, using (...).uniq.count(:user_id) as mentioned in other answers (for this question and elsewhere on SO) will actually lead to an extra DISTINCT being in the query:
SELECT DISTINCT COUNT(DISTINCT user_id) FROM ...
What we actually have to do is use a SQL string ourselves:
(...).count("DISTINCT user_id")
Which gives us:
SELECT COUNT(DISTINCT user_id) FROM ...
Also, make sure you have an index on the field in your db, or else that query will quickly become sloooow.
(It's much better to do this in SQL, otherwise you pull the entire db table into memory just to answer the count.)

How to get an array of 2-tuples from Laravel query builder?

Basically, I want to do this:
$locals['companies'] = Company::orderBy('name')->get(['id','name'])->map(function($c) { return [$c->id, $c->name]; })->toArray();
But without such a verbose map function. Isn't there a get-like method that will return flat numeric arrays instead of objects?
To be clear, the output should look like this:
array:4 [
0 => array:2 [
0 => 4
1 => "My Company"
]
1 => array:2 [
0 => 14
1 => "Example Company"
]
2 => array:2 [
0 => 13
1 => "Best Company"
]
3 => array:2 [
0 => 12
1 => "Super Co"
]
]
This is what I mean by 2-tuples: two-element numeric arrays. I know they don't exist in PHP, but the concept is the same; each entry has a fixed length.
There is no function out of the box to do this, but Laravel's Collection is Macroable, so you can add your own function to it to do this.
For example, somewhere in your code (like the boot() method of your AppServiceProvider), you can add a new method to the Collection:
// add toIndexedArray method to collections
\Illuminate\Support\Collection::macro('toIndexedArray', function() {
return array_map('array_values', $this->toArray());
});
Now you can use this new method like any other normal Collection method, so your final code would be:
$locals['companies'] = Company::orderBy('name')->get(['id','name'])->toIndexedArray();
If this is something you need a lot, you can change the PDO fetch mode in config/database.php to PDO::FETCH_NUM. I'm assuming it's possible to change it on-the-fly as well, but the code probably won't look that great. I don't think there's a Laravel command to change it for a single query, I'm afraid.
Otherwise, since the array is multidimensional, I'm afraid you do need to map over them somehow, and Laravel collections don't work nicely with e.g. ->map('array_values') which would have been a lot cleaner.
You could wrap it in array_map('array_values', $array) but that seems silly.
At least you could make it a little shorter if you change ->map() to ->transform() - then you don't need to tack on the ->toArray() at the end.
Use pluck():
$locals['companies'] = Company::orderBy('name')->pluck('id', 'name')->toArray();
If you need a list for Form::select this will work:
$locals['companies'] = Company::orderBy('name')->pluck('name', 'id');
You can omit the map function and just do:
$locals['companies'] = Company::orderBy('name')->get(['id','name'])->toArray();

Ruby put in order columns when creating CSV document from Mongoid

I need to create CSV document from database. So I want to organise columns in particular order and I have template of this order and this template stored as array of headers
header = ["header1", "header2", "header3", "header4", "header5"]
record = [{"header4" =>"value4"}, {"header3" =>"value3"}, {"header5"=>"value5"}, {"header1"=>"value1"}, {"header2"=>"value2"}]
I need to get array like tis
record = [{"header1" =>"value1"}, {"header2" =>"value2"}, {"header3"=>"value3"}, {"header4"=>"value4"}, {"header5"=>"value5"}]
but when I doing
csv<< mymodel.attributes.values.sort_by! { |h| header.index(h.keys[0])
It does not work
When you call mymodel.attributes, you get a Hash back which maps attributes names (as strings) to their values. If your attribute names are header1 through header5 then mymodel.attributes will be something like this:
{
'header1' => 'value1',
'header2' => 'value2',
'header3' => 'value3',
'header4' => 'value4',
'header5' => 'value5'
}
Of course, the order depends on how things come out of MongoDB. The easiest way to extract a bunch of values from a Hash in a specified order is to use values_at:
mymodel.attributes.values_at(*header)

Use Hash with Redundant Items in sqlite3 Bind

I'm trying to run an INSERT query such as the following:
Query = "INSERT INTO t (x,y,z) VALUES (:aval,:bval,:cval)"
Using the following format, I can use a hash to insert the actual values:
db.execute(Query,{"aval" => "1", "bval" => "2", "cval" => "3"})
My problem is that the values are already in a hash that has some redundant values, e.g.:
{"aval" => "1", "bval" => "2", "cval" => "3", "dval" => "4"}
Since dval is not one of the required parameters, I get the error -
SQLite3::Exception: no such bind parameter
Of course, I may be wrong and the error may be due to a different reason.
It would be great if there were a way to overcome this using SQLite3. Alternatively, a method for creating a "trimmed" copy of the has with only the required parameters would also be OK.
You should write as -
Query = "INSERT INTO t (x,y,z) VALUES (?,?,?)"
hash = {"aval" => "1", "bval" => "2", "cval" => "3"}
db.execute(Query, hash.values_at("aval", "bval", "cval" ))
Read this execute( sql, *bind_vars ) {|row| ...} documentation.

Resources