Translate an SQL subquery to a more efficient version - sql

I'm working with a moderately large MSAccess .mdb file that I need to manipulate with SQL. Unfortunately some statements which work in theory seem to cause it to hang, and I've run into a brick wall.
Here is a simplified representation in SQL Fiddle
Three tables: products, product_category, and categories
I need to SELECT categories that ONLY contain items that have the field 'HIDE = 1'
If a category contains products that are hide = 0, it should not be selected.
I can do this relatively easily with subqueries, but the query stalls out. In the past queries that rely on left joins seem to execute efficiently, but I cannot wrap my mind around joins enough to translate this query into that format.
EDIT:
SELECT c.categoryid
FROM product_category AS c
LEFT JOIN
(
SELECT DISTINCT c.categoryid
FROM product_category AS c
LEFT JOIN products AS p
ON c.catalogid = p.catalogid
WHERE p.hide = 0
) y ON y.categoryid = c.categoryid
WHERE y.categoryid IS NULL
Someone posted the above query as an answer but then for some reason deleted it. As far as I can tell it works and works quickly. I consider this question to be answered. If I remember I will self-post the answer once the timer allows me to.

I believe you just need to un-correlate the subquery eg...
SELECT c.categoryid FROM product_category AS c
WHERE c.categoryid NOT IN
(SELECT DISTINCT c1.categoryid FROM product_category AS c1
LEFT JOIN products AS p ON c1.catalogid = p.catalogid
WHERE p.hide = 0)
Note how I have aliased the subquery product_category table as c1 instead of c - This means the subquery will only execute once as opposed to once for every row of the your main query.
SQL Fiddle
Note that there will no doubt be more efficiencies still to be found however I think this will suffice for your purposes.
In fact there is no need for a LEFT JOIN here I don't think ie...
SELECT c.categoryid FROM product_category AS c
WHERE c.categoryid NOT IN
(SELECT DISTINCT c1.categoryid FROM product_category AS c1
INNER JOIN products AS p ON c1.catalogid = p.catalogid
WHERE p.hide = 0)
..This will afford you some extra speed.

If there is only one categoryid per catalogid then you can get rid of the distinct:
Select
c.id, c.categoryname
From
category c
Where
Not Exists (
Select
'x'
From
products p
Inner Join
product_category pc
on pc.catalogid = p.catalogid
Where
pc.categoryid = c.id and
p.hide = 0
)
Edited - the test data in the fiddle seems wrong, I've corrected it. This should work now
http://sqlfiddle.com/#!6/56f5e/1/0

Related

How to build a complex sql query?

Database design here
I want to get records from the product_spec_data table that are associated with products whose category_id is 5.
Please help me make a query to the database...
All of the relationship are clearly laid out, a simple join across tables would works.
SELECT psd.*
FROM product_spec_data psd
INNER JOIN product_spec_values psv ON psd.id = psv.product_spec_data_id
INNER JOIN products prod ON psv.product_id = prod.id
INNER JOIN categories cat ON prod.category_id = cat.id
-- with category id = 5
WHERE cat.id = 5;
I am not sure what do you mean by complex query. I guess your question is not complete. I think the query for your question (if it is T-SQL) will be as follows
select d.*
from product_spec_data d
left outer join product_spec_values v on d.id=v.product_spec_data_id
left outer join products p on v.product_id=p.id
where p.category_id=5
(I think in MySQL also above syntax will remain same, you may remove [outer] clause in MySQL)

How to combine these two SELECT statment into one

