Oracle SQL is not a group by expression - sql

When I run this code
SELECT sales_date AS DAY, SUM (TOTAL_AMOUNT_RM)
FROM SALESINVOICE
GROUP BY SALES_DATE
order by sales_date;
it gives me a table which is
enter image description here
Then I run the other code
SELECT SR.DAY, SUM(SR.PROFIT_RM) AS TOTALPROFIT_RM
FROM
(SELECT SALESINVOICE_ID AS NO, S.ITEM_ID AS ITEM, SALES_DATE AS DAY, S.QUANTITY, C.PRICE_RM, C.COST_RM, ((S.QUANTITY*C.PRICE_RM)-(S.QUANTITY*C.COST_RM)) AS PROFIT_RM
FROM SALESINVOICE S
INNER JOIN (SELECT ITEM_ID, PRICE_RM, COST_RM FROM ITEM I ) C
ON S.ITEM_ID = C.ITEM_ID
ORDER BY SALESINVOICE_ID, SALES_DATE) SR
GROUP BY SR.DAY
ORDER BY SR.DAY;
which gave me a table enter image description here
I try to inner join both table, but when I try to call the column sum(total_amount_rm), it gives me an group by expression error
SELECT SR.DAY, SUM(SR.PROFIT_RM) AS TOTALPROFIT_RM, D.TOTAL
FROM
(SELECT SALESINVOICE_ID AS NO, S.ITEM_ID AS ITEM, SALES_DATE AS DAY, S.QUANTITY, C.PRICE_RM, C.COST_RM, ((S.QUANTITY*C.PRICE_RM)-(S.QUANTITY*C.COST_RM)) AS PROFIT_RM
FROM SALESINVOICE S
INNER JOIN (SELECT ITEM_ID, PRICE_RM, COST_RM FROM ITEM I ) C
ON S.ITEM_ID = C.ITEM_ID
ORDER BY SALESINVOICE_ID, SALES_DATE) SR
INNER JOIN (SELECT SALES_DATE AS DAY, SUM(TOTAL_AMOUNT_RM) AS TOTAL FROM SALESINVOICE GROUP BY SALES_DATE ORDER BY SALES_DATE) D
ON D.DAY = SR.DAY
GROUP BY SR.DAY
ORDER BY SR.DAY;
However, it can run without the selection of D.TOTAL. Can anyone help me? I am sql newbie

You get an error because with GROUP BY, in SELECT clause, all columns that is not in the GROUP BY clause must come with an aggregate function
To solve it, one way is calculating sum before joining
SELECT s.day, s.totalprofit_rm, d.total
FROM
(
SELECT sr.day,
SUM(sr.profit_rm) AS totalprofit_rm
FROM
(
SELECT salesinvoice_id AS no,
s.item_id AS item,
sales_date AS day,
s.quantity,
c.price_rm,
c.cost_rm,
(s.quantity * c.price_rm) - (s.quantity * c.cost_rm) AS profit_rm
FROM salesinvoice s
INNER JOIN (SELECT item_id, price_rm, cost_rm FROM item) c
ON s.item_id = c.item_id
) sr
GROUP BY sr.day
) s
INNER JOIN
(
SELECT sales_date AS day,
SUM(total_amount_rm) AS total
FROM salesinvoice
GROUP BY sales_date
) d
ON d.day = s.day
ORDER BY s.day;
And this above query could be rewrite to be shorter:
SELECT sr.day, sr.totalprofit_rm, d.total
FROM
(
SELECT
s.sales_date AS day,
SUM((s.quantity * c.price_rm) - (s.quantity * c.cost_rm)) AS totalprofit_rm
FROM salesinvoice s
INNER JOIN item c
ON s.item_id = c.item_id
GROUP BY s.sales_date
) sr
INNER JOIN
(
SELECT sales_date AS day,
SUM(total_amount_rm) AS total
FROM salesinvoice
GROUP BY sales_date
) d
ON d.day = sr.day
ORDER BY sr.day;

Related

SQL - Finding entries that are the max of a count?

