I have a stored procedure where i want to grab some data through a connection table. The database is old and have no constraint.
This is my procedure:
select distinct(p.id), pd.language, p.Company, pd.shortDescription from dbo.Category c
join dbo.ProductCategory pc on c.id = pc.CategoryId
join dbo.Product p on pc.id = p.id
join dbo.ProductDescription pd on p.id = pd.id
where
c.Company = 'Normstahl' and
c.languageid = 'en' and
p.Company = 'Normstahl' and
pc.Company = 'Normstahl' and
c.id != 'Deckenlauf' and
pd.language = 'en' and
pd.Company = 'Normstahl'
and as you can see i want to select products that is not connected to the category 'Deckenlauf'.
the problem is that if a product is connected to multiple categories i will recive the product that is connected to that category anyway since it just skips the product that is connected to the category but finds the same product that is connected to another category.
id CategoryId Company
1 Deckenlauf Normstahl
1 RGD_EUR9_DL Normstahl
this is from the connectiontable between category and product. So in my stored procedure i don't want to recieve any products with the id = 1 but now i will because if takes the second row since it is not connected to the category 'Deckenlauf'. How can i solve this problem in my stored procedure?
I solved it like this:
select distinct(p.id), pd.language, p.Company, pd.shortDescription from dbo.Category c
join dbo.ProductCategory pc on c.id = pc.CategoryId
join dbo.Product p on pc.id = p.id
join dbo.ProductDescription pd on p.id = pd.id
where
c.Company = #companyName and
c.languageid = #languageId and
p.Company = #companyName and
pc.Company = #companyName and
c.id != #categoryId and
pd.language = #languageId and
pd.Company = #companyName and
p.id not in (select id from ProductCategory where CategoryId = #categoryId)
by adding the last select
I would try using a LEFT JOIN with a condition/filter and WHERE IS NULL to check whether the product has any links to the chosen category.
While your subselect solution most definately would work and maybe be more readable the it should give you better performance.
Assuming I've understood how you use your input parameters to the stored procedure it would be something like this:
LEFT JOIN dbo.ProductCategory pc on pc.id = p.id AND pc.CategoryId = #categoryId -- Only join for the chosen category
WHERE pc.CategoryId IS NULL and -- Only include rows which did not "hit" the left join
I've updated your solution and also moved what seemed to me to be join conditions, i.e. the pd.Company and pc.Company to the ON part.
In addition I've removed the join to Category as you don't seem to use it in the select.
Which gives the following query:
select distinct(p.id), pd.language, p.Company, pd.shortDescription
from dbo.Product p
left join dbo.ProductDescription pd on p.id = pd.id AND pd.Company = p.Company AND pd.languageid = #languageId
left join dbo.ProductCategory pc on pc.id = p.id AND pc.Company = p.Company AND pc.CategoryId = #categoryId
where pc.CategoryId IS NULL -- No matching category exists
and p.Company = #companyName
Note that the left join to ProductDescription is there to include rows in the result even for products that do not have a description for the supplied #languageid, as this is how the query in your solution would work.
This could/should be changed to an inner join if products should only be included in the result if they do have a description.
Related
I've just started learning SQL. Here is part of my Database:
I want to get the project name from the Project table with condition:
name = 'turbine' and value = '03' in Parameter table.
I have wrote the following query and it seems to work!
But I was wondering if any smarter query can do the job for me :
SELECT name
FROM Project
WHERE id IN (
SELECT projectId
FROM Cases
WHERE id IN (
SELECT caseId
FROM ParamValue
WHERE parameterId IN (SELECT id FROM Parameter WHERE name = 'turbine')
AND value = '03')
)
;
Instead of several nested IN clause with subquery seems more easy to read a proper set of inner join
select distinct Project.name
from Project
INNER JOIN Cases ON Cases.projectId = Project.id
INNER JOIN ParamValue ON ParamValue.caseId = Cases.id
AND ParamValue.value ='03'
INNER JOIN Parameter ON ParamValue.parameterId = Parameter.id
AND Parameter.name = 'turbine'
Sure here you go without subqueries:
SELECT pj.Name
FROM Parameter p
INNER JOIN ParamValue pv ON pv.Value = '03' AND p.Id = pv.parameterId
INNER JOIN Cases c ON pv.caseId = c.Id
INNER JOIN Project pj ON c.projectId = pj.Id
WHERE p.name = 'turbine'
;
select pr.name from Project pr
left join Cases c on pr.name = c.id
left join ParamValue pv on c.id = pv.parameterId
left join Parameter p on p.id = pv.parameterId
where p.name = 'turbine' and pv.value = '03';
I have been working on a multi-table query (something I haven't had much experience in) and at first I thought it was working perfectly fine until I noticed that half of the results had null values. I have put the query and table structures below so any help would be appreciated!
SELECT
i.name, i.material, i.price, a.str_mod, a.def_mod,
a.dex_mod, a.spd_mod, i.level_req
FROM `character` as c
LEFT JOIN item_owned as o ON c.uid = o.oid
LEFT JOIN items as i ON o.iid = i.id
LEFT JOIN armour as a ON i.id = a.aid
WHERE uid = :id AND o.equipped = 1 AND i.type = 'Armour'
Above is the query I have been running and below is the table structures
Found the solution thanks to Malfunct on discord... The query had a column typo so should have been
SELECT
i.name, i.material, i.price, a.str_mod, a.def_mod, a.dex_mod, a.spd_mod, i.level_req
FROM `character` as c
JOIN item_owned as o ON c.uid = o.oid
JOIN items as i ON o.iid = i.id
JOIN armour as a ON i.id = a.aid WHERE uid = 1
AND o.equipped = 1
AND i.type = 'Armour'
I want to fetch one product information using joins from different tables. I have three additional tables (review,thread,award) and I'd like to check whether records exist relating to this specific product. If they exist, return a non-null value, otherwise null. There is a possibility that more of this type of checking will be added to the query in the future.
Which query would you prefer performance wise to test if records exists?
Using exists with multiple subqueries:
$sql = "SELECT p.product_id,p.name,m.model,m.model_id,b.brand,me.merchant,
EXISTS(SELECT 1 FROM review WHERE product_id = :id) AS has_review,
EXISTS(SELECT 1 FROM thread WHERE product_id = :id) AS has_thread,
EXISTS(SELECT 1 FROM award WHERE product_id = :id) AS has_award
FROM product p
INNER JOIN model m ON m.model_id = p.model_id
INNER JOIN brand b ON b.brand_id = m.brand_id
INNER JOIN merchant me ON me.merchant_id = m.merchant_id
WHERE p.product_id = :id
LIMIT 1";
$dbh->prepare($sql);
Using multiple left joins:
$sql = "SELECT p.product_id,p.name,m.model,m.model_id,b.brand,me.merchant,
(t.product_id is not null) AS has_thread,
(r.product_id is not null) AS has_review,
(a.product_id is not null) AS has_award
FROM product p
INNER JOIN model m ON m.model_id = p.model_id
INNER JOIN brand b ON b.brand_id = m.brand_id
INNER JOIN merchant me ON me.merchant_id = m.merchant_id
LEFT JOIN review r ON re.product_id = p.product_id
LEFT JOIN thread t ON t.product_id = p.product_id
LEFT JOIN award a ON a.product_id = a.product_id
WHERE p.product_id = :id
LIMIT 1";
The first is much preferable.
For performance, for either version, you want indexes on review(product_id), thread(product_id), and award(product_id).
Why is using EXISTS better? When no matching rows exist in the three tables, then the two versions should be equivalent (minus the typo in the last on clause on the second query). However, when rows do exist, then the second version will create cartesian products of those rows, throwing off both the results and performance.
Note: I would be inclined to write the EXISTS clause using correlated subqueries, so the parameter is only referenced once:
EXISTS (SELECT 1 FROM review r WHERE r.product_id = p.product_id) AS has_review,
EXISTS (SELECT 1 FROM thread t WHERE t.product_id = p.product_id) AS has_thread,
EXISTS (SELECT 1 FROM award a WHERE a.product_id = p.product_id) AS has_award,
I have following query.
select
Product.*,
(
select
group_concat(features.feature_image order by product_features.feature_order)
from product_features
inner join features
on features.id = product_features.feature_id
where
product_features.product_id = Product.id
and product_features.feature_id in(1)
) feature_image
from products as Product
where
Product.main_product_id=1
and Product.product_category_id='1'
I want to bypass the row if feature_image is empty.
Your query looks a bit strange because you are doing most of the work in a subquery:
select p.*, (select group_concat(f.feature_image order by pf.feature_order)
from product_features pf inner join
features f
on f.id = pf.feature_id
where pf.product_id = p.id and pf.feature_id in (1)
) as feature_image
from products p
where p.main_product_id=1 and p.product_category_id='1';
A more common way to phrase the query is as an inner join in the outer query:
select p.*, group_concat(f.feature_image order by pf.feature_order) as feature_image
from products p join
product_features pf
on pf.product_id = p.id and pf.feature_id in (1) join
features f
on f.id = pf.feature_id
where p.main_product_id=1 and p.product_category_id='1'
group by p.id;
This will automatically include only products that have matching features. You would use left outer join to get all products.
Just when I thought I had figured out MySQL JoINS I run into this and it's doing my head in..
I've three tables for a shop:
tblProducts tblProdCat tblCategory
prodID prodCatID categoryID
prodName categoryID categoryHidden
prodPrice prodID categoryName
And what i'd like to do is create a query that finds products in a category that aren't hidden and I cannot get this to work.
At present all I can do is find the products in a category by joining the products table to the ProdCat table. As this query works I adapted it to this:
SELECT
p.prodID, p.name,
FROM
tblProducts p
INNER JOIN
tblProdCat pc
ON
pc.prodID = p.prodID
inner JOIN
tblCategory c
ON
c.categoryID = pc.categoryID
WHERE
pc.categoryID = '7' AND
c.categoryHidden = '0'
Can anybody help me identify why this isn't working?
edit: fixed the name of prodID (it was a typo and not what is causing the issue)
You do not have a column called productID on tblProducts
try
SELECT p.prodID, p.name,
FROM tblProducts p
INNER JOIN tblProdCat pc
ON pc.productID = p.prodID
inner JOIN tblCategory c
ON c.categoryID = pc.categoryID
WHERE pc.categoryID = '7' AND c.categoryHidden = '0'
Also I can not tell from your schema but if pc.categoryID is a number then you do not need quotes.
select * from tblProducts as p Inner join tblProdCat as pc Inner join tblCategory as c
where pc.categoryID = '7' AND c.categoryHidden = '0'
try this way
Try something like this (just dummy code )
select p.productID, p.name from
tblProducts as p,tblCategory as c,tblProdCat as pc
where pc.categoryID = '7' AND c.categoryHidden = '0'