Using more than one queries on perl DBI - sql

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.

Related

PostgreSQL stored function with multiple queries

I'm working on a PostgreSQL function, but having a real struggle with it. It's not my main area so that's probably why, but I wanted to see if this is doable.
I'm trying to create a function to generate records based on year.
In php I'd do something along the lines of:
function recordsByYear($year=''){
$years = array();
if(empty($year)){
$sql = "SELECT year FROM myTable GROUP BY year ORDER BY year;";
$res = $conn->query($sql);
if($res !== false){
$data = $res->fetchAll(PDO::FETCH_COLUMN);
foreach($data as $oneYear){
$years[] = $oneYear;
}
}
}
else{
$years[] = $year;
}
foreach($years as $thisYear){
//do some queries based on $thisYear
}
}
But I'd like to create a function inside of PostgreSQL, and struggling because I'm not familiar enough with how it all works. I'd like to return a table. I can do some basic stuff, but haven't been able to get something like this working where I have one query then loop through those results running an additional query for each year, then combine the results of that second query and spit out the results as a table.
Is hard to guess without more information but you probably need a JOIN
SELECT *
FROM (
SELECT DISTINCT year
FROM myTable
) y
JOIN ( SELECT year, ... <some query>
....
) t
ON y.year = t.year
PostgreSQL function:
CREATE FUNCTION record_years() RETURNS SETOF int AS $$
SELECT DISTINCT year FROM myTable ORDER BY 1
$$ LANGUAGE SQL;
Your PHP code:
$sql = "SELECT record_years()";
or
$sql = "SELECT year FROM record_years() AS year";
or alternatively all values in a single row, as a JSON array:
$sql = "SELECT array_to_json(ARRAY(SELECT record_years())) AS arr";
$res = $conn->query($sql);
if($res !== false){
$row = $res->fetch();
$years[] = json_decode($row['arr']);
}

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.

How to execute query with subqueries on a table and get a Rowset object as a result in Zend?

I'm currently struggling on how to execute my query on a Table object in Zend and get a Rowset in return. Reason I need particularly THIS is because I'm modifying a code for existing project and I don't have much flexibility.
Query:
SELECT *
FROM `tblname` ud
WHERE ud.user_id = some_id
AND
(
(ud.reputation_level > 1)
OR
(
(SELECT COUNT( * )
FROM `tblname` t
WHERE t.user_id = ud.user_id
AND t.category_id <=> ud.category_id
AND t.city_id <=> ud.city_id
) = 1
)
)
Is there a way to describe this query using Select object?
Previous SQL solution was very simple and consisted of one WHERE clause:
$where = $this->getAdapter()->quoteInto("user_id = ?",$user_id);
return $this->fetchAll($where);
I need to produce same type of the result (so that it could be processed by existing code) but for more complicated query.
Things I've tried
$db = Zend_Db_Table::getDefaultAdapter();
return $db->query($sql)->fetchAll();
---------------- OR ----------------------
return $this->fetchAll($select);
---------------- OR ----------------------
return $this->_db->query($sql)->fetchAll();
But they either produce arrays instead of objects or fail with Cardinality violation message.
I would appreciate any help on how to handle SQL text queries in Zend.
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
//change the fetch mode becouse you don't like the array
$dbAdapter->setFetchMode(Zend_Db::FETCH_OBJ);
$sql = "you're long sql here";
$result = $dbAdapter->fetchAll($sql);
Zend_Debug::dump($result);
exit;
For a list of all fetch modes go to Zend_Db_Adapter
To write you're query using Zend_Db_Select instead of manual string , look at Zend_Db_Slect

Is there SQL parameter binding for arrays?

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