Inner joining to a select statement where the inner select statement's where clause references the outer select? - sql

This is a slimmed down query of my greater problem, but the gist is that I'm trying to inner join to a select where the select is limited by the outer select. Is that possible? I'm getting an error about multipart identifier S.Item and S.SerialNum on the inner select.
The gist is this, we have to group by the item/serial, and the query is big enough, we don't want to go back and group everything in the entire query for this minor join.
SELECT S.Item, S.SerialNum, S.ReceiveDate
FROM SALES S
INNER JOIN (SELECT W.Item, W.SerialNum, MIN(W.SalesDate)
FROM WARRANTY W
WHERE W.Item = S.Item AND
W.SerialNum = S.SerialNum
GROUP BY Item, SerialNum, SalesDate) WW
ON S.Item = WW.Item AND WW.SerialNum

Looks like you have your JOIN reference in the wrong place.
SELECT S.Item, S.SerialNum, S.ReceiveDate
FROM SALES S
INNER JOIN
(
SELECT W.Item, W.SerialNum, MIN(W.SalesDate) MinSalesDate
FROM WARRANTY W
GROUP BY Item, SerialNum
) WW
ON S.Item = WW.Item
AND S.SerialNum = WW.SerialNum
Edit, based on your comment about filtering, you can place a WHERE clause on your inner SELECT:
SELECT S.Item, S.SerialNum, S.ReceiveDate, WW.MinSalesDate
FROM SALES S
INNER JOIN
(
SELECT W.Item, W.SerialNum, MIN(W.SalesDate) MinSalesDate
FROM WARRANTY W
WHERE yourFilter here
GROUP BY Item, SerialNum
) WW
ON S.Item = WW.Item
AND S.SerialNum = WW.SerialNum

You can try a common_table_expression instead of a JOIN also. Check the WITH clause here.
It could be something like this:
WITH Warranty_CTE (Item, SerialNum, MinSalesDate)
AS
(
SELECT W.Item, W.SerialNum, MIN(W.SalesDate) MinSalesDate
FROM WARRANTY W
GROUP BY Item, SerialNum
)
SELECT S.Item, S.SerialNum, S.ReceiveDate
FROM SALES S
INNER JOIN Warranty_CTE WC
ON S.Item = WC.Item
AND S.SerialNum = WC.SerialNum

Related

Returning multiple aggregated columns from Subquery

