Passing Data Through A Three Tiered Scalar Subquery - sql

I have a query, that has three tiers. That is to say, I have a main query, which has a scalar subquery, which also contains a scalar subquery.
The bottom level scalar query is returning two different values two which the mid-level subquery is returning an average of. However, instead of the bottom level query receiving the current value, it is averaging ALL of the values in the table.
Does anyone know how to properly pass the value of the current top level query result to the bottom subquery?
Code Example:
Select Product,
Description,
(Select Avg(Mfg_Cost, Purchasing_Cost)
FROM (Select Mfg_Cost,
Purchasing Cost
From Cost
Where Cost.Product = Products.Product))
From Products;

Can't you just use a JOIN and GROUP BY, like so:
Select p.Product,
p.Description,
Avg(c.Mfg_Cost),
Avg(c.Purchasing_Cost)
From Products p
INNER JOIN
Cost c
ON c.Product = p.Product
GROUP BY p.Product, p.Description;
In general, if you need to return more than one value from a subquery:
Select p.Product,
p.Description,
q2.AvgMfg_Cost,
q2.AvgPurchasing_Cost
From Products p INNER JOIN
(
Select c.Product,
Avg(c.Mfg_Cost) AS AvgMfg_Cost,
Avg(c.Purchasing_Cost) AS AvgPurchasing_Cost
From Cost c
Group by c.Product
) AS q2
on q2.Product = p.Product;
In Microsoft SQL Server, you can also use a Common Table Expression (CTE)

Related

SQL Server select on aggregate function

I have a challenge regarding the use of an aggregate function in a where statement.
SELECT [tb_users].[id_user]
,[name]
,[Title]
,[FirstName]
,[LastName]
,[type]
,sum ([tb_LockAuditTrail].[price]) as SumPrice
FROM [SALTO_SPACE].[dbo].[tb_Users]
join [tb_LockAuditTrail]
on [tb_Users].[Cardcode] = [tb_LockAuditTrail].[Cardcode]
where [type] = 1
group by [tb_users].[id_user]
,[name]
,[Title]
,[FirstName]
,[LastName]
,[type]
order by [SumPrice] desc
This is running fine. From this dataset I want to select only the records above a certain SumPrice level. How do I do this?
I cannot use the alias SumPrice in the where statement because aliases are not allowed in where statements.
I also cannot use the aggregate SUM function in a where statement. So for now I don't see a solution other than filtering the result afterwards in an Excel sheet.
After the group by block and before the orderby block you write
HAVING sum ([tb_LockAuditTrail].[price]) > 100
Change the 100 accordingly
Even if HAVING weren't a thing, you don't have to use excel. Every SELECT creates a block of data that can be used like a table so you could wrap it all in brackets, give it an alias, and select from the query as if it were a table:
SELECT *
FROM
(
SELECT productcategory, sum(price) as sumprice
FROM products
GROUP BY productcategory
) summed
WHERE
summed.sumprice > 100
Get down with the notion that tables are only one kind of "block of data" that queries can use; you'll considerably increase your SQL querying ability if you keep in mind the fact FROM can use blocks of data from other sources such as queries and table valued functions. Here's another example:
SELECT *
FROM
products p
INNER JOIN
(
SELECT productcategory, sum(price) as sumprice
FROM products
GROUP BY productcategory
) sumcat
ON p.productcategory = sumcat.productcategory
INNER JOIN
(
SELECT warehouse, count(*) as sumprice
FROM products
GROUP BY warehouse
) countwh
ON p.warehouse = countwh.warehouse
WHERE
...
You can also use the WITH command, to alias a query so you can then use it like a table:
WITH
sumcat AS (
SELECT productcategory, sum(price) as sumprice
FROM products
GROUP BY productcategory
),
countwh as (
SELECT warehouse, count(*) as sumprice
FROM products
GROUP BY warehouse
)
--now use them like tables
SELECT *
FROM
products p
INNER JOIN sumcat s ON p.productcategory = s.productcategory
INNER JOIN countwh c ON p.warehouse = c.warehouse
This is one way how we can get every product detail but also the sum of all the prices in the category and the count of all the products in the various warehouses - perhaps pointless data but I'm demonstrating how you can use subqueries to generate blocks of data that can be joined to tables, and also each detail line can have a summary aspect too. Ordinarily you have to lose detail to generate summaries so the learning notions here are:
subqueries are blocks of data just like tables and can be joined just like tables
give everything an alias and use it
to get detail and summary together generate the summary and join it to the detail
a table can appear multiple times in a query and can be joined to itself even, so long as the two occurrences of the table have different aliases

