codeigniter poll module using hmvc - php

I'm trying create a poll module within codeigniter using hmvc
I'm trying to return the count as percentages against the amount of user_id's that have voted into the candidate_id
I'm using a so called perfectcontroller and perfectmodel
where I can return the count on the number of rows each candidate has a user_id
but I have seven candidate_id's so getting the percentages is a little tricky
but its also tricky trying to get the count to match up against the id its counting
I do not know if this is the right way to go about it or if I should be doing it another way any suggestions would help
thanks
<?php
function get_result(){
$query = $this->get_where_custom('candidate_id',$candidate_id);
$num_rows = $query->num_rows();
//returns the amount of user votes
foreach($query->result() as $row){
$candidate_id = $row->candidate_id;
}
}
?>

No matter if you have 7 or 77 candidates. Votes are counting. So if you have only one vote in DB, it means 100% for one candidate and 0% for rest of them. I don't know what do you consider by perfectcontroller and perfectmodel. But your table could be like
election_id | voter_id | candidate_id
your query would be like
$this->db->query("SELECT `e`.`candidate_id`, COUNT(1) AS `total`, COUNT(1) / `t`.`cnt` * 100 AS `percentage` FROM `elections` `e` CROSS JOIN (SELECT COUNT(1) AS `cnt` FROM `elections`) `t` GROUP BY `e`.`candidate_id`;");

Related

Efficient comment system pagination query

So I've been looking around the web about any information about pagination.
From what I've seen there are 3 kinds, (LIMIT, OFFSET) a, (WHERE id > :num ORDER BY id LIMIT 10) b and (cursor pagination) c like those used on facebook and twitter.
I decided that for my project I'll go with the "b" option as it looks pretty straightforward and efficient.
I'm trying to create some kind of "facebook" like post and comment system, but not as complex.
I have a ranking system for the posts and comments and top 2 comments for each post that are fetched with the post.
The rest of the comments for each specific post are being fetched when people click on to see more comments.
This is a query for post comments:
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.weight DESC, c.createdAt ASC LIMIT 10 OFFSET 2
offset 2 is because there were 2 comments being fetched earlier as top 2 comments.
I'm looking for a way similar to this of seeking next 10 comments each time through the DB without going through all the rows like with LIMIT,OFFSET
The problem is that I have two columns that are sorting the results and I won't allow me to use this method:
SELECT * FROM Comments WHERE id > :lastId LIMIT :limit;
HUGE thanks for the helpers !
Solution So Far:
In order to to have an efficient pagination we need to have a single column with as much as possible unique values that make a sequence to help us sort the data and paginate through.
My example uses two columns to sort the data so it makes a problem.
What I did is combine time(asc sorting order) and weight of the comment(desc sorting order), weight is a total of how much that comment is being engaged by users.
I achieved it by getting the pure int number out of the DateTime format and dividing the number by the weight let's call the result,"ranking" .
this way a comment with a weight will always have a lower ranking ,than a comment without a weight.
DateTime after stripping is a 14 digit int ,so it shouldn't make a problem dividing it by another number.
So now we have one column that sorts the comments in a way that comments with engagement will be at the top and after that will come the older comments ,so on until the newly posted comments at the end.
Now we can use this high performance pagination method that scales well:
SELECT * FROM Comments WHERE ranking > :lastRanking ORDER BY ASC LIMIT :limit;
Ok i want to say about other way, in my opinion this very useful.
$rowCount = 10; //this is count of row that is fetched every time
$page = 1; //this is for calculating offset . you must increase only this value every time
$offset = ($page - 1) * $rowCount; //offset
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.ranking DESC, c.createdAt ASC LIMIT $rowCount OFFSET $offset
There can be an error because i didn't check it , please don't make it matter

Picking which data to view in group by