I am trying to extend an existing query by aggregating some rows from another table.
It works when I only return one column like this:
Select DISTINCT
Contracts.id,
Contracts.beginTime,
Contracts.endTime,
Suppliers.name
(SELECT COUNT(p.id) from production as p where p.id_contract = Contracts.id)
FROM Contracts
LEFT JOIN Suppliers on Contracts.id = Suppliers.id_contract
Then I tried to add another column for the aggregated volume:
Select DISTINCT
Contracts.id,
Contracts.beginTime,
Contracts.endTime,
Suppliers.name
(SELECT COUNT(p.id), SUM(p.volume) from production as p where p.id_contract = Contracts.id)
FROM Contracts
LEFT JOIN Suppliers on Contracts.id = Suppliers.id_contract
However, this returns the following error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
I experimented a bit with the EXISTS keyword, but couldn't figure out how to make it work. Also I'm not sure whether this is the way to go in my case.
The desired output would be like so:
contract1Id, supplierInfoContract1, nrItemsContract1, sumVolumeContract1
contract2Id, supplierInfoContract2, nrItemsContract2, sumVolumeContract2
Instead of using DISTINCT and subqueries, use GROUP BY and normal joins to get the aggregates. And always use aliases, it will make your life easier:
SELECT
c.id,
c.beginTime,
c.endTime,
s.name,
COUNT(p.id) prod_count,
SUM(p.volume) prod_vol
FROM Contracts c
LEFT JOIN production p on p.id_contract = c.id
LEFT JOIN Suppliers s on c.id = s.id_contract
GROUP BY c.id, c.beginTime, c.endTime, s.name;
Another option is to APPLY the grouped up subquery:
SELECT DISTINCT
c.id,
c.beginTime,
c.endTime,
s.name,
p.prod_count,
p.prod_vol
FROM Contracts c
LEFT JOIN Suppliers s on c.id = s.id_contract
OUTER APPLY (
SELECT
COUNT(p.id) prod_count,
SUM(p.volume) prod_vol
FROM production p WHERE p.id_contract = c.id
GROUP BY ()
) p;
You can also use CROSS APPLY and leave out the GROUP BY (), this uses a scalar aggregate and returns 0 instead of null for no rows.
One last point: DISTINCT in a joined query is a bit of a code smell, it usually indicates the query writer wasn't thinking too hard about what the joined tables returned, and just wanted to get rid of duplicate rows.
You should use it like below:
Select DISTINCT Contracts.id, Contracts.beginTime, Contracts.endTime, Suppliers.name
(SELECT COUNT(p.id) from production as p where p.id_contract = Contracts.id) as CNT,
(SELECT SUM(p.volume) from production as p where p.id_contract = Contracts.id) as VOLUME
FROM Contracts
LEFT JOIN Suppliers on Contracts.id = Suppliers.id_contract
I guess you can try to rework your query as
SELECT X.ID,X.beginTime,X.endTime,X.name,CR.CNTT,CR.TOTAL_VOLUME
FROM
(
Select DISTINCT Contracts.id, Contracts.beginTime, Contracts.endTime, Suppliers.name
FROM Contracts
LEFT JOIN Suppliers on Contracts.id = Suppliers.id_contract
)X
CROSS APPLY
(
SELECT COUNT(p.id)AS CNTT,SUM(p.volume) AS TOTAL_VOLUME
from production as p where p.id_contract = X.id
)CR
I reworked you query slightly by separating the subqueries.
Select DISTINCT
Contracts.id,
Contracts.beginTime,
Contracts.endTime,
Suppliers.name,
(SELECT COUNT(p.id) from production as p where p.id_contract = Contracts.id) count_id,
(SELECT SUM(p.volume) from production as p where p.id_contract = Contracts.id) sum_volume
FROM Contracts
LEFT JOIN Suppliers on Contracts.id = Suppliers.id_contract

Is there a way to distinct multiple columns in sql?

Is there a way to distinct multiple columns? When I tried to do it with p.name it says that there is an error that occurred.
SELECT DISTINCT( V.NAME ),
POH.status,
poh.shipdate,
pod.orderqty,
POD.receivedqty,
POD.rejectedqty,
p.NAME
FROM purchasing.vendor v
INNER JOIN purchasing.productvendor pv
ON v.businessentityid = pv.businessentityid
INNER JOIN production.product p
ON pv.productid = P.productid
INNER JOIN purchasing.purchaseorderdetail POD
ON P.productid = POD.productid
INNER JOIN purchasing.purchaseorderheader POH
ON POD.purchaseorderid = POH.purchaseorderid
ORDER BY v.NAME,
p.NAME;
If you want one row per NAME, then you can use ROW_NUMBER():
with q as (
<your query here with columns renamed so there are no duplicates>
)
select q.*
from (select q.*,
row_number() over (partition by v_name order by v_name) as seqnum
from q
) q
where seqnum = 1;
DISTINCT is not a function, it is an operator and its scope is the entire SELECT clause
(The query formatting is just for emphasizing the point)
SELECT DISTINCT
V.NAME,
POH.status,
poh.shipdate,
pod.orderqty,
POD.receivedqty,
POD.rejectedqty,
p.NAME
FROM purchasing.vendor v
...
That answers the error you get, however, I doubt if this will give you the results you are looking for

How can I get sum of Items from one table to another in Sql

