MySQL join returning redundant data - php

I am building an auction website. Right now, I am building the item description page, that has item details, as well as current bid history. My bids table has a FK of Item_id.
My current query looks something like this:
SELECT bids.Item_id, bids.User_email, bids.Bid_amount, products.*
FROM bids
INNER JOIN products
ON bids.Item_id=products.Item_id;
This returns all of the bid information I need - but also returns the item description for every bid row. I only need the product information once. Is it best to just use two queries on this?
Any help is appreciated

If you need the bids data separately from the products data, then you should use two queries.
One query cannot really be arrange to return different columns for different rows.

SELECT b.Item_id, b.User_email, b.Bid_amount, p.*
FROM bids b
INNER JOIN products p
ON b.Item_id=p.Item_id
WHERE p.Item_id=something;
This will not repeat products..

Related

MySQL & PHP: Selecting buyer records by product purchased

I have a somewhat complex MySQL query I'm attempting to derive but I'm just confusing the hell out of myself.
I have several tables: products, stores, buyer, payment and buyer_purchase.
buyer_purchase is basically made up of foreign keys. This table more or less links all the other together. The tables are filled up with junk data. The actual records themselves are not as important as the methods used to retrieve them.
I need to be able to select buyers who have purchased a specific product, how many of that item they've purchased, what store they bought it from, and the payment method used.
It will all be displayed in a webpage using PHP in the following manner:
Product1
name of buyer1
Payment method
Number Purchased
Store purchased From
product2
Name of buyer1
Payment
Number Purchased
Store Purchased from
Name of buyer2
Payment
etc.
I have a query that somewhat works:
SELECT products.item, stores.seller_name, buyer.f_name, buyer.l_name, payment.company
FROM buyer_purchases
INNER JOIN products
ON products.id = buyer_purchases.product_listings_id
INNER JOIN payment
ON payment.id = buyer_purchases.buyer_id
INNER JOIN buyer
ON buyer.id = buyer_purchases.buyer_id
INNER JOIN stores
ON stores.id = buyer_purchases.product_listings_stores_id
ORDER BY products.item
This will return all the data I need in a joined table, but getting it formatted to display as I need is what is confusing me. Obviously, I need to invoke a COUNT to display how many of a given product someone bought, but I'm not sure how to implement it. Further, I think I need GROUP BY rather than ORDER BY, but GROUP BY eliminates all but one record for each product. I've not been able to implement a subquery that MySQL doesn't complain about.
Right now, I'm trying to pull all of the data from the tables in a single query, and store it as a PHP array I can iterate through. If this is not the best way to go about this process, I can find another way.
I guess you just need more experience with MySQL, the trick is in the way you group the elements. In this case you want to group by multiple fields.
SELECT
products.item as product,
concat(buyer.f_name,' ',buyer.l_name) as buyer,
payment.company as payment,
count(products.id) as number_purchased
stores.seller_name as store,
FROM buyer_purchases
INNER JOIN products
ON products.id = buyer_purchases.product_listings_id
INNER JOIN payment
ON payment.id = buyer_purchases.buyer_id
INNER JOIN buyer
ON buyer.id = buyer_purchases.buyer_id
INNER JOIN stores
ON stores.id = buyer_purchases.product_listings_stores_id
GROUP BY buyer.id,stores.id,payment.id
ORDER BY products.item
You can have multiple ORDER BY clauses which work in order, so looks like you want ORDER BY products.item, buyer.id which will list all records for item 1 first, ordered by customer, then all records for item 2 ordered by cutomer etc

SQL Nested Queries - Part 2

Just the other day I asked a question about nesting queries together - in a nutshell it was about how to pull data from two different related tables when there may or may not be linked rows in a second table. That led me down the path to LEFT/RIGHT JOINS and was easy to pick up. Now, however I am coming across an entirely new problem, and this is stumping me.
When one pulls back information using LEFT/RIGHT JOINS (let's assume that it's just a LEFT JOIN for the rest of this question), in the scenario where there are multiple matching rows in Table B, Table A's information is pulled back each time a match in Table B is found, and this results in entirely new rows being pulled back as well. Is there a way to aggregate the information from Table B when doing LEFT JOINS?
For example, let's say that we have a products table with only one product row in it and an images table, which has 6 image rows, all linked to the one product in the products table.
Let's say that we then use the query:
SELECT product_name, large_image, medium_image FROM products
LEFT JOIN images ON images.image_product_Id = products.productId
This will pull back 6 rows, with repeated information for the product_name in each row, but unique information for the large_image and medium_image.
Is there a way to pull back this information in one row, with the rows from the images table nested? The result returning one row, and the image information for the 6 images in an associative array that is pulled back as a column?
Ideally, this information would come back as :
[
1 => [ product_name => 'Apple iPhone',
images => [
{
large_image=>'image1.jpg',
'medium_image'=>'image1_m.jpg'
},
{
large_image=>'image2.jpg',
medium_image=>'image2_m.jpg'}
...
]
]
]
The only solution I can currently think of is to pull the information for the products back in one query:
SELECT product_name, productId FROM products
and while iterating over that information, making subsequent queries to the database for each row to get all the images for that product:
SELECT large_image, medium_image FROM images WHERE images.image_product_Id
= '$productId'
where '$productId' is the parameter I am feeding the second query, and which represents the productId column from the first query. This seems not only inefficient, but bad programming practice. Could someone point me in the right direction?
I hope I was able to word this clearly - if not, please let me know what I can do word it better!
Thanks in advance :)
As per your question I found an answer that works for you, see the below SQL.
SELECT product_name,
GROUP_CONCAT(DISTINCT large_image) AS LargeImages,
GROUP_CONCAT(DISTINCT medium_image) AS medium_images
FROM products
LEFT JOIN images ON images.image_product_Id = products.productId
GROUP BY productId