I have two SELECT query which are written in MySQL
$sql = 'SELECT Name FROM category WHERE CategoryID = '.$id;
AND
$sql = "SELECT * FROM sub_category WHERE ParentID = $id ORDER BY OrderIndex";
So far I have to translate these two query to Oracle PL/SQL‚I came in idea to combine these two statment into one and so far I write query but I am a little bit confusing since I am begginer in PL/SQL
SELECT
c.Name,
sc.Name as SubCategoryName,
sc.ID as SubCategoryID,
sc.Picture as Picture,
sc.OrderIndex
FROM Category c
INNER JOIN sub_category sc ON ParentID =
WHERE CategoryID = 23;
Here is graphical representation of tables
CategoryTable
SubcategoryTable
Your idea is correct, joining tables is one of the standard ways to "combine two select statements together".
You are going to get different results depending on the kind of join you use.
See for instance this question for an explanation of the different kind of joins.
Your query is almost correct (assuming you want an inner join), you are missing the right hand side on the join condition:
SELECT
c.Name,
sc.Name as SubCategoryName,
sc.ID as SubCategoryID,
sc.Picture as Picture,
sc.OrderIndex
FROM Category c
INNER JOIN sub_category sc ON sc.ParentID = c.CategoryID
WHERE CategoryID = 23;
Your select is close to be working, you forgot to connect joined tables on their column which will result in error and there is no need to use inner as inner join = join. More useful information on joins can be found here https://www.techonthenet.com/oracle/joins.php
This should work for you:
SELECT c.name,
sc.name subcategoryname,
sc.id subcategoryid,
sc.picture picture,
sc.orderindex
FROM category c
JOIN sub_category sc
ON sc.parentid = c.categoryid
WHERE c.categoryid = 23;

SQLite GROUP_CONCAT from another table, multiple joins

Having trouble with my sql query. Not an SQL expert by any means.
SELECT
transactions.*,
categories.*,
GROUP_CONCAT(tags.tagName) as concatTags
FROM transactions
INNER JOIN categories
ON transactions.category = categories.categoryId
LEFT JOIN TransactionTagRelation AS ttr
ON transactions.transactionId = ttr.transactionId
LEFT JOIN tags
ON tags.tagId = ttr.tagId;
(There's also a where and group by, but didn't think it was relevant to the question).
I'm trying to get:
transactionId1, ...otherStuff..., "tagId1,tagId2,tagId3"
transactionId2, ...otherStuff..., "tagId1,tagId3"
What I have now seems to merge the tags into one transaction or something. I tried adding a GROUP BY transactionID at the end, but it gives a syntax error for some reason. I have a feeling my joins are incorrect, but I wasn't able to get anything better.
Do something like this:
SELECT t.*, c.*,
(SELECT GROUP_CONCAT(tg.tagName)
FROM TransactionTagRelation ttr JOIN
Tags tg
ON tg.tagId = ttr.tagId
WHERE t.transactionId = ttr.transactionId
) as concatTags
FROM transactions t JOIN
categories c
ON t.category = c.categoryId;
This eliminates the GROUP BY in the outer query and allows you to use t.* and c.* in the SELECT.

SQL ACCESS (need ideas and help about a query)

