I am very new to SQL, so I am having a couple problems figuring things out. The database is for an online store, and the structure is something like this:
users: UserID, UserName, etc.
pricelists: PricelistID, UserID, ProductItemID, Price
productitems: ProductItemID, ProductID, ItemID
products: ProductID, ManufacturerID, WebPageText, etc.
Each product can have one or more items (e.g. if the product page is selling T-shirts, there may be 5 different T-shirt variations for sale). That's pretty normal. What's not normal is that the pricing for each item is determined by what's in the user's pricelist, because each user is assigned particular pricing. Not all users have a price assigned for every item.
Here is the question: The requirement from management is that the user should be able to see every product, even if that product is not in the user's pricelist. If the user has no pricelist entry for a productitem, the page should display, "Contact us for pricing." I have not been able to figure out how to do this with one query. Every join that I attempted involving the pricelist table threw out products for which the user didn't have a price assigned. I am given only the ProductID and UserID. I have put my code below, which is a mix of mysql and PHP. It works, but it is clunky, especially seeing as I have to redo the second query over and over. Please tell me how I really ought to be doing it.
$query_product = sprintf("SELECT * , (items.Stock - (SELECT Coalesce(Sum(DetailQuantity),0)
FROM orderdetails
INNER JOIN orders ON OrderID = DetailOrderID
INNER JOIN productitems ON orderdetails.ProductItemID = productitems.ProductItemID
INNER JOIN products ON productitems.ProductID = products.ProductID
INNER JOIN items ON productitems.ItemID = items.ItemID
WHERE OrderDate > ProductUpdateDate))*ProductLive
AS NumLeft
FROM productitems
INNER JOIN products ON products.ProductID = productitems.ProductID
JOIN items ON productitems.ItemID = items.ItemID
WHERE products.ProductID = %s",GetSQLValueString($ProductID, "int"));
$product = mysql_query($query_products, $x) or die(mysql_error());
$row_product = mysql_fetch_assoc($product);
//after narrowing down to one productitem, either through user selections or while looping through $row_product:
$query_price = sprintf("SELECT Price FROM pricelists
WHERE UserID = %s AND ProductItemID = %s", GetSQLValueString($UserID, "int"), GetSQLValueString($row_product['ProductItemID'], 'int'));
$price = mysql_query($query_price, $x) or die(mysql_error());
$row_price = mysql_fetch_assoc($price);
$row_product['Price'] = $row_price['Price'];
Later in the code, I check whether $row_products['Price'] is empty or not to determine what is displayed on the page.
EDIT: I thought this would be obvious, but ... I have to limit pricelists by WHERE UserID = %s. That means I can't just tack on a LEFT JOIN pricelists ON pricelists.ProductItemID = productitems.ProductItemID, which was actually the first thing I tried, and which does not work. Limiting the outer join in the WHERE statement turns it into an inner join.
Add the pricelists table as an LEFT join to your main query:
SELECT field1, field2, ..., pl.*
FROM orderdetails
INNER JOIN orders ON OrderID = DetailOrderID
...
LEFT JOIN pricelists pl PN pl.ProductItemID = productitems.ProductItemID
..
In your code check if the price in the pricelists table is null, if so, show the text.
You should look into using an outer join for this. Heres a great description of the difference between an inner and outer join -> inner join vs outer join
Related
I have an ecommerce site where I have some products that can be in multiple categories.
I therefore have a products table, a categories table, and a product_categories table.
So what I do is query the product_categories table for the category ID of the category I want and get an array of product IDs like so:
$product_ids = [];
$params = [$category_id];
$sql = "SELECT * FROM product_categories WHERE category_id=?";
$stmt = DB::run($sql,$params);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$product_id = $row['product_id'];
array_push($product_ids,$product_id);
}
I then have a show_products function which takes the array and spits out all of the products. However, this function has a foreach statement like so:
foreach($ids as $id){
$params = [$id];
$sql = "SELECT * FROM products WHERE id=?";
$stmt = DB::run($sql,$params);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
//show products
}
}
This is fine and works but I want to be able to determine the order of which the products are displayed, so in my products table I have an 'order_by' column and would like to do something like SELECT * FROM products WHERE id=? ORDER BY order_by ASC.
Obviously the way I'm set up at the moment means that it is doing the query for 1 id at a time so it can't order them in this way.
I was thinking to get around this I would need to query myproducts like I've done above but instead of showing the products at this stage I would get the order_by column and create another array and sort them by this order nand then actually use that array to query again and show the products.
Is there a better way of doing this?
**Note: I would like to keep my show_products function seperate for use in other parts of my site as it is fairly complicated (gets 'as low as' price and stock levels etc). So I want to be able to just pass an array of IDs to this function.
All you need is to move some logic to the SQL query from PHP code.
I think that for doing this you should modify your show_products function too, look at JOIN Syntax and GROUP_CONCAT function syntax.
Here is an example of what you can do with a simple JOIN and a GROUP_CONCAT aggregate function (assuming to have products.name and categories.description columns):
SELECT P.ID, P.name, GROUP_CONCAT(C.description)
FROM products P
INNER JOIN product_categories PC ON P.category_id = PC.id
INNER JOIN categories C ON PC.category_id = C.id
GROUP BY P.ID, P.name
This is my final SQL query.
SELECT product_categories.product_id
FROM product_categories INNER JOIN products ON product_categories.product_id=products.id
WHERE product_categories.category_id=?
ORDER BY products.order_by ASC
I am usually very good at retrieving data from a DB, but i have come to a stand still.
I have a search bar, which directs the user to another page to show the products they have searched for on the first page. My website allows users to search for takeaways in their area. On the second page i have a drop down list, this is used to search through the different food categories for that area.
My problem is i have almost 30 food categories, not all areas have takeaways joints for each of the 30 categories, i am trying to only show the food categories the area has and not show the others. My query so far shows all 30 categories, no matter the number of categories the area actually has, and when i set the WHERE to any other than restaurant_id the categories no longer show.
I have tried Inner joining 2/3 tables to make this query work, but no joy.
Delivery_Pcode Tbl
1 Del_code_id
2 Restaurant_ID
3 Pcode
4 Del_Price
Rest_Category tbl
1 CategoryID
2 Cuisine_category
3 Category_img
Rest_Details Tel
1 Resturant_ID
2 Resturant_name
3 City_name
4 Cat_ID
Query
$get_cats = "SELECT * FROM Rest_Category
INNER JOIN Rest_Details
ON Rest_Category.CategoryID = Rest_Details.Cat_ID
INNER JOIN Delivery_Pcode
ON Delivery_Pcode.Restaurant_ID = Rest_Details.Resturant_ID
WHERE Delivery_Pcode.Pcode= $searchq";
I am trying to select all from the tables where the Pcode is equal to the users input code. The postcodes each restaurant delivers to are saved in Delivery_Pcode.Pcode and $searchq is defined at the top of the page as the inputted postcode $_post value.
I have tried:
WHERE Restaurant_name = $rest_name (also defined)
WHERE Restaurant_name = $rest_name and Restaurant_id = $rest_id( also defined)
I don't know if i am thinking to deep into this, and there is a very simple method (i have a tendency to do that), or i need to delete the query and start again... and advise or guidance is much appreciated.
I know the query works, as it populates it is just the query which is causing me problems.
You can try adding quotes
"SELECT * FROM Rest_Category
INNER JOIN Rest_Details
ON Rest_Category.CategoryID = Rest_Details.Cat_ID
INNER JOIN Delivery_Pcode
ON Delivery_Pcode.Restaurant_ID = Rest_Details.Resturant_ID
WHERE Delivery_Pcode.Pcode= '$searchq'";
or (before a proper input sanitize)
"SELECT * FROM Rest_Category
INNER JOIN Rest_Details
ON Rest_Category.CategoryID = Rest_Details.Cat_ID
INNER JOIN Delivery_Pcode
ON Delivery_Pcode.Restaurant_ID = Rest_Details.Resturant_ID
WHERE Delivery_Pcode.Pcode= '" . $searchq ."';";
I have 3 tables:
-Sales
-Items_cstm
-Items
Sales and Items_cstm contains the data I have to get with the query and Items the ability of the item "Deleted (1 or 0)" (and dome more info I don't need).
The Items_cstm's id is = Items's id.
I have to list the Sales of the Items which aren't deleted (0).
I've tried somehow with inner join but it didn't work and I don't really know what am I doing wrong:
SELECT Items_cstm.quantity
FROM Items_cstm, Sales
WHERE '".$_POST['name']."' = Sales.name
AND
INNER JOIN Items ON Items_cstm.id = Items.id
WHERE Items.deleted = 0;
You can join your tables like this (but you did not show the link between Items_cstm and sales - you have to modify that)
SELECT ic.quantity
FROM Items_cstm ic
INNER JOIN Sales s on s.id = ic.id
INNER JOIN Items i ON ic.id = i.id
WHERE i.deleted = 0
AND s.name = '".$escapedName."'
Also always escape your user input.
Try something like this:
SELECT Items_cstm.quantity
FROM Items_cstm
INNER JOIN Sales ON sales.JOINCOLUMN = Items_cstm.JOINCOLUMN
INNER JOIN Items ON Items_cstm.id = Items.id
WHERE Sales.name = '".$_POST['name']."'
WHERE Items.deleted = 0;
Replace JOINCOLUMN above with the columns that let you match records between these two tables
Something like this should work:
SELECT Items_cstm.quantity
FROM Items_cstm
INNER JOIN Items ON Items_cstm.id = Items.id
INNER JOIN Sales ON Items_cstm.id = Sales.item_id // ??? check this one for column names
WHERE '$name' = Sales.name AND Items.deleted = 0;
Beware of SQL injection.
My goal is to only show purchase orders that haven't received all their product yet.
I have a master table called test_po and two other tables test_po_bom, test_rog_bom. The test_po_bom and test_rog_bom tables are where I'm storing the list of products. test_po_bom is the list of products I've ordered, test_rog_bom is the list of products I've received.
Basically: loop purchase_orders WHERE products_received < products_ordered
Table Structure:
table `test_po`: `ID`, `vendor_ID`
table `test_po_bom`: `ID`, `po_ID`, `product_ID`, `quantity`
table `test_rog_bom`: `ID`, `po_ID`, `product_ID`, `quantity`
Code:
$SQL = "SELECT
*,
test_po.ID AS test_po_ID
FROM
test_po
LEFT JOIN test_po_bom ON test_po_bom.po_ID=test_po.ID
LEFT JOIN test_rog_bom ON test_rog_bom.po_ID=test_po.ID
WHERE
(SELECT SUM(quantity) FROM test_rog_bom WHERE po_ID=test_po.ID) < (SELECT SUM(quantity) FROM test_po_bom WHERE po_ID=test_po.ID)";
$result = mysql_query($SQL) or die(mysql_error());
while($row = mysql_fetch_array($result))
{
echo $row['test_po_ID'].'<br>';
}
It doesn't spit out anything, and I've tried many different variations but I just can't figure it out.
The problem appears to be with your query. Don't use * and instead specify the desired columns. The following solution uses aliases to help make your code more readable, especially with similar names. You will also notice HAVING instead of WHERE.
SELECT
p.ID as PO_ID
,p.VENDOR_ID
,pb.product_ID as PRODUCT_ID
,SUM(pb.quantity) as QUANTITY_ORDERED
,SUM(rb.quantity) as QUANTITY_RECEIVED
FROM test_po as p
LEFT JOIN test_po_bom as pb ON pb.po_ID = p.ID
LEFT JOIN test_rog_bom as rb ON rb.po_ID = p.ID
GROUP BY
p.ID
,p.VENDOR_ID
,pb.product_ID
HAVING SUM(rb.quantity) < SUM(pb.quantity)
THIS IS UNTESTED CODE. I apologize for posting untested code but I can't test it right now, and I think it demonstrates some things for you to try differently even if it's not exactly correct.
Try this:
select po.ID, po_bom.quant n_ordered, rog_bom.quant n_received
from test_po po
left join (select po_ID, sum(quantity) as quant from test_po_bom group by po_ID) po_bom
on po.ID = po_bom.po_ID
left join (select po_ID, sum(quantity) as quant from test_rog_bom group by po_ID) rog_bom
on po.ID = rog_bom.po_ID
where coalesce(rog_bom.quant, 0) < coalesce(po_bom.quant, 0);
This changes a few things from how you were doing them:
Uses table aliases to clearly specify which references refer to the same table row.
Uses group by to aggregate the sums by ID.
Uses coalesce to deal with situation where at least one of your tables (probably test_rog_bom) has no rows for an ID. I suspect this was actually the source of your problem in the first place.
I'm a little bit confused about these kind of new query types for me (DISTINCT,GROUP BY etc.)
I have three tables in my database.
BRANDS
PRODUCTS
RECIPES
What I would like to do is to list BRANDS then look for PRODUCTS of that brand and look if there is a RECIPE of those products, then I want it to be written.
I'm able to write it with the basic queries;
$sql = "SELECT * FROM BRANDS";
$sql2 = "SELECT * FROM PRODUCTS WHERE BRANDID = 1";
$sql3 = "SELECT * FROM RECIPES WHERE PRODUCTID = 2";
but it repeats the brands.As far as I googled I have to use GROUP BY or DISTINCT in my query.
How should I create my query layout and which of them I should use?
Thank you.
I don't think you necessarily need to do all the distinct and grouping, unless your database has duplicate rows. You can select all of that in one query like so:
Select Brands.BrandId, Brands.BrandName, Products.ProductId, Products.ProductName, Recipes.*
From Brands
Inner Join Products On Products.BrandId = Brands.BrandId
Inner Join Recipes On Recipes.ProductId = Products.ProductId
Where
Brands.BrandId = 1 And
Products.ProductId = 2
Order By Brands.BrandId, Products.ProductId, Recipes.RecipeId
Please note that I assumed you have a Brands.BrandName and Products.ProductName column. This query will get you a complete recordset, filtered based on Brand and Product.