Rails3 NOT IN query takes string as a value - mysql

I am trying to select users who don't have post in the forum list. For that I wrote a query like this
users_id = Post.where(:forum_id => 1).collect { |c| c.user_id }
#users = User.where('topic_id = ? and id not in ? ', "#{#topic.id}", "#{users_id}")
This code throws mysql error, and in my log
SELECT `user`.* FROM `user` WHERE (forum_id = '2222' and id not in '[5877, 5899, 5828, 5876, 5841, 5838, 5840, 5882, 5881, 5870, 5842, 5843, 5844, 5845, 5889, 5896, 5869, 5847, 5849, 5850, 5855, 5857, 5859, 5867, 5861, 5863, 5865, 5868, 5829, 5830, 5831, 5832, 5833, 5900, 6326, 6326, 6332, 5898, 6333, 6334, 6335, 6336, 6339, 7034, 7019, 6336, 5887, 5827, 9940, 9943, 9949, 7030, 9979, 9980, 5892, 9896, 14208, 14224, 14281, 14282, 14283, 5894, 5895, 14689, 14717]'
In mysql I execute the following query, and got the expected result
select * from users where topic_id = 1 and id not in (select users_id from posts where forum_id = 1);
The above query in rails doesn't seem to work..

Try this:
users_ids = Post.where(:forum_id => 1).collect { |c| c.user_id }
#users = User.where('topic_id = ? and id not in (?) ', #topic.id, users_ids)
Also, I advice you to make some changes:
Use pluck instead of collect (pluck is on the DB level)(pluck doc ; pluck vs. collect)
users_ids = Post.where(:forum_id => 1).pluck(:user_id)
name the table in the where clause to avoid ambiguous calls (in where chaining for example):
User.where('users.topic_id = ? AND users.id NOT IN (?)', #topic.id, users_ids)
The final code:
users_ids = Post.where(:forum_id => 1).pluck(:user_id)
#users = User.where('users.topic_id = ? AND users.id NOT IN (?)', #topic.id, users_ids)

Related

Subquery By Zend Database Format

I created a query in MYSQL as follows :
select subsql.item_id, subsql.uid, subsql.post_date, subsql.uid_prev,
max(workflow_trans.date) AS pre_date
from (
select item_id, max(date) AS post_date, uid, uid_prev
from workflow_trans
where uid = 'name' and date <= 'date1' and date >= 'date2'
group by item_id, uid, uid_prev
) AS subsql
left join workflow_trans on subsql.item_id = workflow_trans.item_id and
subsql.uid_prev = workflow_trans.uid and subsql.post_date > workflow_trans.date
where workflow_trans.date is not null
group by subsql.item_id, subsql.uid, subsql.post_date, subsql.uid_prev
order by subsql.post_date
The In translated it to Zend DB format as follows :
$this->db->select()->from(array("subsql" => $this->db->select()
->from(array($this->_name), array('item_id', 'max(date) AS post_date',
'uid', 'uid_prev'))
->where("uid = ?", $username)->where("date >= ?", $tgl1)
->where("date <= ?", $tgl2)
->group(array('item_id', 'uid', 'uid_prev'))),
array('subsql.item_id','subsql.uid',
'subsql.post_date','subsql.uid_prev','max(workflow_trans.date)
as pre_date'))
->joinLeft($this->_name, 'subsql.item_id = workflow_trans.item_id
and subsql.uid_prev = workflow_trans.uid and subsql.post_date >
workflow_trans.date')
->where("workflow_trans.date != ?", null)
->group(array('subsql.item_id', 'subsql.uid', 'subsql.post_date',
'subsql.uid_prev'))
->order(array('subsql.post_date'))
Yet the Zend model could not working. i've been reexamined it, and made 3 changes, but still there is something wrong with the formatting. Fresh pair of sharp eyes are appreciated.
ANSWER UPDATE
Hello guys, I already found it, below the running code :
$this->db->select()->from(array("subsql" => $this->db->select()
->from(array($this->_name), array('item_id', 'max(date) AS post_date',
'uid', 'uid_prev'))
->where("uid = ?", $username)->where("date >= ?", $tgl1)
->where("date <= ?", $tgl2)
->group(array('item_id', 'uid', 'uid_prev'))),
array('subsql.item_id','subsql.uid','subsql.post_date','subsql.uid_prev'))
->joinLeft(array($this->_name), 'subsql.item_id = workflow_trans.item_id and
subsql.uid_prev = workflow_trans.uid and
subsql.post_date > workflow_trans.date',
array('max(workflow_trans.date) as pre_date'))
->where("workflow_trans.date is not null")
->group(array('subsql.item_id', 'subsql.uid', 'subsql.post_date',
'subsql.uid_prev'))
->order(array('subsql.post_date'));
You should define the column from joining table in its own join function, and of course I got it wrong on how to handle the null.
If we talks about zf3, this query generates same sql as with yours.
$from = $this->getSql()->select('workflow_trans')
->columns(['item_id, max(date) AS post_date, uid, uid_prev', new Literal('max(workflow_trans.date) AS pre_date')])
->where(['uid = ?' => 'name'])
->where(['date <= ?' => 'date1'])
->where(['date >= ?' => 'date2'])
->group(['item_id', 'uid', 'uid_prev']);
$query = $this->getSql()->select()
->columns(['item_id, uid, post_date, uid_prev'])
->from(['subsql' => $from])
->join('workflow_trans', 'subsql.item_id = workflow_trans.item_id and subsql.uid_prev = workflow_trans.uid and subsql.post_date > workflow_trans.date', [])
->where(new IsNotNull('workflow_trans.date'))
->group(['subsql.item_id', 'subsql.uid', 'subsql.post_date', 'subsql.uid_prev'])
->order('subsql.post_date');

How to convert this query to doctrine DQL

SELECT apntoken,deviceid,created
FROM `distribution_mobiletokens` as dm
WHERE userid='20'
and not exists (
select 1
from `distribution_mobiletokens`
where userid = '20'
and deviceid = dm.deviceid
and created > dm.created
)
What this query does is selects all mobiletokens where the user id is equal to 20 and the deviceid is the same but chooses the newest apntoken for the device.
My database looks like below.
For more information on this query, I got this answer from another question I asked here(How to group by in SQL by largest date (Order By a Group By))
Things I've Tried
$mobiletokens = $em->createQueryBuilder()
->select('u.id,company.id as companyid,user.id as userid,u.apntoken')
->from('AppBundle:MobileTokens', 'u')
->leftJoin('u.companyId', 'company')
->leftJoin('u.userId', 'user')
->where('u.status = 1 and user.id = :userid')
->setParameter('userid',(int)$jsondata['userid'])
->groupby('u.apntoken')
->getQuery()
->getResult();
//#JA - Get the list of all the apn tokens we need to send the message to.
foreach($mobiletokens as $tokenobject){
$deviceTokens[] = $tokenobject["apntoken"];
echo $tokenobject["apntoken"]."\n";
}
die();
This gives me the incorrect response of
63416A61F2FD47CC7B579CAEACB002CB00FACC3786A8991F329BB41B1208C4BA
9B25BBCC3F3D2232934D86A7BC72967A5546B250281FB750FFE645C8EB105AF6
latestone
Any help here is appreciated!
Other Information
Data with SELECT * FROM
Data after using the SQL I provided up top.
You could use a subselect created with the querybuilder as example:
public function selectNewAppToken($userId)
{
// get an ExpressionBuilder instance, so that you
$expr = $this->_em->getExpressionBuilder();
// create a subquery in order to take all address records for a specified user id
$sub = $this->_em->createQueryBuilder()
->select('a')
->from('AppBundle:MobileTokens', 'a')
->where('a.user = dm.id')
->andWhere('a.deviceid = dm.deviceid')
->andWhere($expr->gte('a.created','dm.created'));
$qb = $this->_em->createQueryBuilder()
->select('dm')
->from('AppBundle:MobileTokens', 'dm')
->where($expr->not($expr->exists($sub->getDQL())))
->andWhere('dm.user = :user_id')
->setParameter('user_id', $userId);
return $qb->getQuery()->getResult();
}
I did this for now as a temporary fix, not sure if this is best answer though.
$em = $this->em;
$connection = $em->getConnection();
$statement = $connection->prepare("
SELECT apntoken,deviceid,created
FROM `distribution_mobiletokens` as dm
WHERE userid=:userid
and not exists (
select 1
from `distribution_mobiletokens`
where userid = :userid
and deviceid = dm.deviceid
and created > dm.created
)");
$statement->bindValue('userid', $jsondata['userid']);
$statement->execute();
$mobiletokens = $statement->fetchAll();
//#JA - Get the list of all the apn tokens we need to send the message to.
foreach($mobiletokens as $tokenobject){
$deviceTokens[] = $tokenobject["apntoken"];
echo $tokenobject["apntoken"]."\n";
}

default image if file not exist mysql pdo

I'm trying to add a default image in a query, if the file does not exist.
I succeeded when the query doesn't have a while loop. But since i need to loop this values. i would like to add the default image to the query.
I wont get any errors, its just printing out some random info from my DB.
function fetch_tweets($uid){
$uid = (int)$uid;
$query = $this->link->query
("SELECT user.id, user.email, user.username, tweets.message, tweets.date,
userdetails.profile_img, userdetails.firstname, userdetails.lastname,
following.id, following.user_id, following.follow_id
FROM user
LEFT JOIN
following ON user.id = following.user_id
JOIN userdetails ON user.id = userdetails.user_id
JOIN tweets ON userdetails.user_id = tweets.user_id
WHERE user.id='{$uid}'
OR
user.id IN (SELECT follow_id
FROM following
WHERE
following.user_id = '{$uid}' )
GROUP BY tweets.date ORDER BY tweets.date DESC "
);
$tweet = array();
while(($row = $query->fetch(PDO::FETCH_ASSOC)) !==FALSE) {
$tweet[] = $row;
$tweet['profile_img'] = (file_exists("img/{$uid}.jpg")) ?
"img/{$uid}.jpg" : "img/default.jpg" ;
}
return $tweet;
}
You're using the variable $tweet in the wrong way in your loop.
The following line will add a new element to the array $tweet:
$tweet[] = $row;
And the following line updates the element called "profile_img" in your $tweet array:
$tweet['profile_img'] = "...";
But I think, what you want is something like this:
while(($row = $query->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
// Update the value for profile_img in the row
$row['profile_img'] = file_exists("img/{$uid}.jpg") ? "img/{$uid}.jpg" : "img/default.jpg" ;
// Add the manipulated row to the $tweet-array
$tweet[] = $row;
}
Please test using a debugger like xdebug (maybe a bit complicated to set up if you're not familiar with PHP) or just use var_dump();. You would've found that out pretty soon ...

Writing a MYSQL Following Users Query

I'm building an app where a user can follow other users and be followed.
A user can also look at who another user is following.
Now lets say user1 is looking at who user2 is following, I need to find all the people IDs that user2 is following and compare it against who user1 is following.
Instead of returning only the IDs of all the users that match both user1 and user2 (which I've seen in other forums), I need to retrieve all user2's following IDs and User Names as well as a flag that indicates if the followed person is also followed by user1.
I've got it to work in PHP with a double for loop of each Query, but I worry that this code will be expensive and would be far better optimized with a single MYSQL query.
Relevant tables and columns:
following_table
follower_id
followed_id
following: varchar -- 'true' or 'false'
user_table
user_id
user_name
Here is my PHP code:
$user_id1 = '1991';
$myFollowingQuery = "SELECT following_table.followed_id, user_table.user_name
FROM following_table
INNER JOIN user_table ON
following_table.followed_id = user_table.user_id
WHERE following_table.following = 'true'
AND following_table.follower_id = '$user_id1'";
$user_id2 = '1985';
$userFollowingQuery = "SELECT following_table.followed_id, user_table.user_name
FROM following_table
INNER JOIN user_table ON
following_table.followed_id = user_table.user_id
WHERE following_table.following = 'true'
AND following_table.follower_id = '$user_id2'";
$userFollowingResult = mysql_query($userFollowingQuery)
or doResponse('error',"Couldn't connect to the database");
$myFollowingResult = mysql_query($myFollowingQuery)
or doResponse('error',"Couldn't connect to the database");
for($i = 0; $i< mysql_num_rows($userFollowingResult);$i++){
$loopArray = array(followed_id => mysql_result($userFollowingResult,$i,"followed_id"),
followed_name => mysql_result($userFollowingResult,$i,"user_name"));
for($j = 0; $j< mysql_num_rows($myFollowingResult);$j++){
if(mysql_result($userFollowingResult,$i,"followed_id")
==mysql_result($myFollowingResult,$j,"followed_id")) {
$loopArray['is_following'] = 'true';
break;
}
if($j==mysql_num_rows($myFollowingResult)-1){
$loopArray['is_following'] = 'false';
break;
}
}
$resultArray[$i] = $loopArray;
}
echo json_encode($resultArray);
Here is a simplified query:
http://sqlfiddle.com/#!2/6b8d6/3
SELECT
user.user_id,
user.user_name,
he.follower_id AS follower_id,
IF(me.followed_id,1,0) AS metoo
FROM following AS he
INNER JOIN user
ON user.user_id = he.followed_id
LEFT JOIN following AS me
ON me.follower_id = 1
AND me.followed_id = he.followed_id
WHERE he.follower_id = 2

Need help turning a SQL query into an ActiveRecord query

I have the following SQL code:
SELECT u.full_name, pu.task_name, sum(hours)
FROM efforts
INNER JOIN project_tasks pu ON efforts.project_task_id = pu.id
INNER JOIN users u ON efforts.user_id = u.id
GROUP BY user_id, task_name
Which outputs all users, their tasks and their hours. What I'm now trying to do is convert this to a Rails' ActiveRecord query.
I am trying to make it look similar to what I have done below but cannot seem to get my logic right.
Project.all.each do |project|
projdata = { 'name' => project.project_name.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support'].each do |taskname|
record = Effort.sum( :hours,
:joins => :project_task,
:conditions => { "project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values' ].push( record )
end
#data.push( projdata )
end
end
end
Added image link
Link to image
The link illustrates a graph. What I need to do is convert my SQL statement into an activeRecord query which will display it like my other graph just as I supplied.
SELECT u.full_name, pu.task_name, hours
FROM efforts
INNER JOIN project_tasks pu ON efforts.project_task_id = pu.id
INNER JOIN users u ON efforts.user_id = u.id
GROUP BY user_id, task_name
Effort.find(:all, :select => "users.full_name, project_tasks.task_name, hours", :joins => [:user, :project_task], :group => "users.user_id, project_tasks.task_name")
But, I have one doubt, how can you get the "hours" field without adding it's on the grouping section. So, it's better, you can add the hours too in grouping section.
But, You should make some additional changes in the following file
vendor/plugins/expandjoinquery/init.rb’
class ActiveRecord::Base
class << self
private
def add_joins!(sql, options, scope = :auto)
scope = scope(:find) if :auto == scope
join = (scope && scope[:joins]) || options[:joins]
sql << " #{expand_join_query(join)} " if join
end
def expand_join_query(*joins)
joins.flatten.map{|join|
case join
when Symbol
ref = reflections[join] or
raise ActiveRecord::ActiveRecordError, "Could not find the source association :#{join} in model #{self}"
case ref.macro
when :belongs_to
"INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, primary_key, table_name, ref.primary_key_name]
else
"INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, ref.primary_key_name, table_name, primary_key]
end
else
join.to_s
end
}.join(" ")
end
end
end
Reference: http://snippets.dzone.com/posts/show/2119
My suggestion is,why should you use the eager loading with association names?.
Try this:
Effort.select(
"users.full_name full_name,
project_tasks.task_name task_name,
SUM(efforts.hours) total_hours").
joins(:project_task, :user).
group("users.user_id, users.full_name, project_tasks.task_name").map do |e|
puts e.full_name, e.task_name, e.total_hours
end
try something like :
Effort.joins(:project_tasks, :user).select("sum(hours) as total_hours, users.full_name, project_tasks.task_name").group("users.user_id, project_tasks.task_name")