adding column to select gives group_by error - sql

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;

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

Multiple joins with group by (Sum)

When I using multiple JOIN, I hope to get the sum of some column in joined tables.
SELECT
A.*,
SUM(C.purchase_price) AS purcchase_total,
SUM(D.sales_price) AS sales_total,
B.user_name
FROM
PROJECT AS A
LEFT JOIN
USER AS B ON A.user_idx = B.user_idx
LEFT JOIN
PURCHASE AS C ON A.project_idx = C.project_idx
LEFT JOIN
SALES AS D ON A.project_idx = D.project_idx
GROUP BY
????
You need to use subquery as follows:
SELECT A.project_idx,
a.project_name,
A.project_category,
sum(C.purchase_price) AS purcchase_total,
sum(D.sales_price) as sales_total,
B.user_name
FROM PROJECT AS A
LEFT JOIN USER AS B ON A.user_idx = B.user_idx
LEFT JOIN (select project_idx, sum(purchase_price) as purchase_price
from PURCHASE group by project_idx ) AS C ON A.project_idx = C.project_idx
LEFT JOIN (select project_idx, sum(sale_price) as sale_price
from SALES group by project_idx) AS D ON A.project_idx = D.project_idx
I am not sure but you can use inner join of project with user instead of left join.
SELECT A.project_idx,
a.project_name,
A.project_category,
purcchase_total,
sales_total,
B.user_name
FROM PROJECT AS A
LEFT JOIN USER AS B ON A.user_idx = B.user_idx
LEFT JOIN (select project_idx, sum(purchase_price) as purchase_total
from PURCHASE group by project_idx ) AS C ON A.project_idx = C.project_idx
LEFT JOIN (select project_idx, sum(sale_price) as sale_total
from SALES group by project_idx) AS D ON A.project_idx = D.project_idx
This is working correctly on MS-SQL Server.
Thanks to Popeye
You are attempting to aggregate over two unrelated dimensions, and that throws off all the calculations.
Correlated subqueries are an alternative:
SELECT p.*,
(SELECT SUM(pu.purchase_price)
FROM PURCHASE pu
WHERE p.project_idx = pu.project_idx
) as purchase_total,
(SELECT SUM(s.sales_price)
FROM SALES s
WHERE p.project_idx = s.project_idx
) as sales_total,
u.user_name
FROM PROJECT p LEFT JOIN
USER u
ON p.user_idx = u.user_idx ;
Note that this uses meaningful table aliases so the query is easier to read. Arbitrary letters are really no better (and perhaps worse) than using the entire table name.
Correlated subqueries avoid the outer aggregation as well -- and let you select all the columns from the first table, which is what you want. They also often have better performance with the right indexes.

Sql query to minus the two tables. What Is wrong?

select table1.t1 from
(
(
select
ItemCategory.Name as Category,
InventoryItems.Name as ItemName,
sum(SalesItems.Quantity) as Quantity,
(InventoryItems.Weight*sum(SalesItems.Quantity)) as Weight,
sum(SalesItems.Amount) as Amount
from SalesInvoices
inner join Sales on Sales.ID = SalesInvoices.SalesID
inner join SalesItems on SalesItems.SalesID = Sales.ID
inner join InventoryItems on InventoryItems.ID = SalesItems.InventoryItemID
inner join ItemCategory on ItemCategory.ID = InventoryItems.ItemCategoryID
inner join BusinessPartners on Sales.BusinessPartnerID = BusinessPartners.ID
where SalesInvoices.Date >= '2013-07-1' and SalesInvoices.Date <= '2013-11-7'
group by ItemCategory.Name,InventoryItems.Name,InventoryItems.Weight
) as t1,
(
select
ItemCategory.Name as Category,
InventoryItems.Name as ItemName,
sum(SalesAdjustmentItems.AdjustedQuantity)*-1 as Quantity,
(sum(SalesAdjustmentItems.AdjustedQuantity)*InventoryItems.Weight)*-1 as
Weight,
sum(SalesAdjustmentItems.AmountReturn)*-1 as Amount
from SalesInvoices
inner join Sales on Sales.ID = SalesInvoices.SalesID
inner join SalesItems on SalesItems.SalesID = Sales.ID
inner join SalesAdjustmentItems on SalesAdjustmentItems.SalesItemID = SalesItems.ID
inner join InventoryItems on InventoryItems.ID = SalesItems.InventoryItemID
inner join ItemCategory on ItemCategory.ID = InventoryItems.ItemCategoryID
inner join SalesAdustment on SalesAdustment.SalesInvoiceID = SalesInvoices.ID
inner join BusinessPartners on Sales.BusinessPartnerID = BusinessPartners.ID
where SalesAdustment.Date>= '2013-07-1' and SalesAdustment.Date <= '2013-11-7'
group by ItemCategory.Name,InventoryItems.Name,InventoryItems.Weight
) as t2
)
as table1
What I am doing wrong in this query. 1st query is for Sales and second query is for Sale returns. I want to get the difference of Sales and Returns. But is giving me error.
Thanks
The SQL minus operator is known as EXCEPT e.g. to find sales that have no invoices:
-- Sales minus SalesInvoices
SELECT ID
FROM Sales
EXCEPT
SELECT SalesID
FROM SalesInvoices;
If you are using older versions,
SELECT ID
FROM Sales
where not exists(SELECT SalesID FROM SalesInvoices where sales.ID=SalesID);

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

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