I am new at access SQL and i need help with some query. What i want is to find those customers that prefer a car (manufacturer and model) from prefer_to_buy AND prefer_to_rent that no one else prefer.
For example if 2 customers prefer toyota aygo must not be in the result table.
customer(customer_id,name)
prefer_to_buy(customer_id,manufacturer,model)
prefer_to_rent(customer_id, manufacturer,model)
I have tried a lot of ways including exists and i know there must be about 2-3 subqueries but i cant get it to work, any ideas?
Your problem definition is very vague, so the answer is also sort of generic. You should try to create a Left Outer Join on customer table and, for example, a "prefer_to_buy" Table using customer_id as a join field, and include:
customer_id,name from the left table and manufacturer,model from the right table. The same logic applies to prefer_to_rent Table: you can actually combine these 3 Tables in a single Access SQL query using the aforementioned Outer Joins.
Hope this may help. Best regards,enter code here
There are a couple of parts to cover here. First, you could use the union operator to treat prefer_to_buy and prefer_to_rent as a single table (possibly with an additional literal "column" to indicate preference type). Once you've done this, you could use the exists operator to make sure no other customers prefer this car:
SELECT c.name, p.manufacturer, p.model
FROM customer c
JOIN (SELECT customer_id, manufacturer, model
FROM prefer_to_buy
UNION
SELECT customer_id, manufacturer, model
FROM prefer_to_buy) p ON c.customer_id = p.customer_id
WHERE NOT EXISTS (SELECT *
FROM prefer_to_buy pb
WHERE c.customer_id != pb.customer_id AND
p.manufacturer = pb.manufacturer AND
p.model = pb.model) AND
NOT EXISTS (SELECT *
FROM prefer_to_rent pr
WHERE c.customer_id != pr.customer_id AND
p.manufacturer = pr.manufacturer AND
p.model = pr.model)
First you need to do to two joins one on prefer_to_buy AND one on prefer_to_rent.
Next, you need to check if any one else would like the same manufacturer and model. Do this with a exist
SELECT *
FROM customer AS c
JOIN prefer_to_buy AS pb ON c.customer_id = pb.customer_id
JOIN prefer_rent AS pr ON c.customer_id = pr.customer_id
WHERE NOT EXISTS ( SELECT 'x' FROM prefer_to_buy AS pb1 WHERE pb.manufacturer = pb1.manufacturer AND pb.model = pb1.model AND pb.customer_id <> pb1.customer_id)
AND NOT EXISTS ( SELECT 'x' FROM prefer_to_rent AS pr1 WHERE pb.manufacturer = pr1.manufacturer AND pb.model = pr1.model AND pb.customer_id <> pr1.customer_id)

Translating subquery to left join in sqlite

I have a query that is running against a SQLite database that uses a couple of subqueries. In order to accommodate some new requirements, I need to translate it to use joins instead. Below is the structure version of the original query:
SELECT c.id AS category_id, b.budget_year,
(
SELECT sum(actual)
FROM lines l1
WHERE status = 'complete'
AND category_id = c.id
AND billing_year = b.budget_year
) AS actual
(
SELECT sum(planned)
FROM lines l2
WHERE status IN ('forecasted', 'in-progress')
AND category_id = c.id
AND billing_year = b.budget_year
) AS rough_proposed
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
GROUP BY c.id, b.budget_year;
The next query is my first attempt to convert it to use LEFT OUTER JOINs:
SELECT c.id AS category_id, b.budget_year, sum(l1.actual) AS actual, sum(l2.planned) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l1 ON (l1.category_id = c.id
AND l1.billing_year = b.budget_year
AND l1.status = 'complete')
LEFT OUTER JOIN lines AS l2 ON (l2.category_id = c.id
AND l2.billing_year = b.budget_year
AND l2.status IN ('forecasted', 'in-progress'))
GROUP BY c.id, b.budget_year;
However, the actual and rough_proposed columns are much larger than expected. I am no SQL expert, and I am having a hard time understanding what is going on here. Is there a straightforward way to convert the subqueries to joins?
There is a problem with both your queries. However, the first query hides the problem, while the second query makes it visible.
Here is what's going on: you join lines twice - once as l1 and once more as l2. The query before grouping would have the same line multiple times when there are both actual lines and forecast-ed / in-progress lines. When this happens, each line would be counted multiple times, resulting in inflated values.
The first query hides this, because it does not apply aggregation to actual and rough_proposed columns. SQLite picks the first entry for each group, which has the correct value.
You can fix your query by joining to lines only once, and counting the amounts conditionally, like this:
SELECT
c.id AS category_id
, b.budget_year
, SUM(CASE WHEN l.status = 'complete' THEN l.actual END) AS actual
, SUM(CASE WHEN l.status IN ('forecasted', 'in-progress') THEN l.planned END) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l ON (l.category_id = c.id AND l1.billing_year = b.budget_year)
GROUP BY c.id, b.budget_year
In this new query each row from lines is brought in only once; the decision to count it in one of the actual/planned columns is made inside the conditional expression embedded in the SUM aggregating function.