Showing users who liked an item in an item list

This is an issue that I've deemed impractical to implement but I would like to get some feedback to confirm.
I have a product and users database, where users can like products, the like data is stored in a reference table with just pid and uid.
The client request is to show 3 users who have liked every product in the product listing.
The problem is, its not possible to get this data in one query for the product listing,
How I once implemented and subsequently un-implemented it was to perform a request for the users who have liked the products during the loop through the product list.
ie.
foreach($prods as $row):
$likers = $this->model->get_likers($row->id);
endforeach;
That works, but obviously results in not only super slow product listings, and also creates a big strain on the database/cpu.
The final solution that was implemented was to only show the latest user who has liked it (this can be gotten from a join in the products list query) and have a link showing how many people have liked, and upon clicking on it, opens a ajax list of likers.
So my question is, is there actually a technique to show likers on the product list, or is it simply not possible to execute practically? I notice actually for most social media sites, they do not show all likers on the listings, and do employ the 'click to see likers' method. However, they do show comments per items on the listing, and this is actually involves the same problem doesn't it?
Edit: mock up attached on the desired outcome. there would be 30 products per page.
By reading your comment reply to Alex.Ritna ,yes you can get the x no. of results with per group ,using GROUP_CONCAT() and the SUBSTRING_INDEX() it will show the likers seperated by comma or whatever separator you specified in the query (i have used ||).ORDER BY clause can be used in group_concat function.As there is no schema information is available so i assume you have one product table one user table and a junction table that maintains the relation of user and product.In the substring function i have used x=3
SELECT p.*,
COUNT(*) total_likes,
SUBSTRING_INDEX(
GROUP_CONCAT( CONCAT(u.firstname,' ',u.lastname) ORDER BY some_column DESC SEPARATOR '||'),
'||',3) x_no_of_likers
FROM product p
LEFT JOIN junction_table jt ON(p.id=jt.product_id)
INNER JOIN users u ON(u.id=jt.user_id)
GROUP BY p.id
Fiddle
Now at your application level you just have to loop through the products and split the x_no_of_likers by separator you the likers per product
foreach($prods as $row):
$likers=explode('||',$row['x_no_of_likers']);
$total_likes= $row['total_likes'];
foreach($likers as $user):
....
endforeach;
endforeach;
Note there is a default 1024 character limit set on GROUP_CONCAT() but you can also increase it by following the GROUP_CONCAT() manual
Edit from comments This is another way how to get n results per group, from this you can get all the fields from your user table i have used some variables to get the rank for product group ,used subquery for junction_table to get the rank and in outer select i have filtered records with this rank using HAVING jt.user_rank <=3 so it will give three users records per product ,i have also used subquery for products (SELECT * FROM product LIMIT 30 ) so the first 30 groups will have 3 results for each,for below query limit cannot be used at the end so i have used in the subquery
SELECT p.id,p.title,u.firstname,u.lastname,u.thumbnail,jt.user_rank
FROM
(SELECT * FROM `product` LIMIT 30 ) p
LEFT JOIN
( SELECT j.*,
#current_rank:= CASE WHEN #current_rank = product_id THEN #user_rank:=#user_rank +1 ELSE #user_rank:=1 END user_rank,
#current_rank:=product_id
FROM `junction_table` j ,
(SELECT #user_rank:=0,#current_rank:=0) r
ORDER BY product_id
) jt ON(jt.product_id = p.id)
LEFT JOIN `users` u ON (jt.`user_id` = u.`id`)
HAVING jt.user_rank <=3
ORDER BY p.id
Fiddle n results per group
You should be able to get a list of all users that have liked all products with this sql.
select uid,
count(pid) as liked_products
from product_user
group by uid
having liked_products = (select count(1) from products);
But as data grows this query gets slow. Better then to maintain a table with like counts that is maintained through a trigger or separately. On every like/dislike the counter is updated. This makes it easy to show the number of likes for each product. Then if the actual users that liked that product is wanted do a separate call (on user interaction) that fetches the specific likes for one product). Don't do this for all products on a page until actually requested.
I am assuming the size of both these tables is non-trivially large. You should create a new table (say LastThreeLikes), where the columns would be pid,uid_1,uid_2 and uid_3, indexed by pid. Also, add a column to your product table called numLikes.
For each "like" that you enter into your reference table, create a trigger that also populates this LastThreeLikes table if the numLikes is less than 3. You can choose to randomly update one of the values anyway if you want to show new users once in a while.
While displaying a product, simply fetch the uids from this table and display them back.
Note that you also need to maintain a trigger for the "Unlike" action (if there is any) to re-populate the LastThreeLikes table with a new user id.
Problem
The problem is the volume of data. From the point of view that you need two integer value as a answer you should forget about building a heavy query from your n<->n relations table.
Solution
Generates a storable representation using the file_put_contents() with append option each time a user likes a product. I don't have enough room to write the class in here.
public function export($file);
3D array format
array[product][line][user]
Example:
$likes[1293][1][456]=1;
$likes[82][2][656]=1;
$likes[65][3][456]=1;
.
.
.
Number of users who like this particular product:
$number_users_like_this_product = count($likes[$idProduct]);
All idUser who like this particular product:
$users_like_this_product = count($likes[$idProduct][$n]);
All likes
$all_likes = count($likes);
Deleting a like
This loop will unset the only line where $idProduct and $IdUser you want. Since all the variables are unsigned integer it is very fast.
for($n=1, $n <= count($likes[$idProduct]), $n++)
{
unset($likes[$idProduct][$n][$idUser]);
}
Conclusion
Get all likes will be easy as:
include('likes.php');
P.S If you want to give a try i will be glad to optimize my stuff and share it. I've created the class in 2012.