So yesterday, I am trying to sort data in groups made by Group by
I must select which data I want to show in those group
There is list of debts and each person may be in debt in the past but never have more than 1 unpaid debt
I need to know how many times how many times each user have been in debt before this last debt.
This is the column in the data base
Table "Users"
uid | name | date_of_birth
Table "Debt"
uid | debt_duration | paid_count | created_date
I end up with a hack like this in php
$res = mysql_query( "
SELECT * FROM Debt
JOIN Users
WHERE Users.uid = Debt.uid
ORDER BY created_date
GROUP BY Debt.uid");
while( $row = mysql_fetch_array( $res ) ){
$uid = $row['uid'];
$r = mysql_fetch_array( mysql_query("SELECT COUNT(*) FROM Debt WHERE uid = $uid") );
$previous_debts_count = $r[0];
}
This script is quite heavy but fortunately my client doesn't complain.
The script run at around 3 seconds top
But I need to know better ways to do this
sorry for the strange formatting, I am new here ...
I think the query you want is this:
SELECT Users.uid, COUNT(*) as cnt
FROM Debt JOIN
Users
ON Users.uid = Debt.uid
GROUP BY Debt.uid
ORDER BY created_date ;
Just loop through the results and don't use multiple queries for this. Check that Users.uid is the primary key on the users table. And add an index on debt(uid) to improve performance.
First of all, you should never use * in an SQL statement.
It makes the query highly vulnerable to SQL injection.
And I recommend you to use a PDO or a PHP framework.
Try this:
SELECT COUNT(Debt.uid) AS users
FROM Debt
LEFT JOIN Users
ON Users.uid = Debt.uid
GROUP BY Debt.uid

select 3 rows from mysql table, then get each row's specific column

Select 3 rows from table1
Get a specific column data out of each row.
Then use that each column data obtained , to make a query again to get data from table2.
Store the data obtained in step 4 into a variable for each row.
Then put them in json array (table 1 , 3 rows + table 2's data(each of them).
I am building a rank table, it displays top 3 users with their rank name.
For example:
User1 has 2000 points , user 2 has 4000points , user 3 has 10k points , so the top 3 user is :
user 3 > user 2 > user 1
So , i want the php to go to 'users' table and get the top 3 members using this:
$query = mysql_query("SELECT * FROM users ORDER BY pts DESC LIMIT 3");
$rows = array();
while($r = mysql_fetch_assoc($query)) {
$rows[] = $r;
}
Table structure for 'user':
1.username(varchar)
2.pts(int)
After the rows are put into an array , how can i get 'points' for each of the row in that array.
Then go to 'rank' table to get their ranknames.
Table structure for 'rank':
1.rank(varchar)
2.pts(int)
Inside rank table there is 'pts' to let php choose compare which rank the user is at based on the points from each row of the array.
Normally i would use this if its only for 1 user , but for multiple users , im not sure:
$result = mysql_query("SELECT * FROM rank WHERE pts <= '$upts' ORDER BY pts DESC LIMIT 1")
or die(mysql_error());
Then after getting the rank for the top 3 users , php will now add the ranks to each of the user(row) in that array(of course , add it to the rank owner, not just simply place it in).
Then JSON encode it out.
How can i do this?
I am not sure if this is what you want. That is combine the two query into one query. Please take a look at http://sqlfiddle.com/#!2/ad419/8
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts group by user.id
UPDATED:
For extracting top 3, could do as below;
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts
GROUP BY user.id
ORDER BY pts DESC LIMIT 3
If i understand correctly, you need to get values from Rank and Users tables. In order to do that in just one query You need to add FK (Foreign Key) to the Rank table that points to a specific user in the Users table.
So you need to add userId to the Rank table and then you can run:
SELECT r.rank, u.points from users u,rank r where u.userId = r.userId
This is roughly what you need.
Not quite the answer to your exact question, but this might be of use to you: How to get rank using mysql query. And may even mean that you don't require a rank table. If this doesn't help, I'll check back later.
Use this query
$query = "SELECT
u.pts,
r.rank
FROM users as u
left join ranks as r
on r.pts = u .pts
ORDER BY pts DESC
LIMIT 3";
This will bring what you required without putting into an array
$rec = mysql_query($query);
$results = arrau();
while($row = mysql_fetch_row($rec)){
$results[] = $row;
}
echo json_encode($results);
It looks like what you're trying to do is retrieve the rank with the highest point requirement that the user actual meets, which isn't quite what everyone else is giving here. Fortunately it is easily possible to do this in a single query with a nice little trick:
SELECT
user.username,
SUBSTRING_INDEX(GROUP_CONCAT(rank.rank ORDER BY pts DESC),",",1) AS `rank`
FROM user
LEFT JOIN rank ON user.pts >= rank.pts
GROUP BY user.id
ORDER BY pts DESC
LIMIT 3
Basically what the second bit is doing is generating a list of all the ranks the user has achieved, ordering them by descending order of points and then selecting the first one.
If any of your rank names have commas in then there's another little tweak we need to add on, but I wouldn't have thought they would so I've left it out to keep things simple.

How to optimize query with table scans?

This is by far the slowest query in my web application.
SELECT prof.user_id AS userId,
prof.first_name AS first,
prof.last_name AS last,
prof.birthdate,
prof.class_string AS classes,
prof.city,
prof.country,
prof.state,
prof.images,
prof.videos,
u.username,
u.avatar,
(SELECT Count(*)
FROM company_member_sponsorship
WHERE member_id = prof.user_id
AND status = 'sponsored') AS sponsor_count,
(SELECT Count(*)
FROM member_schedules
WHERE user_id = prof.user_id) AS sched_count
FROM member_profiles prof
LEFT JOIN users u
ON u.id = prof.user_id
ORDER BY ( prof.images + prof.videos * 5 + (
CASE
WHEN prof.expire_date > :time THEN 50
ELSE 0
end ) + sponsor_count * 20 + sched_count * 4
) DESC,
prof.last_name ASC
LIMIT :start, :records
Everything else on the site takes less than a second to load even with lots of queries happening on all levels. This one takes about 3-4 seconds.
It's obviously the table scans that are causing the slowdown. I can understand why; the first table has 50,000+ rows, the second 160,000+ rows.
Is there any way I can optimize this query to make it go faster?
If worse comes to worst I can always go through my code and maintain a tally for sponsorships and events in the profile table like I do for images and videos though I'd like to avoid it.
EDIT: I added the results of an EXPLAIN on the query.
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY prof ALL NULL NULL NULL NULL 44377 Using temporary; Using filesort
1 PRIMARY u eq_ref PRIMARY PRIMARY 3 mxsponsor.prof.user_id 1
3 DEPENDENT SUBQUERY member_schedules ref user_id user_id 3 mxsponsor.prof.user_id 6 Using index
2 DEPENDENT SUBQUERY company_member_sponsorship ref member_id member_id 3 mxsponsor.prof.user_id 2 Using where; Using index
EDIT2:
I ended up dealing with the problem by maintaining a count in the member profile. Wherever sponsorships/events are added/deleted I just invoke a function that scans the sponsorship/events table and updates the count for that member. There might still be a way to optimize a query like this, but we're publishing this site rather soon so I'm going with the quick and dirty solution for now.
Not guaranteed to work, but try using join and group by rather than inner selects:
SELECT prof.user_id AS userId,
prof.first_name AS first,
prof.last_name AS last,
prof.birthdate,
prof.class_string AS classes,
prof.city,
prof.country,
prof.state,
prof.images,
prof.videos,
u.username,
u.avatar,
Count(cms.id) AS sponsor_count,
Count(ms.id) AS sched_count
FROM member_profiles prof
LEFT JOIN users u
ON u.id = prof.user_id
LEFT JOIN company_member_sponsorship cms
ON cms.member_id = prof.user_id
AND cms.status = 'sponsored'
LEFT JOIN member_schedules ms
ON ms.user_id = prof.user_id
GROUP BY u.id
ORDER BY ( prof.images + prof.videos * 5 + (
CASE
WHEN prof.expire_date > :time THEN 50
ELSE 0
end ) + sponsor_count * 20 + sched_count * 4
) DESC,
prof.last_name ASC
LIMIT :start, :records
If that's not any better, a explain of that query would help.

MySQL: Querying a leaderboard with ties

I know this is a popular topic, but I still haven't found quite what I'm looking for. I'd like to query one table
BOOKS_READ
id
user_id
book_id
to format a leaderboard of users who have listed the most books as having been read. When a user reads a book, a record matching the book id and the user id gets logged into the books_read table.
Is it possible to rank the results of this query, starting at 1, and with consideration of ties?
SELECT user_id, COUNT(*) AS book_count
FROM books_read
GROUP BY user_id
ORDER BY book_count DESC LIMIT 10
In the event of a tie, I would like to list an '=' sign in the results.
For example,
rank user_id book_count
=1 30 121
=1 17 121
2 101 119
=3 11 104
=3 91 104
Many thanks for any help! I don't mind using PHP to handle some of it, but I'm very interested in learning straight SQL solutions to these kinds of things :-)
SELECT GROUP_CONCAT(user_id, book_count
FROM (
SELECT user_id, COUNT(*) AS book_count
FROM books_read
GROUP BY user_id
ORDER BY book_count DESC
) AS T1
GROUP BY book_count
ORDER BY book_count
Giving you
user_id book_count
30,17 121
101 119
11,91 104
Which you can then use PHP to parse for ties.
<?php
$rank = 1;
while ($row = mysql_fetch_assoc($result)) {
$users = explode(',', $row['user_id'];
foreach ($users as $user) {
echo 'Rank: ' . $rank . ' ' . $user . "\n;
}
$rank++;
}
?>
What you want to do is count the number of people who have a better score than the record you're interested in, and add 1.
So, if you're the best player, zero people have a better score, so 0 + 1 = #1 ranking.
If five people have a better score, regardless of how many of them tied with each other, you're still 5 + 1 = 6.
The problem is that this is kind of an expensive query to do for every row you display. So you probably want to do it for the first row in your result set and then add one for everyone after that in your presentation layer (PHP). Be sure to take ties into consideration as you do this.
And the edge condition is that you don't know if the first row in your result set is tied with people ahead of "him". So you need to know how many people have the same score as the first person in your result set. (If you're starting at the top, this isn't a problem.)

Categories