How can I get sum of items from one table to another in sql
use join and subquery
select p.*,stock from theProduct p
join ( select prodcode,sum(Qty) as stock from thePurchaseDetail group by prodcode) t
on p.prodcode=t.prodcode
You can use APPLY :
select p.*, pp.stock
from theProduct p cross apply
(select sum(pp.qty) as stock
from thePurchaseDetail pp
where pp.prodcode = p.prodcode
) pp;
You can also use window function with JOIN :
select p.*, sum(pp.qty) over (partition by p.prodcode) as stock
from theProduct p inner join
thePurchaseDetail pp
on pp.prodcode = p.prodcode;
You would left join in case there are missing prodcodes in tblPurchaseDetail table
select p.*
,t.stock
from tblProduct p
left join ( select prodcode
,sum(Qty) as stock
from tblPurchaseDetail
group by prodcode
) t
on p.prodcode=t.prodcode

adding column to select gives group_by error

I have a following query:
SELECT transactions.created,
installments.*,
transactions.installment_id,
Sum(transactions.amount) AS s,
Sum(installments.amount) AS s1
FROM installments
LEFT OUTER JOIN transactions
ON transactions.installment_id = installments.id
WHERE installments.customer_id = ?
GROUP BY installments.id
which gives me error
org.h2.jdbc.JdbcSQLException: Column "TRANSACTIONS.CREATED" must be in
the GROUP BY list; SQL statement:
But I need the created field and don't want to use it in GROUP_BY.
SELECT *,s,S1,created
FROM installments
LEFT OUTER JOIN
(
SELECT SUM(transactions.amount) AS s, SUM(installments.amount) AS
s1,created
FROM transactions
GROUP BY created
) A ON A.installment_id = installments.id
WHERE installments.customer_id=?
Including it in the GROUP BY fixes the syntax problem:
SELECT t.created, i.*,
Sum(t.amount) AS s,
Sum(i.amount) AS s1
FROM installments i LEFT OUTER JOIN
transactions t
ON t.installment_id = i.id
WHERE i.customer_id = ?
GROUP BY i.id, t.created;
But the real issue is that you might have multiple transactions. Which created value do you want? You probably intend something more like:
SELECT Min(t.created) as created, i.*,
Sum(t.amount) AS s,
Sum(i.amount) AS s1
FROM installments i LEFT OUTER JOIN
transactions t
ON t.installment_id = i.id
WHERE i.customer_id = ?
GROUP BY i.id;
I should note that the calculation of s1 is probably wrong, because it is getting multiplied by the number of transactions. I'm thinking something more like this may be what you intend:
SELECT t.created, i.*,
t.amount as s,
Sum(i.amount) as s1
FROM installments i LEFT OUTER JOIN
(SELECT t.installment_id, t.created as created,
SUM(t.amount) as s
FROM transactions t
GROUP BY t.installment_id
) t
ON t.installment_id = i.id
WHERE i.customer_id = ?
GROUP BY i.id, t.created, t.amount;

How to have SQL INNER JOIN accept null results

