Codeigniter Join Table to Showing Different Content Results - php

I have 7 tables to store user data such posts, images, updates, comments, likes, reposts and user itself.
And here is my questions: How to using right query to execute join table?
I'm using this query:
if ( ! function_exists('getTimeline'))
{
function getTimelines($contributor = null, $limit = 10, $offset = 0)
{
$CI =& get_instance();
$CI->db->select('
abycms_posts.*,
abycms_images.imageID,
abycms_images.contributor as owner,
abycms_images.imageContent,
abycms_images.imageFile,
abycms_images.imageSlug,
abycms_images.timestamp as date,
abycms_images.visits_count as visits,
abycms_updates.updateID,
abycms_updates.userID as updater,
abycms_updates.updateContent,
abycms_updates.visibility,
abycms_updates.timestamp as update_time,
abycms_likes.likeID,
abycms_likes.userID as userLike,
abycms_likes.type as likeType,
abycms_likes.timestamp as like_time,
abycms_comments.commentID,
abycms_comments.userID as commentUser,
abycms_comments.type as commentType,
abycms_comments.timestamp as comment_time,
abycms_reposts.repostID,
abycms_reposts.userID as repostUser,
abycms_reposts.type as repostType,
abycms_reposts.timestamp as repost_time
');
$CI->db->from('abycms_users');
$CI->db->join('abycms_posts', 'abycms_posts.contributor = abycms_users.userID', 'left');
$CI->db->join('abycms_images', 'abycms_images.contributor = abycms_users.userID', 'left');
$CI->db->join('abycms_updates', 'abycms_updates.userID = abycms_users.userID', 'left');
$CI->db->join('abycms_likes', 'abycms_likes.userID = abycms_users.userID', 'left');
$CI->db->join('abycms_comments', 'abycms_comments.userID = abycms_users.userID', 'left');
$CI->db->join('abycms_reposts', 'abycms_reposts.userID = abycms_users.userID', 'left');
$CI->db->where('abycms_users.userID', $contributor);
$CI->db->limit($limit, $offset);
// How to order results by newest `timestamp` for posts, images, updates, comments, likes or reposts?
$CI->db->order_by('abycms_posts.timestamp', 'desc');
// How to handle not duplicate `postID` or `imageID` also group it by different `type`s?
$CI->db->group_by('abycms_posts.postID, abycms_images.imageID');
$query = $CI->db->get();
if($query->num_rows() > 0)
{
return $query->result_array();
}
else
{
return array();
}
}
}
And there is my view to handle results in different type:
foreach(getTimelines($page['userID'], $limit, $offset) as $row)
{
if($row['updateID'] != null) // Updating Status
{
// This status updates
}
elseif($row['postID'] != null) // Writing Article
{
// This is posts
}
elseif($row['imageID'] != null) // Uploading Image
{
// This is images
}
elseif($row['commentID'] != null) // Commented on Post
{
// This is comments
}
elseif($row['likeID'] != null) // Liking User Post
{
// This is likes
}
elseif($row['repostID'] != null) // Reposting User Post
{
// This is reposts
}
}
When i'm using above query, results is showing up but i have no idea to separate content types. It always shown as status updates, and all unique id such postID, imageID, updateID, repostID, likeID and commentID have same value.

The query is generating a partial cross product.
For every row returned from _users, MySQL is getting all of the matching rows from _likes.
For sake of an example, we'll assume that there is one row being returned from _users, and there are four matching rows in _likes, returning (so far) a total of four rows. The row from _users gets matched to each of the four rows from _likes. All of the columns from the row from _users is duplicated into each of the four rows.
And from the _posts table, for the sake of an example, we'll assume that there are two rows that match. So each of those two rows returned from _posts is going to matched to each of the four rows we already have, giving us a total of eight rows. (Every row returned from _posts is matched with every row returned from _likes.)
From the _comments table, for this example, let's say there are six rows returned. Each of those rows gets matched with the eight rows we already have, giving us a total of 48 rows. And a lot of values from the columns of each table is getting "duplicated" into new rows, as multiple rows from the new tables are joined in.
And so on, with each additional joined table.
It's a partial "cross product" of the tables. (A semi-Cartesian product?)
If you want to return distinct list of _posts, a distinct list of _likes, and distinct list of _comments, etc. then you could run a separate query for each table. That would avoid the "duplication" that happens due to the join operation. That's probably the simplest approach.
Otherwise, if you want to get a distinct list of _posts, _likes, _comments, et al. out of the resultset the current query is returning, you'd need the client to sift through the rows to filter out the duplicated _posts, _likes, _comments. You'd need to have a unique identifier for each of those tables included in the returned rows.
Basically, your code would need to build separate arrays for _posts, _likes, _comments. For each row from the resultset, you'd need to check whether the values from the _posts columns were from a row from _posts you've already processed. If it's one you've already processed, discard it, otherwise, add it to the array. Essentially de-duplicating the rows down into separate results from each table, in the form that you'd get from a separate query of each table.

Related

Codeigniter Complex MySQL Query

I got two table which I would like to query from, which the users table and the user_job table
users table structure
user_job table
What I want to achieve is to write a MySQL query in CodeIgniter to display user information from users table if user_status in users table is "Active" And if there is no row in user_job table where user_job_status is equal to "On Probation" or user_job_status is equal to "Active"
In simple English, I want to display a user information if a user not currently on a job.
My current Codeigniter Model code is:
//get all user that are not currently assigned to a position
function get_idle_user(){
$this->db->select('*');
$this->db->from('users');
$this->db->join('user_job', 'user_job.user_id_fk = users.user_id', 'INNER');
$this->db->where('users.user_status','Active');
$this->db->where("(user_job.user_job_status != 'Active' OR user_job.user_job_status != 'On Probation')", NULL, FALSE);
$this->db->group_by('users.user_id');
if($query = $this->db->get()){
return $query;
}else{
return false;
}
}
The problem with this code that it will still display user information if there is a row related to a user and user_job_status does not satisfy the where condition above.
Please Help.
I believe this is what you are after ...
$db->select('user_id_fk,COUNT(*) as `Tally`');
$db->where('user_job_status','On Probabtion');
$db->or_where('user_job_status','Active');
$db->group_by('user_id_fk');
$sub = $db->get_compiled_select('user_job_table');
$db->join("($sub) ujt",'ujt.user_id_fk = users.user_id AND Tally = 0','left');
$db->where('user_status','Active');
// View this query in full
//echo $db->get_compiled_select('users_table');
// Get the data
$data = $db->get('users_table')->result_array();
Note:
You don't need select('*') - it isn't needed
You don't need ->from() - it can be used in the get() element
I have left in the get_compiled_select which will allow you to see the full query and get an idea what its doing. You should comment out the $data line if you uncomment the echo line.
What this query is doing is saying get all users from the job_table which don't have the status Active or On Probation and a list of User ID's. That way you can then get Active users from the users_table and users with a tally of 0.

PHP and SQL: code is really slow

$unique = array();
$sql = "SELECT ID, TitleName, ArtistDisplayName, Mix FROM values_to_insert as A
WHERE A.ID = ";
//Get a single row from our data that needs to be inserted...
while($result = $conn->query(($sql. $count)))
{
//Get the $data of the single row query for inserting.
$data = mysqli_fetch_row($result);
$count++;
//SQL to get a match of the single row of $data we just fetched...
$get_match = "SELECT TitleName_ti, Artist_ti, RemixName_ti from titles as B
Where B.TitleName_ti = '$data[1]'
and B.Artist_ti = '$data[2]'
and B.RemixName_ti = '$data[3]'
LIMIT 1";
//If this query returns a match, then push this data to our $unique value array.
if(!$result = $conn->query($get_match))
{
//If this data has been pushed already, (since our data includes repeats), then don't
//put a repeat of the data into our unique array. Else, push the data.
if(!in_array($unique, $data))
{
echo 'Pushed to array: ' . $data[0] . "---" . $data[1] . "</br>";
array_push($unique, $data);
}
else
echo'Nothing pushed... </br>';
}
}
This has taken 5+ minutes and nothing has even printed to screen. I'm curious as to what is eating up so much time and possibly an alternative method or function for whatever it is taking all this time up. I guess some pointers in the right direction would be great.
This code basically gets all rows, one at a time, of table 'A'. Checks if there is a match in table 'B', and if there is, then I don't want that $data, but if there isn't, I then check whether or not the data itself is a repeat because my table 'A' has some repeat values.
Table A has 60,000 rows
Table B has 200,000 rows
Queries within queries are rarely a good idea
But there appear to be multiple issues with your script. It might be easier to just do the whole lot in SQL and push the results to the array each time. SQL can remove the duplicates:-
<?php
$unique = array();
$sql = "SELECT DISTINCT A.ID,
A.TitleName,
A.ArtistDisplayName,
A.Mix
FROM values_to_insert as A
LEFT OUTER JOIN titles as B
ON B.TitleName_ti = A.ID
and B.Artist_ti = A.TitleName
and B.RemixName_ti = A.ArtistDisplayName
WHERE B.TitleName_ti IS NULL
ORDER BY a.ID";
if($result = $conn->query(($sql)))
{
//Get the $data of the single row query for inserting.
while($data = mysqli_fetch_row($result))
{
array_push($unique, $data);
}
}
As to your original query.
You have a count (I presume it is initialised to 0, but if a character then that will do odd things), and get the records with that value. If the first id was 1,000,000,000 then you have done 1b queries before you ever find a record to process. You can just get all the rows in ID order anyway by removing the WHERE clause and ordering by ID.
You then just get a single record from a 2nd query where the details match, but only process them if no record is found. You do not use any of the values that are returned. You can do this by doing a LEFT OUTER JOIN to get matches, and checking that there was no match in the WHERE clause.
EDIT - as you have pointed out, the fields you appear to be using to match the records do not appear to logically match. I have used them as you did but I expect you really want to match B.TitleName_ti to A.TitleName, B.Artist_ti to A.ArtistDisplayName and B.RemixName_ti to A.Mix

Check if a value exists in mysql column

Is there a way to check if a value exists in a mysql column? I have table songs, and there are some columns, one of them is called 'agent_ip' where i will put a list/array of all user ip's that will visit the site. I need to check if current user ip is present in column 'agent_ip'. Here is some of my code:
public function voteSong($song_id, $case, $agent_ip) {
$query = $this->link->prepare("SELECT * FROM songs WHERE id = ? LIMIT 1");
$query->bindValue(1, $song_id);
$query->execute();
$rowcount = $query->rowCount();
if ($rowcount != 0)
{
if (!in_array($agent_ip, $r['ip']))
{
if ($case === 'like')
{
while($r = $query->fetch())
{
$vote = $r['votes'] + 1;
}
}
elseif ($case === 'dislike')
{
while ($r = $query->fetch())
{
if ($r['votes'] > 0)
{
$vote = $r['votes'] - 1;
}
else
{
$vote = 0;
}
}
}
$query = $this->link->prepare("UPDATE songs SET datetime = ?, votes = ?, agent_ip = ? WHERE id = ?");
$query->execute(array(date("Y-m-d H:i:s"), $vote, $agent_ip, $song_id));
}
}
}
The line if(!in_array($agent_ip, $r['ip'])) contains the wrong function which won't work, but i need an alternative for mysql. $r['ip'] variable is data from the 'agent_ip' column which look like this 127.0.0.1, 127.0.0.1, 127.0.0.1 (using 127.0.0.1 just for example, every 127.0.0.1 is a different ip)
If you're only checking against a single IP, why don't you just modify your query from:
"SELECT * FROM songs WHERE id = ? LIMIT 1"
To:
"SELECT * FROM songs WHERE id = ? AND agent_ip = ? LIMIT 1"
It seems a bit wasteful to query your whole result set when you are only querying against a specific IP and returning a single row.
EDIT: Your current method would be extremely inefficient, you are passing a unique agent_ip each time you want to query a song to check if the IP exists, that would be fine, but you are creating a new DB connection every time from which you pull back all info which belongs to that song.
Lets say we have 1 song, and 3IP's, currently the application would work like this:
1) Call the method, passing IP_1
2) Query the database getting all songs for ID1
3) Check if IP_1 is in the result set and do process
4) Call the method, passing IP_2
5) Query the database getting all songs for ID1
6) Check if IP_2 is in the result set and do process
7) Call the method, passing IP_3
8) Query the database getting all songs for ID1
9) Check if IP_2 is in the result set and do process
As you can see, there is a lot of repetition here which is going to hinder your apps performance as it scales, you would be so much better modifying your current function to accept a list of results for a song which is pre-queried only once and then recursively call a check function by passing that result array with your unique IP address.
UPDATE You stated I understand that i need to have 2 tables(1 = songs; 2 = votes). But i cannot imagine how i will get songs from database, arranged by votes quantity.
You should read SQL's JOIN documentation, the concept is simple - JOIN allows you to pull back a more detailed set of information based on what you want to query, in your example you may want to find out how many votes a specific song has.
Your tables may look like:
Songs
SONG_ID Primary Key
SONG_TITLE
SONG_DURATION
SONG_TAGS
Votes
VOTE_ID Primary Key
SONG_ID Foreign Key - (references the song_id table)
VOTE_RES Bool (either 0 for no, 1 for yes)
AGENT_IP Who sent the vote
You could then find out how many people said they liked the song by performing a join:
SELECT * FROM songs
JOIN votes
ON songs.song_id = votes.song_id
WHERE songs.song_id = 1
AND votes.vote_res = 1;
This would return all the song with the id of 1 and all of its associated likes. Hope that helps a bit :)
First you need to deserialize/decode the data from the column to the proper php array and then you can use in_array function. In your post edit you stated that you have a comma separated list of IP's, so to convert it to array you need to use an explode function:
$ip_list = explode(', ', $r['ip']);
now you can use the in_array function on the new array:
if(!in_array($agent_ip, $ip_list))

