SQL JOINS retrieving and displaying twice - php

I am joining 2 tables and trying to display the results. Only problem is every result is duplicated. I have 2 tables, messages and follow. Messages are what a certain user inputs, and I want it to display only to the people that follow that certain user.
Messages | Follow
-id -id
-message -mem1 (logged in user)
-userid -mem2 (followed user)
-created
$display ="";
$sql = mysql_query("
SELECT * FROM messages AS me
JOIN follow AS fl
ON me.userid = fl.mem2
WHERE fl.mem1 = $id (logged in user)
ORDER BY me.created
DESC LIMIT 10
") or die(mysql_error());
while($row=mysql_fetch_array($query)){
$msgid = $row["id"];
$message = $row["message"];
$userid = $row["userid"];
$created = $row["created"];
$display .="<?php echo $userid; ?> : <?php echo $message; ?><br />
<?php echo $created; ?>";
}
In the database there are no duplicates, just on the retrieve. Thanks for the input!
Edited: Display Code

You're getting "double" results, most likely because the query results in something different then you expect.
If I understand your table-structure correctly; you have a one-to-many relation from messages to followers.
In your query, however, you fetch combinations of messages and followers. Each line will consist of a unique combination of message<>follower.
In short; when a single message has two followers, you'll get two rows in the result with the same message; but a different follower entry.
If you want to show each message once; and then list all followers per message you can either use group-by functions (e.g group_concat) and group-by on message entries. The other possibility is to fetch the followers in a separate query once you've retrieved the message row, and then print the results from that query as the followers for that message.
If you're simply trying to get the number of followers; you can use a group-by on the UID of your message table and add a count on the UID or user ID of the follower table. (Do not that with group-by, the select * from shouldn't be used; but separate columns can.)

There's really only a few things that could cause the records to duplicate - try breaking down the query into basic components to see if there are more than one record:
SELECT * FROM follow WHERE mem1 = [id];
SELECT * FROM messages WHERE userid = [mem2 from previous result];
If either of the previous statements return more than one record, than the problem lies there. Other than that, I'd look at the PHP code to see if you're doing something there.
As for the query itself, I have a few recommendations:
Place the table with the filter first - the sooner you can narrow the results the better.
Specify a field list instead of using '*' - this will be a tiny bit more efficient, and clarify what you're after. Also, it will give 'DISTINCT' a fighting chance to work...
Here's an example:
SELECT DISTINCT me.id, me.message, me.userid, me.created
FROM follow AS fl
INNER JOIN messages AS me ON me.userid = fl.mem2
WHERE fl.mem1 = :logged_in_user
ORDER BY me.created
DESC LIMIT 10