I have the following query:
SELECT TOP 25 CLIENT_ID_MD5, COUNT(CLIENT_ID_MD5) TOTAL
FROM dbo.amazonlogs
GROUP BY CLIENT_ID_MD5
ORDER BY COUNT(*) DESC;
Which returns:
283fe255cbc25c804eb0c05f84ee5d52 864458
879100cf8aa8b993a8c53f0137a3a176 126122
06c181de7f35ee039fec84579e82883d 88719
69ffb6c6fd5f52de0d5535ce56286671 68863
703441aa63c0ac1f39fe9e4a4cc8239a 47434
3fd023e7b2047e78c6742e2fc5b66fce 45350
a8b72ca65ba2440e8e4028a832ec2160 39524
...
I want to retrieve the corresponding client name (FIRM) using the returned MD5 from this query, so a row might look like:
879100cf8aa8b993a8c53f0137a3a176 126122 Burger King
So I made this query:
SELECT a.CLIENT_ID_MD5, COUNT(a.CLIENT_ID_MD5) TOTAL, c.FIRM
FROM dbo.amazonlogs a
INNER JOIN dbo.customers c
ON c.CLIENT_ID_MD5 = a.CLIENT_ID_MD5
GROUP BY a.CLIENT_ID_MD5, c.FIRM
ORDER BY COUNT(*) DESC;
This returns something like:
879100cf8aa8b993a8c53f0137a3a176 126122 Burger King
06c181de7f35ee039fec84579e82883d 88719 McDonalds
703441aa63c0ac1f39fe9e4a4cc8239a 47434 Wendy's
3fd023e7b2047e78c6742e2fc5b66fce 45350 Tim Horton's
Which works, except I need to return an empty value for c.FIRM if there is no corresponding FIRM for a given MD5. For example:
879100cf8aa8b993a8c53f0137a3a176 126122 Burger King
06c181de7f35ee039fec84579e82883d 88719 McDonalds
69ffb6c6fd5f52de0d5535ce56286671 68863
703441aa63c0ac1f39fe9e4a4cc8239a 47434 Wendy's
3fd023e7b2047e78c6742e2fc5b66fce 45350 Tim Horton's
How should I modify the query to still return a row even if there is no corresponding c.FIRM?
Replace INNER JOIN with LEFT JOIN
use LEFT JOIN instead of INNER JOIN
Instead of doing an INNER join, you should do a LEFT OUTER join:
SELECT
a.CLIENT_ID_MD5,
COUNT(a.CLIENT_ID_MD5) TOTAL,
ISNULL(c.FIRM,'')
FROM
dbo.amazonlogs a LEFT OUTER JOIN
dbo.customers c ON c.CLIENT_ID_MD5 = a.CLIENT_ID_MD5
GROUP BY
a.CLIENT_ID_MD5,
c.FIRM
ORDER BY COUNT(0) DESC
http://www.w3schools.com/sql/sql_join.asp
An inner join excludes NULLs; you want a LEFT OUTER join.
SELECT a.CLIENT_ID_MD5, COUNT(a.CLIENT_ID_MD5) TOTAL, IsNull(c.FIRM, 'Unknown') as Firm
FROM dbo.amazonlogs a
LEFT JOIN dbo.customers c ON c.CLIENT_ID_MD5 = a.CLIENT_ID_MD5
GROUP BY a.CLIENT_ID_MD5, c.FIRM ORDER BY COUNT(*) DESC;
This will give you a value of "Unknown" when records in the customers table don't exist. You could obviously drop that part and just return c.FIRM if you want to have actual nulls instead.
Change your INNER JOIN to an OUTER JOIN...
SELECT a.CLIENT_ID_MD5, COUNT(a.CLIENT_ID_MD5) TOTAL, c.FIRM
FROM dbo.amazonlogs a
LEFT OUTER JOIN dbo.customers c
ON c.CLIENT_ID_MD5 = a.CLIENT_ID_MD5
GROUP BY a.CLIENT_ID_MD5, c.FIRM
ORDER BY COUNT(*) DESC;
WITH amazonlogs_Tallies
AS
(
SELECT a.CLIENT_ID_MD5, COUNT(a.CLIENT_ID_MD5) TOTAL
FROM dbo.amazonlogs a
GROUP
BY a.CLIENT_ID_MD5
),
amazonlogs_Tallies_Firms
AS
(
SELECT a.CLIENT_ID_MD5, a.TOTAL, c.FIRM
FROM amazonlogs_Tallies a
INNER JOIN dbo.customers c
ON c.CLIENT_ID_MD5 = a.CLIENT_ID_MD5
)
SELECT CLIENT_ID_MD5, TOTAL, FIRM
FROM amazonlogs_Tallies_Firms
UNION
SELECT CLIENT_ID_MD5, TOTAL, '{{NOT_KNOWN}}'
FROM amazonlogs_Tallies
EXCEPT
SELECT CLIENT_ID_MD5, TOTAL, '{{NOT_KNOWN}}'
FROM amazonlogs_Tallies_Firms;