Get row from one table, plus COUNT from a related table

I'm trying to build an SQL query where I grab one table's information (WHERE shops.shop_domain = X) along with the COUNT of the customers table WHERE customers.product_id = 4242451.
The shops table DOES NOT have product.id in it, but the customers table DOES HAVE the shop_domain in it, hence my attempt to do some sort of join.
I essentially want to return the following:
shops.id
shops.name
shops.shop_domain
COUNT OF CUSTOMERS WHERE customers.product_id = '4242451'
Here is my not so lovely attempt at the query.
I think I have the idea right (maybe...) but I can't wrap my head around building this query.
SELECT shops.id, shops.name, shops.shop_domain, COUNT(customers.customer_id)
FROM shops
LEFT JOIN customers ON shops.shop_domain = customers.shop_domain
WHERE shops.shop_domain = 'myshop.com' AND
customers.product_id = '4242451'
GROUP BY shops.shop_id
Relevant database schemas:
shops:
id, name, shop_domain
customers:
id, name, product_id, shop_domain
You are close. The condition on customers needs to go in the ON clause, because this is a LEFT JOIN and customers is the second table:
SELECT s.id, s.name, s.shop_domain, COUNT(c.customer_id)
FROM shops s LEFT JOIN
customers c
ON s.shop_domain = c.shop_domain AND c.product_id = '4242451'
WHERE s.shop_domain = 'myshop.com'
GROUP BY s.id, s.name, s.shop_domain;
I am also inclined to include all three columns in the GROUP BY, although Postgres (and ANSI/ISO standards) are happy with just id if it is declared as the primary key in the table.
A correlated subquery should be substantially cheaper (and simpler) for the purpose:
SELECT id, name, shop_domain
, (SELECT count(*)
FROM customers
WHERE shop_domain = s.shop_domain
AND product_id = 4242451) AS special_count
FROM shops s
WHERE shop_domain = 'myshop.com';
This way you only need to aggregate in the subquery, and need not worry about undesired effects on the outer query.
Assuming product_id is a numeric data type, so I use a numeric literal (4242451) instead of a string literal '4242451' - which might cause problems otherwise.

NTILE Function and Using Inner Join in Oracle

I am supposed to use the given Database(Its pretty huge so I used codeshare) to list last names and customer numbers of top 5% of customers for each branch. To find the top 5% of customers, I decided to use the NTILE Function, (100/5 = 20, hence NTILE 20). The columns are pulled from two separate tables so I used Inner joins. For the life of me, I honesly cannot figure out where I am going wrong. I keep getting "missing expression" errors but Do not know what exactly I am missing. Here is the Database
Database: https://codeshare.io/5XKKBj
ERD: https://drive.google.com/file/d/0Bzum6VJXi9lUX1d2ZkhudTE3QXc/view?usp=sharing
Here is my SQL Query so far.
SELECT
Ntile(20) over
(partition by Employee.Branch_no
order by sum(ORDERS.SUBTOTAL) desc
) As Top_5,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME
FROM
CUSTOMER
INNER JOIN ORDERS
ON
CUSTOMER.CUSTOMER_NO = ORDERS.CUSTOMER_NO
GROUP BY
ORDERS.SUBTOTAL,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME;
You need to join Employee and the GROUP BY must include all non-aggregated expressions. You can use a subquery to generate the subtotals and get the NTILE in the outer query, e.g.:
SELECT
Ntile(20) over
(partition by BRANCH_NO
order by sum_subtotal desc
) As Top_5,
CUSTOMER_NO,
LNAME
FROM (
SELECT
EMPLOYEE.BRANCH_NO,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME,
sum(ORDERS.SUBTOTAL) as sum_subtotal
FROM CUSTOMER
JOIN ORDERS
ON CUSTOMER.CUSTOMER_NO = ORDERS.CUSTOMER_NO
JOIN EMPLOYEE
ON ORDERS.EMPLOYEE_NO = EMPLOYEE.EMPLOYEE_NO
GROUP BY
EMPLOYEE.BRANCH_NO,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME
);
Note: you might want to include BRANCH_NO in the select list as well, otherwise the output will look confusing with duplicate customers (if a customer has ordered from employees in multiple branches).
Now, if you want to filter the above query to just get the top 5%, you can put the whole thing in another subquery and add a predicate on the Top_5 column, e.g.:
SELECT CUSTOMER_NO, LNAME
FROM (... the query above...)
WHERE Top_5 = 1;