results table in php

I am having trouble understanding how a loop I have created works.
I have input fields on another page, which where results are sent to the results table in my database, with team name, team score, opposition score, opposition name.
Now the page where the results are entered mirrors the current contents of the results table so that previously entered scores appear already in the correct input field, and the ones with no socres enetered are at 0.
This page works perfectly in that a score that is edited, replaces the score in the results table, blank ones are left blank etc.
Howwever, I need to use these results to update my league table table in my database.
Currently I have a rather large loop, which (after I have fetched team name, team score, opposition score, opposition name, from the results table), works out how many points to give that team, and the opposing team, plus how many to add to the 'win' column and the 'loss' column etc of my league table.
The problem I have is that it will only ever do one result per team, because on each iteration of the loop, as soon as it finds a matching if statement (ie if team_score>opposition score) it updates the table, before running through the other results to find that teams results.
Because an edited result needs to be treated as an edited result, not an additional result, this is the only way I have been able to find to get even close.
here is a snippet of the code. There are many more if statementswithin the for loop but they are not needed to describe the problem I am having.
$query = $database->query("SELECT team_name, team_score, opposition_score, opposition_name from results_a");
while ($row = $query->fetch(PDO::FETCH_NUM)) {
$team[] = ($row[0]);
$team_score[] = ($row[1]);
$opposition_score[] = ($row[2]);
$opposition[] = ($row[3]);
}
$count=count($team);
for ($i = 0; $i < $count; $i++) {
$team_bonus[$i] = $team_score[$i] / 2;
$opposition_bonus[] = $opposition_score[$i] / 2;
//TEAM WINS, NO OPPOSITION BONUS
else if (($team_score[$i] > $opposition_score[$i]) && ($team_bonus[$i] > $opposition_score[$i])) {
$team_points[]+=3;
$team_win[]+=1;
$team_draw[]+=0;
$team_loss[]+=0;
$team_extra[]+=0;
$opposition_points[]+=0;
$opp_win[]+=0;
$opp_draw[]+=0;
$opp_loss[]+=1;
$opp_extra[]+=0;
$team_played[]+=1;
$opposition_played[]+=1;
}
$team_gd[] = $team_score[$i] - $opposition_score[$i];
$opposition_gd[] = $opposition_score[$i] - $team_score[$i];
//UPDATE LEAGUE TABLE
$query = $database->query("UPDATE pool_a SET played='$team_played[$i]',
win='$team_win[$i]',
draw='$team_draw[$i]',
loss='$team_loss[$i]',
goals_for='$team_score[$i]',
goals_against='$opposition_score[$i]',
goal_difference='$team_gd[$i]',
bonus_points='$team_extra[$i]',
points='$team_points[$i]'
where team_name = '$team[$i]'");
$query2 = $database->query("UPDATE pool_a SET played='$opposition_played[$i]',
win='$opp_win[$i]',
draw='$opp_draw[$i]',
loss='$opp_loss[$i]',
goals_for='$opposition_score[$i]',
goals_against='$team_score[$i]',
goal_difference='$opposition_gd[$i]',
bonus_points='$opp_extra[$i]',
points='$opposition_points[$i]'
where team_name='$opposition[$i]'");
The+= within the if statement are useless, because instead of going to the next row where the team name is the first teams name, it runs the query, then when that teams name appears again, it replaces the data next time the query runs.
Very stuck, have spent a lot of time on this!
If you need any more info let me know.
Many thanks

Selecting and updating any number of rows in large table using CodeIgniter

I've got a large table, about 10 million rows, and I'm trying to build a function that selects a random number of rows (anything between 1 and all), updates a field on the selected rows to x (winner) and updates the field on remaining rows to y (loser).
All rows have a unique id but not all rows will be selected from (the table has competition entries for a number of competitions but the function needs to select winners for one competition only).
I've considered ordering by random but it's too slow for a big table.
I've also considered assigning a random value into the row when it's inserted into the table so that it can be used to order but it would have to be unique and there can be batch updates for thousands of rows so it would be slow to assign a unique random value to each row.
There's too much data to save the result as an array and shuffle.
Can anyone suggest other solutions?
This is how I would do it if the table wasn't huge:
function getWinnersLosers($winnercount,$allwinners) {
// Must have draw id and ticketstatus id
if ( ! $this->draw_id || ! $this->ticketstatus_id ) return false;
// Update winners
$this->db->where('draw_id', $this->draw_id);
$this->db->where('ticketstatus_id', CONST_TICKETSTATUS_ENTERED);
$this->db->order_by('id', 'RANDOM');
if ( ! $allwinners ) $this->db->limit($winnercount);
$data = array( 'ticketstatus_id' => CONST_TICKETSTATUS_WINNERUNCLAIMED );
$query = $this->db->update('drawtickets',$data);
// Update losers (remaining tickets)
$this->db->where('draw_id', $this->draw_id);
$this->db->where('ticketstatus_id !=', CONST_TICKETSTATUS_WINNERUNCLAIMED);
$data = array( 'ticketstatus_id' => CONST_TICKETSTATUS_LOSE );
$query = $this->db->update('drawtickets',$data);
}

Categories