I have a table like the above image shown, how can I display the id and names of the customers and the category of the food that have the customer ordered the most?
SELECT Customer_ID, COUNT(F_Catg)
FROM ORDER_RECORD ORD
INNER JOIN FOOD_MENU FM
ON ORD.Item_ID = FM.Item_ID
GROUP BY Customer_ID
HAVING COUNT(F_Catg) =
(SELECT MAX(c) FROM
(SELECT COUNT(F_Catg) AS c
FROM ORDER_RECORD ORD
INNER JOIN FOOD_MENU FM
ON ORD.Item_ID = FM.Item_ID
GROUP BY Customer_ID))
I tried this but it doesn't work.
Your derived table is missing an alias. Fix that, and your query should work:
SELECT Customer_ID, COUNT(F_Catg)
FROM ORDER_RECORD ORD
INNER JOIN FOOD_MENU FM ON ORD.Item_ID = FM.Item_ID
GROUP BY Customer_ID
HAVING COUNT(F_Catg) = (SELECT MAX(c) FROM (
SELECT COUNT(F_Catg) AS c
FROM ORDER_RECORD ORD
INNER JOIN FOOD_MENU FM ON ORD.Item_ID = FM.Item_ID
GROUP BY Customer_ID) t -- fix is here
);
On MySQL 8+, you could simplify a bit by using the RANK analytic function:
WITH cte AS (
SELECT Customer_ID, COUNT(F_Catg) AS cnt,
RANK() OVER (ORDER BY COUNT(F_Catg) DESC) rnk
FROM ORDER_RECORD ORD
INNER JOIN FOOD_MENU FM ON ORD.Item_ID = FM.Item_ID
GROUP BY Customer_ID
)
SELECT Customer_ID, cnt
FROM cte
WHERE rnk = 1;

Get the second last record in a date column within a inner join

I need to pull the second last record in a date column called OrderDate. However, I need to bring only one date (I am making the search into a table with all the purchases orders, dates and costs, in which a have to bring only the second last and its cost). The way its query is written today (and working) is pulling me the the newest date.
select distinct
a.PurchaseNum, a.ItemID, a.SupplierNum, a.Location, a.OrderDate, a.Cost
from
PurchaseOrder a
inner join
(select
l.SupplierNum, l.ItemID, l.Location, maxdate = max(l.OrderDate)
from
PurchaseOrder l
where
l.Cost <> 0
group by
l.SupplierNum, l.itemid, l.Location) l on a.SupplierNum = l.SupplierNumand a.itemid = l.itemid
and l.Location = a.Location
and a.OrderDate = l.maxdate
I have tried to use lag(), offset (but with limitations once is within a join, forcing me to use the order by and include the dateOrder column which is not what I want because we need only one date)
A bit of context: I have a report in which I need to show the last and second last cost of a purchase order for each supplier. Bring the last cost of an order is easy, the problem is go back to the second last... and it is where I am stuck right now.
Any thought?
If I'm understanding you correctly, here's one option using row_number to return the 2 highest orderdate records:
select *
from (
select *,
row_number() over (partition by SupplierNum, ItemID, Location
order by OrderDate desc) rn
from PurchaseOrder
where cost <> 0
) t
where rn <= 2
Inner query does order by desc and outside query does order by asc.
select distinct top 1 a.*
from PurchaseOrder a
inner join
(
select Top 2 l.*
from PurchaseOrder l
where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location order by orderdate desc) l
on a.SupplierNum= l.SupplierNumand a.itemid = l.itemid and l.Location=a.Location and a.OrderDate = l.Orderdate
order by a.orderdate
or
SELECT TOP 1 * FROM (SELECT * FROM PurchaseOrder a
EXCEPT SELECT TOP (SELECT (COUNT(*)-2) FROM PurchaseOrder a where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location) * FROM PurchaseOrder) A
or
SELECT *
FROM PurchaseOrder a
WHERE OrderDate = ( SELECT MAX(OrderDate)
FROM PurchaseOrder
WHERE Orderdate < ( SELECT MAX(OrderDate)
FROM PurchaseOrder l where
l.Cost <> 0
group by l.SupplierNum, l.itemid, l.Location
)
) ;
or
SELECT TOP (1) *
FROM PurchaseOrder
WHERE OrderDate < ( SELECT MAX(OrderDate)
FROM PurchaseOrder where ....
)
ORDER BY OrderDate DESC ;

Find product id of the top selling product of each day, using total sold quantity to determine the top selling product