Query Between two date

SELECT product.pname,stock.pid,stock.qty,stock.rate
FROM product,stock
WHERE (date BETWEEN '2012-04-10' AND '2012-07-16') AND product.pid=stock.pid
This is my sql query but problem is when execute this query its show single result means my product table contains pid and pname and stock table contains pid,rate,qty and date.
I want to display record between two dates.
My query match with two records. But when i add "AND product.pid=stock.pid" its show only 1 record.
I want to display the product name from product table in the respect of pid of stock table.
Probably some product is not in the stock. Also I recommend you to do a JOIN like this:
SELECT product.pname,stock.pid,stock.qty,stock.rate
FROM product
LEFT JOIN
stock
ON product.pid=stock.pid
WHERE (date BETWEEN '2012-04-10' AND '2012-07-16')
I used LEFT JOIN that means to return products no matter if have or nor stock, I think that with this query you will get two Rows.
First try to get it working without the join:
SELECT stock.pid, stock.qty, stock.rate
FROM stock
WHERE stock.date BETWEEN '2012-04-10' AND '2012-07-16'
If that works, then use an outer to join to add information to each row without removing any rows.
SELECT product.pname, stock.pid, stock.qty, stock.rate
FROM stock
LEFT JOIN product ON product.pid=stock.pid
WHERE stock.date BETWEEN '2012-04-10' AND '2012-07-16'
The product name will be NULL if no product is found.
Try the below query.
SELECT product.pname,stock.pid,stock.qty,stock.rate
FROM product,stock
WHERE stock.date BETWEEN '2012-04-10' AND '2012-07-16' AND product.pid=stock.pid
Your selection criteria
WHERE ([stock.]date BETWEEN '2012-04-10' AND '2012-07-16')
makes it effectively RIGHT JOIN, only records that have/had stock within date range will be displayed. If you want to display ALL products irrespective of stock than you will need to add OR [stock.]date IS NULL to your selection criteria.

PHP MySql query 2 tables that have no common attributes at the same time?

I am trying to query 2 tables in a database, each query having nothing to do with each other, other then being on the same page.
Query 1 - The first query on the page will retrieve text and images that are found throughout the page from Table A.
Query 2 - The second query will retrieve several products with a image, description and title for each product from Table B.
I know that putting the second query inside the first query's while loop would work but of course is very inefficient.
How can I and what is the best way to retrieve all the data I need through 1 query?
Thanks,
Dane
So all you want to know is if its ok to have 2 queries on the same webpage? Its A-OK. Go right ahead. Its completelly normal. No one expects a join between table news and table products. Its normal to usetwo queries to fetch data from two unrelated tables.
Use LEFT or INNER JOIN (depends on whether you want to display records from TableA that have no correspondent records in TableB)
SELECT a.*, b.*
FROM TableA a
[LEFT or INNER] JOIN TableB b ON (b.a_id = a.id)
If there's no way to relate the two tables to each other, then you can't use a JOIN to grab records from both. You COULD use a UNION query, but that presumes that you can match up fields from each table, as a UNION requires you to select the same number/type of fields from each table.
SELECT 'pageinfo' AS sourcetable, page.id, page.images, page.this, page.that
WHERE page.id = $id
UNION
SELECT 'product' AS sourcetable, products.id, products.image, product.other, product.stuff
But this is highly ugly. You're still forcing the DB server to do two queries in the background plus the extra work of combining them into a single result set, and then you have to do extra work to dis-entangle in your code to boot.
It's MUCH easier, conceptually and maintenance-wise, to do two seperate queries instead.

Categories