Is there SQL parameter binding for arrays? - sql

Is there a standard way to bind arrays (of scalars) in a SQL query? I want to bind into an IN clause, like so:
SELECT * FROM junk WHERE junk.id IN (?);
I happen to be using Perl::DBI which coerces parameters to scalars, so I end up with useless queries like:
SELECT * FROM junk WHERE junk.id IN ('ARRAY(0xdeadbeef)');
Clarification: I put the query in its own .sql file, so the string is already formed. Where the answers mention creating the query string dynamically I'd probably do a search and replace instead.
Edit: This question is kind of a duplicate of Parameterizing a SQL IN clause?. I originally thought that it should be closed as such, but it seems like it's accumulating some good Perl-specific info.

If you don't like the map there, you can use the 'x' operator:
my $params = join ', ' => ('?') x #foo;
my $sql = "SELECT * FROM table WHERE id IN ($params)";
my $sth = $dbh->prepare( $sql );
$sth->execute( #foo );
The parentheses are needed around the '?' because that forces 'x' to be in list context.
Read "perldoc perlop" and search for 'Binary "x"' for more information (it's in the "Multiplicative Operators" section).

You specify "this is the SQL for a query with one parameter" -- that won't work when you want many parameters. It's a pain to deal with, of course. Two other variations to what was suggested already:
1) Use DBI->quote instead of place holders.
my $sql = "select foo from bar where baz in ("
. join(",", map { $dbh->quote($_) } #bazs)
. ")";
my $data = $dbh->selectall_arrayref($sql);
2) Use an ORM to do this sort of low level stuff for you. DBIx::Class or Rose::DB::Object, for example.

I do something like:
my $dbh = DBI->connect( ... );
my #vals= ( 1,2,3,4,5 );
my $sql = 'SELECT * FROM table WHERE id IN (' . join( ',', map { '?' } #vals ) . ')';
my $sth = $dbh->prepare( $sql );
$sth->execute( #vals );

And yet another way to build SQL is to use something like SQL::Abstract....
use SQL::Abstract;
my $sql = SQL::Abstract->new;
my $values = [ 1..3 ];
my $query = $sql->select( 'table', '*', { id => { -in => $values } } );
say $query; # => SELECT * FROM table WHERE ( id IN ( ?, ?, ? ) )

With plain DBI you'd have to build the SQL yourself, as suggested above. DBIx::Simple (a wrapper for DBI) does this for you automatically using the '??' notation:
$db->query("select * from foo where bar in (??)", #values);

In python, I've always ended up doing something like:
query = 'select * from junk where junk.id in ('
for id in junkids:
query = query + '?,'
query = query + ')'
cursor.execute(query, junkids)
...which essentially builds a query with one '?' for each element of the list.
(and if there's other parameters in there too, you need to make sure you line things up correctly when you execute the query)
[edit to make the code easier to understand for non-python people. There is a bug, where the query will have an extra comma after the last ?, which I will leave in because fixing it would just cloud the general idea]

I use DBIx::DWIW. It contains a function called InList(). This will create the part of the SQL that is needed for the list. However this only works if you have all your SQL in the program instead of outside in a separate file.

Use
SELECT * FROM junk WHERE junk.id = ANY (?);
instead

Related

How to select from subquery using Laravel Query Builder?

I'd like to get value by the following SQL using Eloquent ORM.
- SQL
SELECT COUNT(*) FROM
(SELECT * FROM abc GROUP BY col1) AS a;
Then I considered the following.
- Code
$sql = Abc::from('abc AS a')->groupBy('col1')->toSql();
$num = Abc::from(\DB::raw($sql))->count();
print $num;
I'm looking for a better solution.
Please tell me simplest solution.
In addition to #delmadord's answer and your comments:
Currently there is no method to create subquery in FROM clause, so you need to manually use raw statement, then, if necessary, you will merge all the bindings:
$sub = Abc::where(..)->groupBy(..); // Eloquent Builder instance
$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
->count();
Mind that you need to merge bindings in correct order. If you have other bound clauses, you must put them after mergeBindings:
$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
// ->where(..) wrong
->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
// ->where(..) correct
->count();
Laravel v5.6.12 (2018-03-14) added fromSub() and fromRaw() methods to query builder (#23476).
The accepted answer is correct but can be simplified into:
DB::query()->fromSub(function ($query) {
$query->from('abc')->groupBy('col1');
}, 'a')->count();
The above snippet produces the following SQL:
select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
The solution of #JarekTkaczyk it is exactly what I was looking for. The only thing I miss is how to do it when you are using
DB::table() queries. In this case, this is how I do it:
$other = DB::table( DB::raw("({$sub->toSql()}) as sub") )->select(
'something',
DB::raw('sum( qty ) as qty'),
'foo',
'bar'
);
$other->mergeBindings( $sub );
$other->groupBy('something');
$other->groupBy('foo');
$other->groupBy('bar');
print $other->toSql();
$other->get();
Special atention how to make the mergeBindings without using the getQuery() method
From laravel 5.5 there is a dedicated method for subqueries and you can use it like this:
Abc::selectSub(function($q) {
$q->select('*')->groupBy('col1');
}, 'a')->count('a.*');
or
Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
There are many readable ways to do these kinds of queries at the moment (Laravel 8).
// option 1: DB::table(Closure, alias) for subquery
$count = DB::table(function ($sub) {
$sub->from('abc')
->groupBy('col1');
}, 'a')
->count();
// option 2: DB::table(Builder, alias) for subquery
$sub = DB::table('abc')->groupBy('col1');
$count = DB::table($sub, 'a')->count();
// option 3: DB::query()->from(Closure, alias)
$count = DB::query()
->from(function ($sub) {
$sub->from('abc')
->groupBy('col1')
}, 'a')
->count();
// option 4: DB::query()->from(Builder, alias)
$sub = DB::table('abc')->groupBy('col1');
$count = DB::query()->from($sub, 'a')->count();
For such small subqueries, you could even try fitting them in a single line with PHP 7.4's short closures but this approach can be harder to mantain.
$count = DB::table(fn($sub) => $sub->from('abc')->groupBy('col1'), 'a')->count();
Note that I'm using count() instead of explicitly writing the count(*) statement and using get() or first() for the results (which you can easily do by replacing count() with selectRaw(count(*))->first()).
The reason for this is simple: It returns the number instead of an object with an awkwardly named property (count(*) unless you used an alias in the query)
Which looks better?
// using count() in the builder
echo $count;
// using selectRaw('count(*)')->first() in the builder
echo $count->{'count(*)'};
Correct way described in this answer: https://stackoverflow.com/a/52772444/2519714
Most popular answer at current moment is not totally correct.
This way https://stackoverflow.com/a/24838367/2519714 is not correct in some cases like: sub select has where bindings, then joining table to sub select, then other wheres added to all query. For example query:
select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ?
To make this query you will write code like:
$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->from(DB::raw('('. $subQuery->toSql() . ') AS subquery'))
->mergeBindings($subQuery->getBindings());
$query->join('t2', function(JoinClause $join) {
$join->on('subquery.col1', 't2.col2');
$join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
During executing this query, his method $query->getBindings() will return bindings in incorrect order like ['val3', 'val1', 'val4'] in this case instead correct ['val1', 'val3', 'val4'] for raw sql described above.
One more time correct way to do this:
$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->fromSub($subQuery, 'subquery');
$query->join('t2', function(JoinClause $join) {
$join->on('subquery.col1', 't2.col2');
$join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
Also bindings will be automatically and correctly merged to new query.
I like doing something like this:
Message::select('*')
->from(DB::raw("( SELECT * FROM `messages`
WHERE `to_id` = ".Auth::id()." AND `isseen` = 0
GROUP BY `from_id` asc) as `sub`"))
->count();
It's not very elegant, but it's simple.
This works fine
$q1 = DB::table('tableA')->groupBy('col');
$data = DB::table(DB::raw("({$q1->toSql()}) as sub"))->mergeBindings($q1)->get();
I could not made your code to do the desired query, the AS is an alias only for the table abc, not for the derived table.
Laravel Query Builder does not implicitly support derived table aliases, DB::raw is most likely needed for this.
The most straight solution I could came up with is almost identical to yours, however produces the query as you asked for:
$sql = Abc::groupBy('col1')->toSql();
$count = DB::table(DB::raw("($sql) AS a"))->count();
The produced query is
select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
->selectRaw('your subquery as somefield')
Deriving off mpskovvang's answer, here is what it would look like using eloquent model. (I tried updating mpskovvang answer to include this, but there's too many edit requests for it.)
$qry = Abc::where('col2', 'value')->groupBy('col1')->selectRaw('1');
$num = Abc::from($qry, 'q1')->count();
print $num;
Produces...
SELECT COUNT(*) as aggregate FROM (SELECT 1 FROM Abc WHERE col2='value' GROUP BY col1) as q1

What does it mean "?" in sql query?

I just got a query cod :
SELECT o.id,o.sort_order,od.object FROM i_objects o, i_objects_description od
WHERE o.id=od.objects_id AND o.object_status = ? AND od.languages_id = ?
ORDER BY o.sort_order ASC
I want figure it out what does "?" mean in this query ?
If I run this query , it gives me this error :
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?
Im using PEAR and this is my function :
function getArrayObjects( $language_id )
{
$q = 'SELECT o.id,o.sort_order,od.object FROM ' . TABLE_OBJECTS . ' o, ' . TABLE_OBJECTS_DESCRIPTION . ' od ';
$q.= 'WHERE o.id=od.objects_id AND o.object_status = ? AND od.languages_id = ? ';
$q.= 'ORDER BY o.sort_order ASC';
$sth = $this->_db->prepare( $q );
$res = $sth->execute( array( 'active', $language_id ) );
//var_dump($res);echo "<br>";echo "<br>";echo "<br>";
$objects = array();
while( $row = $res->fetchRow())
{
$objects[$row['id']] = $row;
}
return $objects;
}
It's a placeholder for parameter. In your query you have this:
AND o.object_status = ? AND od.languages_id = ?
And then you execute it like this:
$res = $sth->execute( array( 'active', $language_id ) );
So, when query is actually executed by database server, object_status is 'active' and language_id is $language_id.
This is done this way to guard from SQL injection. Another reason is efficiency. When you use prepared statements, database doesn't need to parse/compile query each time. It uses the template and just substitutes values in it. (more on this: Prepared statement)
The ? are placeholder the values of which are filled in in the $sth->execute( array( 'active', $language_id ) ) statement.
One of the main purposes for this construct is to prevent sql injection attacks.
Its a Used to set the value dynamically ,in other words place holder
These are "parametrized queries". While evaluating "?" are replaced with given values (it's called binding). They protect from sql injection and makes possible to optimize queries.

Using more than one queries on perl DBI

I'm sure there is a better way to do this on the same line, but I'm unable to figure out how, since I'm a beginner in Perl. Basically what I need to do is select, delete and count the results.
$sth = $dbh->prepare("SELECT env,server, mwp.is_reference where env='$ARGV[1]';");
$sth->execute();
$sth2 = $dbh->prepare("delete from mwp.is_info_package where env='$ARGV[1]'");
$sth2->execute();
$sth3 = $dbh->prepare("SELECT count(1) from mwp.is_reference where env='$ARGV[1]'");
$sth3->execute()
The objective is how do i use the 3 queries at the same line, instead having 3 executes.
Well you could start out using placeholders ( '?' ).
my #qlist
= ( 'SELECT env,server FROM mwp.is_reference where env=?'
, 'DELETE mwp.is_info_package WHERE env=?'
, 'SELECT count(1) FROM mwp.is_reference where env=?'
);
And then you can iterate through them like this:
my $env = $ARGV[1];
foreach my $query ( #qlist ) {
$dbh->prepare( $query )->execute( $env );
Carp::croak( $dbh->errstr ) if $dbh->err;
}
But of course, you really want to select the two outputs, don't you?
use Carp qw<croak>;
my $select_query = 'SELECT env,server FROM mwp.is_reference where env=?';
my $delete_query = 'DELETE mwp.is_info_package WHERE env=?';
my $count_query = 'SELECT count(1) FROM mwp.is_reference where env=?';
my %empty_atts;
my $rows
= $dbh->selectall_arrayref( $select_query, \%empty_atts, $env )
;
croak( $dbh->errstr ) if $dbh->err;
$dbh->prepare( $delete_query )->execute( $env );
croak( $dbh->errstr ) if $dbh->err;
my ( $count )
= $dbh->selectrow_array( $count_query, \%empty_atts, $env )
;
croak( $dbh->errstr ) if $dbh->err;
I solved the problem using the following query statement:
$sth = $dbh->prepare("select env,iserver, ( select count(1) from is_reference where env='$ARGV[1]' ) as total from is_reference where env='$ARGV[1]'");
not the most elegant way, but solved my problem with the less lines. Regarding the delete query, i moved to another condition to check if the table have data or not.
THanks all.
You could use a stored procedure that performs those functions and returns the results of the select as well as a count, then you only need to do:
my $sth = $dbh->prepare("EXEC procedure_name ?");
$sth->execute( $ARGV[1] );
As an aside, the way you're using prepare and execute is undesirable. You use prepare to avoid having to have Perl variables directly in the query; your Perl variables should be passed to execute() as values, not part of the string given to prepare(). There are a number of good reasons to do this, including protection against SQL Injection attacks.
I also noticed oddness in your last SQL query. I think you probably want
SELECT count(env) FROM mwp.is_reference where env=?
Otherwise it will always return "1" as the count... Likewise, unless there are database triggers doing something interesting, you could combine the first and last query into one this way (I'll leave count(1) for this in case that's really what you want):
my $sth = $dbh->prepare('SELECT env,server FROM mwp.is_reference where env=?');
$sth = $sth->execute( $ARGV[1] );
my $result_set = $sth->fetchall_arrayref();
my $count = scalar #{ $result_set };
The $result_set will be a reference to an ARRAY of ARRAYRefs containing the results; $count will contain the number of rows in that result set.

Using Perl bind_param with SQL IN statement [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
Is there SQL parameter binding for arrays?
I was wondering if there is anyway to use bind_param with SQL IN statements. According to perl documentation bind_param_array cannot be used as well. Has anyone come across the same situation?
http://search.cpan.org/perldoc?DBI#bind_param_array
No you can't do this easily. One option is to use $dbh->quote, eg.
my #values = (1,2,3,4);
my $sql = "SELECT * from my_table WHERE ID IN (";
$sql .= join(',', map { $dbh->quote($_) } #values)
$sql .= ')';
Or you can create the necessary placeholders and pass the array in as bind parameters, eg.
my #values = (1,2,3,4);
my $sql = "SELECT * from my_table WHERE ID IN (";
$sql .= join(',', map { '?' } #values);
$sql .= ')';
my $sth = $dbh->prepare($sql);
$sth->execute(#values);
Neither is exceptionally pretty.
Not if you're wanting an arbitrary number of placeholders, no. You can use it with an IN, as in
where foo in ( ?, ?, ? )
but then you must have exactly three binds.

SELECT MAX query returns only 1 variable + codeigniter

I use codeigniter and have an issue about SELECT MAX ... I couldnot find any solution at google search...
it looks like it returns only id :/ it's giving error for other columns of table :/
Appreciate helps, thanks!
Model:
function get_default()
{
$this->db->select_max('id');
$query = $this->db->getwhere('gallery', array('cat' => "1"));
if($query->num_rows() > 0) {
return $query->row_array(); //return the row as an associative array
}
}
Controller:
$default_img = $this->blabla_model->get_default();
$data['default_id'] = $default_img['id']; // it returns this
$data['default_name'] = $default_img['gname']; // it gives error for gname although it is at table
To achieve your goal, your desire SQL can look something like:
SELECT *
FROM gallery
WHERE cat = '1'
ORDER BY id
LIMIT 1
And to utilise CodeIgniter database class:
$this->db->select('*');
$this->db->where('cat', '1');
$this->db->order_by('id', 'DESC');
$this->db->limit(1);
$query = $this->db->get('gallery');
That is correct: select_max returns only the value, and no other column. From the specs:
$this->db->select_max('age');
$query = $this->db->get('members');
// Produces: SELECT MAX(age) as age FROM members
You may want to read the value first, and run another query.
For an id, you can also use $id = $this->db->insert_id();
See also: http://www.hostfree.com/user_guide/database/active_record.html#select
CodeIgniter will select * if nothing else is selected. By setting select_max() you are populating the select property and therefore saying you ONLY want that value.
To solve this, just combine select_max() and select():
$this->db->select('somefield, another_field');
$this->db->select_max('age');
or even:
$this->db->select('sometable.*', FALSE);
$this->db->select_max('age');
Should do the trick.
It should be noted that you may of course also utilize your own "custom" sql statements in CodeIgniter, you're not limited to the active record sql functions you've outlined thus far. Another active record function that CodeIgniter provides is $this->db->query(); Which allows you to submit your own SQL queries (including variables) like so:
function foo_bar()
{
$cat = 1;
$limit = 1;
$sql = "
SELECT *
FROM gallery
WHERE cat = $cat
ORDER BY id
LIMIT $limit
";
$data['query'] = $this->db->query($sql);
return $data['query'];
}
Recently I have been utilizing this quite a bit as I've been doing some queries that are difficult (if not annoying or impossible) to pull off with CI's explicit active record functions.
I realize you may know this already, just thought it would help to include for posterity.
2 helpful links are:
http://codeigniter.com/user_guide/database/results.html
http://codeigniter.com/user_guide/database/examples.html