ORA-00979: not a GROUP BY expression? [duplicate]

This question already has answers here:
Oracle ORA-00979 - "not a GROUP BY expression"
(4 answers)
Closed 8 years ago.
I have found the solution to this, but what in case one of the column is a subquery, how can i include it in group by, or do i need to include that in group by. I will paste the query here..
SELECT s.customerid, s.denomid,
(SELECT su.quantity
FROM stockupdations su
WHERE s.customerid = su.custid
AND s.denomid = su.denomid
AND s.curid = su.curid) AS cur_stock, c.name AS cus_name, d.denomname AS denom,
cur.curcode AS currency
FROM stock s
LEFT JOIN customers c
ON s.customerid = c.custid
LEFT JOIN denomination d
ON d.denomid = s.denomid
LEFT JOIN currency cur
ON cur.curid = s.curid
GROUP BY s.denomid, s.customerid, c.name, d.denomname, cur.curcode
ORDER BY s.customerid ASC
What about a WITH statement?
WITH tmp AS
(
SELECT s.customerid, s.denomid,
c.name AS cus_name,
d.denomname AS denom,
cur.curcode AS currency
FROM stock s
LEFT JOIN customers c
ON s.customerid = c.custid
LEFT JOIN denomination d
ON d.denomid = s.denomid
LEFT JOIN currency cur
ON cur.curid = s.curid
GROUP BY s.denomid, s.customerid, c.name, d.denomname, cur.curcode
ORDER BY s.customerid ASC
)
SELECT tmp.customerid, tmp.denomid,
su.quantity,
tmp.cus_name,
tmp.denom,
tmp.currency
FROM tmp
INNER JOIN stockupdations su
ON tmp.customerid = su.custid
AND tmp.denomid = su.denomid
AND tmp.curid = su.curid
You can use your "Inner query" in the from clause than on the select.
Say I have a CUSTOMER table and ORDER table,
I can have something like
SELECT C.CUSTOMER_ID, COUNT(T.ORDER_ID)
FROM CUSTOMERS C
JOIN (SELECT CUSTOMER_ID, ORDER_ID, ORDER_DATE, ORDER_STATUS FROM ORDERS O WHERE O.STATUS <> 'DECLINED') T
ON T.CUSTOMER_ID = C.CUSTOMER ID
GROUP BY C.CUSTOMER_ID
(This SQL is just an example, and I know there are better ways to write this, but I could not think of any other example immediately)
You don't have to do everything at once. Try breaking your query into multiple pieces. Subqueries, analytic functions, or other complicated logic will look like simple rows to the outer query. (Don't worry about performance, Oracle will re-write it and do everything as one step if it makes sense.)
--Step 3
select [simple values]
from
(
--Step 2
select [insanity]
from
(
--Step 1
select [madness]
from
[impossible joins]
)
)
group by [simple values]