Adventureworks2008R2 database.
The result I want is each day the MaxQantity sold, for example, OrderDate 2007-09-01 shall have the max quantity of 96 only, but my query gives me 3 different results from the same day, maybe because it is considering the timestamp as well
SELECT DISTINCT CAST(oh.OrderDate AS DATE) OrderDate, (od.ProductID),SUM(od.OrderQty) MAXOrderQty
FROM Sales.SalesOrderDetail od
Inner Join Sales.SalesOrderHeader oh
ON od.SalesOrderID = oh.SalesOrderID
GROUP BY od.ProductID, CAST(oh.OrderDate AS DATE), od.OrderQty
ORDER BY SUM(od.OrderQty) DESC
You can write CTE and self JOIN on MAX Qty by date
;WITH CTE(OrderDate,ProductID,MAXOrderQty) AS(
SELECT CAST(oh.OrderDate AS DATE) OrderDate,od.ProductID,SUM(od.OrderQty) MAXOrderQty
FROM Sales.SalesOrderDetail od
Inner Join Sales.SalesOrderHeader oh
ON od.SalesOrderID = oh.SalesOrderID
GROUP BY od.ProductID, CAST(oh.OrderDate AS DATE)
)
SELECT t1.*
FROM CTE t1 INNER JOIN (
select OrderDate,MAX(MAXOrderQty) 'MAXOrderQty'
from CTE
GROUP BY OrderDate
)t2 on t1.OrderDate = t2.OrderDate and t1.MAXOrderQty = t2.MAXOrderQty
It's much easier addressing problems like this with window functions. In this case, rank should do the trick:
SELECT OrderDate, ProductID, MaxOrderQty
FROM (SELECT OrderDate, ProductID, MaxOrderQty,
RANK() OVER (PARTITION BY OrderDate ORDER BY MaxOrderQty DESC) AS rk
FROM Sales.SalesOrderDetail) s
WHERE rk = 1

Using SQL how can I write a query to find the top 5 per category per month?

I am trying to get the Top 5 rows with the highest number for each category for a specific time interval such as a month. What I currently have returns 5 of the exact same descriptions for a category. I am trying to get the top five. This only happens when I try to sort it based on a time period.
WITH CustomerRank
AS
(SELECT
Count(*) AS "Count",
d.Item,
d.Description,
Name,
i.Type,
d.CreatedOn
FROM [dbo].i,
d,
dbo.b,
as,
a,
c
WHERE d.Inspection_Id = i.Id AND d.Inspection_Id = i.Id AND
b.Id = i.BuildingPart_Id AND b.as= Assessments.Id
AND as.Application_Id = a.Id AND a.Customer_Id = Customers.Id
group by d.Item, d.Description, Name, i.Type, d.CreatedOn
)
select * from (
SELECT "Count",Item,Description,Type,ROW_NUMBER() Over (PARTITION BY Name order by "Count" desc) AS RowNum, Name, CreatedOn
FROM CustomerRank
where CreatedOn > '2017-1-1 00:00:00'
) s where RowNum <6
Cheers
Try something like this:
WITH CustomerRank
AS
(SELECT
Count(*) AS "Count",
d.Item, d.Description, Name, i.Type
FROM dbo.Inspection i
INNER JOIN dbo.Details d ON d.Inspection_Id = i.Id
INNER JOIN dbo.BuildingParts b ON b.Id = i.BuildingPart_Id
INNER JOIN dbo.Assessments a ON a.Id = b.AssessmentId
INNER JOIN dbo.Applications ap ON ap.Id = a.Application_Id
INNER JOIN dbo.Customers c ON c.Id = a.Customer_Id
where CreatedOn > '2017-1-1 00:00:00'
group by d.Item, d.Description, Name, i.Type
)
select * from (
SELECT "Count",Item,Description,Type,ROW_NUMBER() Over (PARTITION BY Name order by "Count" desc) AS RowNum, Name
FROM CustomerRank
) s where RowNum <6
The idea is that the CreatedOn column must be removed from the GROUP BY clause (because if you keep it there, we would get a different row for each value of the CreatedOn column).
Also, it's better to use JOIN-s and aliases for each table.

SQL: improving join efficiency

If I turn this sub-query which selects sales persons and their highest price paid for any item they sell:
select *,
(select top 1 highestProductPrice
from orders o
where o.salespersonid = s.id
order by highestProductPrice desc ) as highestProductPrice
from salespersons s
in to this join in order to improve efficiency:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders ) o on s.id = o.salespersonid
It still touches every order record (it enumerates the entire table before filtering by salespersonid it seems.) However you cannot do this:
select *, highestProductPrice
from salespersons s join (
select salespersonid, highestProductPrice, row_number(
partition by salespersonid
order by salespersonid, highestProductPrice) as rank
from orders
where orders.salepersonid = s.id) o on s.id = o.salespersonid
The where clause in the join causes a `multi-part identifier "s.id" could not be bound.
Is there any way to join the top 1 out of each order group with a join but without touching each record in orders?
Try
SELECT
S.*,
T.HighestProductPrice
FROM
SalesPersons S
CROSS APPLY
(
SELECT TOP 1 O.HighestProductPrice
FROM Orders O
WHERE O.SalesPersonid = S.Id
ORDER BY O.SalesPersonid, O.HighestProductPrice DESC
) T
would
select s.*, max(highestProductPrice)
from salespersons s
join orders o on o.salespersonid = s.id
group by s.*
or
select s.*, highestProductPrice
from salespersons s join (select salepersonid,
max(highestProductPrice) as highestProductPrice
from orders o) as o on o.salespersonid = s.id
work?