GROUP BY not working in left join query

I m trying to use group by clause in left join sql query and it is not working.
Please help me out, thanks in advance.
SELECT Cust_Mst_Det.Cust_Hd_Code,
Cust_Mst_Det.First_Name,
SL_HEAD20152016.vouch_date AS invoice_2,
SL_HEAD20142015.vouch_date AS invoice_1,
Cust_Mst_Hd.EMail
FROM Cust_Mst_Det
LEFT JOIN SL_HEAD20142015 ON Cust_Mst_Det.Cust_Hd_Code=SL_HEAD20142015.Member_Code
LEFT JOIN SL_HEAD20152016 ON Cust_Mst_Det.Cust_Hd_Code=SL_HEAD20152016.Member_Code
LEFT JOIN Cust_Mst_Hd ON Cust_Mst_Det.Cust_Hd_Code=Cust_Mst_Hd.Cust_Hd_Code
WHERE cust_mst_det.first_name!='NIL'
GROUP BY Cust_Mst_Det.Cust_Hd_Code
ORDER BY SL_HEAD20152016.vouch_date DESC,
SL_HEAD20142015.vouch_date
I'm not sure which DBMS you are using, but on an Oracle your query will not work at all.
First issue: The GROUP BY statement is used in conjunction with the aggregate functions to group the result-set by one or more columns. You do not have any aggregating function in your SELECT statement (count, max, etc.)
Second issue: you must specify all columns from SELECT statement in your GROUP BY statement (excluding columns that represents results of aggregation).
As I said I don't know which DB is used by you, but those two points should be applicable for the most of SQL standards.
It appears that it is impossible to use an ORDER BY on a GROUP BY summarisation. My fundamental logic is flawed. I will need to run the following subquery.
ex :
SELECT p.*, pp.price
FROM products p
LEFT JOIN ( SELECT price FROM product_price ORDER BY date_updated DESC ) pp
ON p.product_id = pp.product_id GROUP BY p.product_id;
This will take a performance hit but as it is the same subquery for each row it shouldn't be too bad.

Select SUM from multiple tables

I keep getting the wrong sum value when I join 3 tables.
Here is a pic of the ERD of the table:
(Original here: http://dl.dropbox.com/u/18794525/AUG%207%20DUMP%20STAN.png )
Here is the query:
select SUM(gpCutBody.actualQty) as cutQty , SUM(gpSewBody.quantity) as sewQty
from jobOrder
inner join gpCutHead on gpCutHead.joNum = jobOrder.joNum
inner join gpSewHead on gpSewHead.joNum = jobOrder.joNum
inner join gpCutBody on gpCutBody.gpCutID = gpCutHead.gpCutID
inner join gpSewBody on gpSewBody.gpSewID = gpSewHead.gpSewID
If you are only interested in the quantities of cuts and sews for all orders, the simplest way to do it would be like this:
select (select SUM(gpCutBody.actualQty) from gpCutBody) as cutQty,
(select SUM(gpSewBody.quantity) from gpSewBody) as sewQty
(This assumes that cuts and sews will always have associated job orders.)
If you want to see a breakdown of cuts and sews by job order, something like this might be preferable:
select joNum, SUM(actualQty) as cutQty, SUM(quantity) as sewQty
from (select joNum, actualQty, 0 as quantity
from gpCutBody
union all
select joNum, 0 as actualQty, quantity
from gpSewBody) sc
group by joNum
Mark's approach is a good one. I want to suggest the alternative of doing the group by's before the union, simply because this can be a more general approach for summing along multiple dimensions.
Your problem is that you have two dimensions that you want to sum along, and you are getting a cross product of the values in the join.
select joNum, act.quantity as ActualQty, q.quantity as Quantity
from (select joNum, sum(actualQty) as quantity
from gpCutBody
group by joNum
) act full outer join
(select joNum, sum(quantity) as quantity
from gpSewBody
group by joNum
) q
on act.joNum = q.joNum
(I have kept Mark's assumption that doing this by joNum is the desired output.)