If you are sure that there are no duplicates and the problem is in the query (you can check that by executing it from your database's interface), you can try two things:
Use the follow table as the leading one:
SELECT messages.*
FROM follow
JOIN messages ON follow.mem2=messages.userid
WHERE follow.mem1=$id
ORDER BY messages.created DESC
LIMIT 0,10;
Use a subquery:
SELECT *
FROM messages
WHERE userid IN(
SELECT DISCTINCT(mem2)
FROM follow
WHERE mem1=$id
)
ORDER BY created DESC
LIMIT 0,10;

Related

How do I improve the speed of these PHP MySQLi queries without indexing?

Lets start by saying that I cant use INDEXING as I need the INSERT, DELETE and UPDATE for this table to be super fast, which they are.
I have a page that displays a summary of order units collected in a database table. To populate the table an order number is created and then individual units associated with that order are scanned into the table to recored which units are associated with each order.
For the purposes of this example the table has the following columns.
id, UID, order, originator, receiver, datetime
The individual unit quantities can be in the 1000's per order and the entire table is growing to hundreds of thousands of units.
The summary page displays the number of units per order and the first and last unit number for each order. I limit the number of orders to be displayed to the last 30 order numbers.
For example:
Order 10 has 200 units. first UID 1510 last UID 1756
Order 11 has 300 units. first UID 1922 last UID 2831
..........
..........
Currently the response time for the query is about 3 seconds as the code performs the following:
Look up the last 30 orders by by id and sort by order number
While looking at each order number in the array
-- Count the number of database rows that have that order number
-- Select the first UID from all the rows as first
-- Select the last UID from all the rows as last
Display the result
I've determined the majority of the time is taken by the Count of the number of units in each order ~1.8 seconds and then determining the first and last numbers in each order ~1 second.
I am really interested in if there is a way to speed up these queries without INDEXING. Here is the code with the queries.
First request selects the last 30 orders processed selected by id and grouped by order number. This gives the last 30 unique order numbers.
$result = mysqli_query($con, "SELECT order, ANY_VALUE(receiver) AS receiver, ANY_VALUE(originator) AS originator, ANY_VALUE(id) AS id
FROM scandb
GROUP BY order
ORDER BY id
DESC LIMIT 30");
While fetching the last 30 order numbers count the number of units and the first and last UID for each order.
while($row=mysqli_fetch_array($result)){
$count = mysqli_fetch_array(mysqli_query($con, "SELECT order, COUNT(*) as count FROM scandb WHERE order ='".$row['order']."' "));
$firstLast = mysqli_fetch_array(mysqli_query($con, "SELECT (SELECT UID FROM scandb WHERE orderNumber ='".$row['order']."' ORDER BY UID LIMIT 1) as 'first', (SELECT UID FROM barcode WHERE order ='".$row['order']."' ORDER BY UID DESC LIMIT 1) as 'last'"));
echo "<td align= center>".$count['count']."</td>";
echo "<td align= center>".$firstLast['first']."</td>";
echo "<td align= center>".$firstLast['last']."</td>";
}
With 100K lines in the database this whole query is taking about 3 seconds. The majority of the time is in the $count and $firstlast queries. I'd like to know if there is a more efficient way to get this same data in a faster time without Indexing the table. Any special tricks that anyone has would be greatly appreciated.
Design your database with caution
This first tip may seems obvious, but the fact is that most database problems come from badly-designed table structure.
For example, I have seen people storing information such as client info and payment info in the same database column. For both the database system and developers who will have to work on it, this is not a good thing.
When creating a database, always put information on various tables, use clear naming standards and make use of primary keys.
Know what you should optimize
If you want to optimize a specific query, it is extremely useful to be able to get an in-depth look at the result of a query. Using the EXPLAIN statement, you will get lots of useful info on the result produced by a specific query, as shown in the example below:
EXPLAIN SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;
Don’t select what you don’t need
A very common way to get the desired data is to use the * symbol, which will get all fields from the desired table:
SELECT * FROM wp_posts;
Instead, you should definitely select only the desired fields as shown in the example below. On a very small site with, let’s say, one visitor per minute, that wouldn’t make a difference. But on a site such as Cats Who Code, it saves a lot of work for the database.
SELECT title, excerpt, author FROM wp_posts;
Avoid queries in loops
When using SQL along with a programming language such as PHP, it can be tempting to use SQL queries inside a loop. But doing so is like hammering your database with queries.
This example illustrates the whole “queries in loops” problem:
foreach ($display_order as $id => $ordinal) {
$sql = "UPDATE categories SET display_order = $ordinal WHERE id = $id";
mysql_query($sql);
}
Here is what you should do instead:
UPDATE categories
SET display_order = CASE id
WHEN 1 THEN 3
WHEN 2 THEN 4
WHEN 3 THEN 5
END
WHERE id IN (1,2,3)
Use join instead of subqueries
As a programmer, subqueries are something that you can be tempted to use and abuse. Subqueries, as show below, can be very useful:
SELECT a.id,
(SELECT MAX(created)
FROM posts
WHERE author_id = a.id)
AS latest_post FROM authors a
Although subqueries are useful, they often can be replaced by a join, which is definitely faster to execute.
SELECT a.id, MAX(p.created) AS latest_post
FROM authors a
INNER JOIN posts p
ON (a.id = p.author_id)
GROUP BY a.id
Source: http://20bits.com/articles/10-tips-for-optimizing-mysql-queries-that-dont-suck/

Echo multiple values with same column name in same table

I have 2 tables, a users table and a trade table.
Which look like:
The structure of my code right now is:
<?php
$history = mysqli_query($con, "SELECT * FROM .......");
while($row = mysqli_fetch_array($history)) {
echo("The sentence");
} ?>
Problem I'm facing is that I'm trying to echo the user_name which in one case has to be the receiver and other the person giving it.
Pro tip: Never use SELECT * in software unless you know exactly why you are doing so. In your case it is harmful.
I'm assuming your query is really against the user and trade tables you mentioned in your question.
First, recast your query using 21st century SQL, as follows:
SELECT *
FROM trade AS t
JOIN user AS s ON s.user_id = t.user_id_sender
WHERE s.facebook_id = $fbid
Second, use this to retrieve your user's names and the item id traded.
SELECT s.user_name AS sender,
r.user_name AS receiver,
t.trade_id AS item_id
FROM trade AS t
JOIN user AS s ON s.user_id = t.user_id_sender
JOIN user AS r ON r.user_id = t.user_id_receiver
WHERE s.facebook_id = $fbid
See how we JOIN the user table twice, with two different aliases s (for sender) and r (for receiver)? That's the trick to fetching both names from IDs.
See how we employ the aliases sender and receiver to disambiguate the two user_name columns in the result set?
Now, when you use the php fetch_array function, you'll end up with these elements in the array.
$history['sender']
$history['receiver']
$history['item_id']
The array index strings correspond to the alias names you specified in your SELECT clause in your query.
So, one reason to avoid SELECT * is that you can get more than one column with the same name, and that means fetch_array will eliminate those duplicates and so it will lose useful information from your result set.

mysql query returning more than one value - should return one

I have a website where visitors can create "battles" and upload videos to compete in these battles.
The following mySQL query is trying to retrieve details of every "battle" in a database.
The problem I have is that if a "battle creator" has uploaded two "videos" to the same battle, a duplicate battle prints out.
How can I make the query only print out one value for each battle, even if the videos table has two entries under the same battle_id?
Thanks!
SELECT * from Battles, Player, Video
WHERE (Battles.battle_creator = Player.player_id
AND Battles.battle_id = Video.battle_id
AND Video.player_id = Battles.battle_creator)
ORDER BY Battles.battle_date DESC;
There's no way to get the information you're asking for from a single query once multiple videos have been assigned to a battle by a single user.
The best way to get all the data for the battle is to separate your query into two subqueries:
SELECT * from Battles, Player
WHERE Battles.battle_creator = Player.player_id
ORDER BY Battles.battle_date DESC;
...and then:
SELECT * from Video
ORDER BY Battles.battle_date DESC, Player.player_id;
The first query will give you one row per battle; the second will give you all videos for all battles, which you can iterate over.
From a scaling perspective, you'll do better to avoid JOINs altogether, so the extra work will be well worth it.
You can either add LIMIT 1 clause to your query to only get first result, or use DISTINCT clause like
SELECT DISTINCT *
FROM ...
That said, you should not use "SELECT * " when querying for more than one table -
use "SELECT table.*" or "SELECT table.field1, table.field2, ..." to be more specific.
You can't do "exactly" that, because your query:
SELECT * from Battles, Player, Video ...
is implicitly asking for all the videos. So you need to ask yourself first, how do I select that one video I want?
If you just want one video, whatever, then add LIMIT 1 to the query and be done with that. ORDER BY video_date ASC or DESC before the LIMIT to retrieve the earliest or latest video.
Otherwise, you have to do something like:
SELECT * from Battles
JOIN Player ON (Battles.battle_creator = Player.player_id)
JOIN Video ON (Battles.battle_id = Video.battle_id
AND Video.player_id = Battles.battle_creator)
WHERE Video.video_id = (SELECT MIN(video_id) FROM Video AS Video2 WHERE
Battles.battle_id = Video2.battle_id
AND Video2.player_id = Battles.battle_creator)
ORDER BY Battles.battle_date DESC;
In the example above I used, as "video choice criterion", "the video with smallest video_id". You will want to have an index on (Video.video_id), something like
CREATE INDEX video_ndx ON Video(player_id, battle_id, video_id);
As Ninsuo's comment points out, the proper way to control this is, after your ORDER BY clause, specify LIMIT 1.
This won't work if you want the entire table, just without duplicates. Consider running some comparison checks on your returned data, or using SELECT DISTINCT.

Counting occurences in second table compared to first

I have two tables, one holds the information of contributors to my site and one holds information on photographs contributed.
For the admin side of the site, I want to create a table using php and mysql that displays all contributors but also counts the number of photographs each contributor has available for the site.
I get the list of names using this code
SELECT *
FROM site_con
ORDER BY surn ASC
I have then set up a loop to list all the names but have added a query within that loop to count the number of photographs using this code
$contributor = $row_rsContrib['con_Code'];
mysql_select_db($database_connGrowl, $connGrowl);
$query_rsCounter = "SELECT COUNT(*) AS Count
FROM site_phts
WHERE photter = $contributor";
$rsCounter = mysql_query($query_rsCounter, $connGrowl) or die(mysql_error());
$row_rsCounter = mysql_fetch_assoc($rsCounter);
$totalRows_rsCounter = mysql_num_rows($rsCounter);
The only problem is when '$contributor' is not in the photographs table, it returns an error.
Any ideas?
You can get the list of contributors & the number of photos in a single query:
SELECT sc.*,
COALESCE(x.numPhotos, 0) AS numPht
FROM SITE_CON sc
LEFT JOIN (SELECT sp.photter,
COUNT(*) AS numPhotos
FROM SITE_PHTS sp
GROUP BY sp.photter) x ON x.photter = sc.con_code
ORDER BY ssc.surn
Your query fails because a photographer doesn't necessarily have contributions -- the query above returns the list of photographers, and those without photos associated will have a numPht value of zero. Here's a primer on JOINs, to help explain the OUTER JOIN that's being used.
Actually the best way to do this is by using MSQL to count rather than PHP:
SELECT site_con.*, COUNT( photo_id )
FROM site_con
LEFT JOIN site_phts ON site_con.con_Code = site_phts.photter
GROUP BY site_con.con_Code
ORDER BY site_con.surn
The LEFT JOIN has the special property of creating NULL entries when there is no row in the right table (photos) that matches a contributor row. COUNT will not count these NULL entries. (You need some unique column in the photos table, I used photo_id for that.)
this is the relation between Contributors and photographs:
1 photograph can have a most 1 Contributor
1 Contributor can have a most infinit photograph
Contributor <-(0,n)------(0,1)-> Photograph
so you might wanna add a connexion betweet those two tables, I mean you add the con_id to the photographs table (as a column).
this way you'll be able to retrieve all the informations in one SQL query.
(like OMG Ponies just said)
Do something like this, I believe this should work :
$result = mysql_query("SELECT COUNT(*) AS Count FROM site_phts WHERE photter = '$contributor'"); // put the single quote if $contributor is a string value
//use mysql_fetch_array
if ($row = mysql_fetch_array($result, MYSQL_NUM)) {
printf("ID: %d", $row[0]);
}
Hopefully this works, Good luck mate !

Mysql: Getting Count of Comma Separated Values With Like

I decided to use favs (id's of users which marked that post as a favorite) as a comma separated list in a favs column which is also in messages table with sender,url,content etc..
But when I try to count those rows with a query like:
select count(id)
from messages
where favs like '%userid%'
of course it returns a wrong result because all id's may be a part of another's
For example while querying for id=1 it also increase the counter for any other content that is favorited by user id 11...
Can you please tell me your idea or any solution to make this system work?
With a few or's, you can have an ugly solution:
select count(id) from messages where favs like 'userid,%' or favs like '%,userid,%' or favs like '%,userid'
There's likely a more elegant solution, but that one will at least return the result you're looking for, I believe.
Is it possible to change your data model such that the association between users and their favorite messages is instead stored in another table?
Storing the associations in a single column negates the advantages of a relational database. You pay a performance cost using the like function, you can no longer store additional data about the relationship, and the data is harder to query.
An alternative model might looking something like this (can't include an image since I'm a new user, but I made one here):
users
- id
messages
- id
favorite_messages
- user_id (foreign key to users.id)
- message_id (foreign key to messages.id)
With that in place, your original query would be simplified to the following:
select count(1) from favorite_messages where user_id = userid
Additionally, you can do things like get a list of a user's favorite messages:
select
*
from
messages
inner join favorite_messages
on messages.id = favorite_messages.message_id
where
user_id = userid
should using this :
SELECT count(id) FROM messages WHERE FIND_IN_SET('userid',favs) > 0
You might have to get the value, explode it with PHP and then count the array.
There are ways to do it in MySQL, but from what I've seen, they are a hassle.

Categories