Select From Multiple tables with group by in SQL - sql

I have two views:
Purchase_Details view:
InvoiceId
ItemId
Quantity
Price
ExpireDate
ItemCode
StoreId
and Sale_Details view
InvoiceId
ItemId
Quantity
Price
ItemCode
StoreId
and Item Table:
ID
Name
SalePrice
PurchasePrice
Purchase_Detail View data
Id StoreId ItemCode Price Quantity ItemId ExpireDate
51 1 345 300.00 1.00 5 2019-10-25
52 1 348 300.00 22.00 5 2019-10-04
53 2 348 300.00 17.00 5 2019-11-11
54 1 345 300.00 8.00 5 2019-12-12
Sale_Details View data
Id StoreId ItemCode Price Quantity ItemId
55 1 345 300.00 4.00 5
56 1 348 300.00 3.00 5
I want to found sum of quantity of each item grouped by ItemId and ItemCode.
Second: is it better to put the sales and purchase and the same table or separate them (as I did?

I think you'll want to union the data together first, and then group by so your sum crosses the sets. You can do this with a CTE. Something like this:
WITH CTE_Detail AS (
SELECT ItemId, ItemCode, Quantity FROM Purchase_Details
UNION ALL
SELECT ItemId, ItemCode, Quantity FROM Sale_Details
)
SELECT ItemId, ItemCode, SUM(Quantity) AS Quantity
FROM CTE_Detail
GROUP BY ItemId, ItemCode
As far as your question about "what is better", that depends on how you want to use it. Obviously, there is a little additional complexity in this query by having them separate, but it might help you in other areas. I believe you said they are views, so can't you have it both ways?

This should do it
with purchases as (
select
ItemId,
ItemCode,
sum(quantity) as purchase_sum
from
Purchase_Detail
group by
1,
2
),
sales as (
select
ItemId,
ItemCode,
sum(quantity) as sales_sum
from
Sales_Details
group by
1,
2
)
select
ItemId,
ItemCode,
(
purchase_sum - sales_sum
) as net
from
purchases
join sales
using (
ItemId,
ItemCode
)

Related

Get value based on date

I am trying to get the price point of a product based on future pricing. I can't really do it by Max (Expiration Date) since the price points are different. And I also can't do a Max(Price) since the high price might be the one expiring or it could be the new one. My data would look like this:
Supplier
Product
Price
Effective Date
Expiration Date
Supplier 1
A
800
04-01-2121
12-31-2023
Supplier 1
A
1000
01-01-2121
03-31-2023
Supplier 1
B
500
04-01-2121
12-31-2023
Supplier 1
B
400
01-01-2121
03-31-2023
Supplier 2
D
200
01-01-2121
12-31-2023
Supplier 2
C
600
01-01-2121
12-31-2023
The result I am trying to get is below:
Supplier
Product
Price
Effective Date
Expiration Date
Supplier 1
A
800
04-01-2121
12-31-2023
Supplier 1
B
500
04-01-2121
12-31-2023
Supplier 2
D
200
01-01-2121
12-31-2023
Supplier 2
C
600
01-01-2121
12-31-2023
Any ideas?
To have the product information when expirationdate is the greatest we can product wise group the rows in descending order of expirationdate and find the row with lowest serial number for each product or we can simply use subquery to select the information where expirationdate=max(expirationdate) within the product group.
First approach will be more efficient. But if your dbms doesn't support row_number() then you can use second approach.
Schema:
create table mydata(Supplier varchar(30),Product varchar(30), Price int,EffectiveDate date, ExpirationDate date);
insert into mydata values('Supplier 1', 'A', 800 ,'04-01-2121', '12-31-2023');
insert into mydata values('Supplier 1', 'A', 1000 ,'01-01-2121', '03-31-2023');
insert into mydata values('Supplier 1', 'B', 500 ,'04-01-2121', '12-31-2023');
insert into mydata values('Supplier 1', 'B', 400 ,'01-01-2121', '03-31-2023');
insert into mydata values('Supplier 2', 'D', 200 ,'01-01-2121', '12-31-2023');
insert into mydata values('Supplier 2', 'C', 600 ,'01-01-2121', '12-31-2023');
Query#1
WITH cte
AS (SELECT supplier,
product,
price,
effectivedate,
expirationdate,
ROW_NUMBER ()
OVER (PARTITION BY product
ORDER BY expirationdate DESC)
rn
FROM mydata)
SELECT supplier,
product,
price,
effectivedate,
expirationdate
FROM cte
WHERE rn = 1
Output:
supplier
product
price
effectivedate
expirationdate
Supplier 1
A
800
2121-04-01
2023-12-31
Supplier 1
B
500
2121-04-01
2023-12-31
Supplier 2
C
600
2121-01-01
2023-12-31
Supplier 2
D
200
2121-01-01
2023-12-31
Query#2 for older version of DBMS:
SELECT supplier,
product,
price,
effectivedate,
expirationdate
FROM mydata m
where effectivedate =
(select max(effectivedate) from mydata md where m.product=md.product)
Output:
supplier
product
price
effectivedate
expirationdate
Supplier 2
D
200
2121-01-01
2023-12-31
Supplier 2
C
600
2121-01-01
2023-12-31
Supplier 1
B
500
2121-04-01
2023-12-31
Supplier 1
A
800
2121-04-01
2023-12-31
db<>fiddle here
It looks you can accomplish what you need with a simple row_number by numbering the rows decending ordered against your ExpirationDate and filtering for the first row in each group.
select Supplier, Product, Price, EffectiveDate, ExpirationDate from (
select Supplier, Product, Price, EffectiveDate, ExpirationDate, row_number() over (partition by Product order by ExpirationDate desc)Seq
from table
)t
where Seq=1

calculating percentage of sales profit in SQL

Product Group Product ID Sales Profit
A 6797 1,000 200
A 6745 500 90
B 1278 200 60
B 1245 1,500 350
C 7890 650 80
D 4587 350 50
Q1). Filter out product IDs that contribute to top 80% of the total profit of their respective group.
Not sure what rdbms you are using you can get the output in SQL server in this way. You can get profit for a group and use aggregate function to compare and filter the rows.
select 'A' as Product_group, 6797 as ProductID, 1000 as Sales , 200 as Profit into #temp1 union all
select 'A' as Product_group, 6745 as ProductID, 500 as Sales , 90 as Profit union all
select 'B' as Product_group, 1278 as ProductID, 200 as Sales , 60 as Profit union all
select 'B' as Product_group, 1245 as ProductID, 1500 as Sales , 350 as Profit union all
select 'C' as Product_group, 7890 as ProductID, 650 as Sales , 80 as Profit union all
select 'D' as Product_group, 4587 as ProductID, 350 as Sales , 50 as Profit
select t.Product_group, t.ProductID, sum(t.sales) totalsles, sum(t.profit) totalProfit, sum(Profit_grp) Groupprofit from #temp1 t
join (select Product_group, sum(sales) totalsles_group, sum(profit) Profit_grp from #temp1 t1 group by Product_group) t1 on t1.Product_group = t.Product_group
group by t.Product_group, t.ProductID
having sum(t.profit) *1.0/ sum(t1.Profit_grp) *1.0 >= 0.8
Output: I added group profit just to compare. You can remove the aggregate and add in group by if you would like
Product_group ProductID totalsles totalProfit Groupprofit
B 1245 1500 350 410
C 7890 650 80 80
D 4587 350 50 50
I think this may works out:
with CTE as(
select [Product Group], sum([Sales]) as Tolsum from Table
group by [Product Group]
select prod.*,
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) as contribution
from CTE cte
inner join
Table prod
on
cte.[Product Group] = prod.[Product Group]
having
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) < 0.8

SQL Running Total Reset on Condition

I have the following table:
Transaction History Table
TransactionHistoryId ProductCode Type Quantity PurchasePrice CurrentPrice
1 Product1 B 10 3.00 2.00
2 Product1 B 5 7.00 2.00
3 Product1 S -7 7.00 2.00
4 Product1 S -8 3.00 3.00
5 Product1 B 4 10.00 10.00
6 Product1 B 5 12.00 12.00
8 Product2 B 8 20.00 20.00
I would like to acheive the following table:
TransactionHistoryId ProductCode Type Quantity PurchasePrice QtyRunning PriceRunning
1 Product1 B 10 3.00 10 30.00
2 Product1 B 5 7.00 15 65.00
3 Product1 S -7 7.00 8 65.00
4 Product1 S -8 3.00 0 0.00
5 Product1 B 4 10.00 4 40.00
6 Product1 B 5 12.00 9 100.00
8 Product2 B 8 20.00 8 160.00
Create Table SQL
IF OBJECT_ID('TEMPDB..#TransactionHistory') IS NOT NULL
DROP TABLE #TransactionHistory
create table #TransactionHistory
(TransactionHistoryId int,
ProductCode varchar(10),
Type char(1),
Quantity smallint,
PurchasePrice decimal(18,2),
CurrentPrice decimal(18,2)
)
insert into #TransactionHistory
values
(1,'Product1','B',10,3.00,2.00),
(2,'Product1','B',5,7.00,2.00),
(3,'Product1','S',-7,7.00,2.00),
(4,'Product1','S',-8,3.00,3.00),
(5,'Product1','B',4,10.00,10.00),
(6,'Product1','B',5,12.00,12.00),
(8,'Product2','B',8,20.00,20.00)
Rules
PriceRunningTotal resets when the quantity running total is 0
PriceRunningTotal sums up only Type = 'B' (buys), when Type = 'S' (sold) keep the previous purchase price running total
Notice there is a Product 2 so it should have it's own running count independent of Product 1
Purpose
A query to ultimately find out the following:
Product Quantity AdjustedPurchasePrice
Product1 9 $11.11
Product2 8 $20
I used the following SQL Server 2012 query to get the result, but I feel it could be done much better:
Query
SELECT *,
PriceRunningTotalFinal =
SUM(CASE
WHEN QuantityRunningTotal = 0 THEN -1 * PriceRunningTotal
WHEN Quantity < 0 THEN 0 ELSE PurchasePrice * Quantity END) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
)
FROM (
SELECT TransactionHistoryId, ProductCode, Type, Quantity, PurchasePrice,
QuantityRunningTotal = SUM(Quantity) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
),
PriceRunningTotal = SUM(CASE WHEN Quantity < 0 THEN 0 ELSE PurchasePrice * Quantity END) OVER
(
PARTITION BY ProductCode
ORDER BY TransactionHistoryId ROWS UNBOUNDED PRECEDING
)
FROM TransactionHistory
) AS Results1
ORDER BY ProductCode;
Problem
Ideally I would of liked to use the QuantityRunningTotal within another query but I can't nest windowed functions.
Anyone know of a more efficient way to achieve this result?
Hmmm. I think something like this:
select th.*,
sum(case when type = 'B' then Quantity * PurchasePrice
else 0
end) over (partition by grp, ProductCode order by TransactionHistoryId
) as PriceRunningTotal
from (select th.*,
sum(case when running_quantity = 0 then 1 else 0 end) over (partition by ProductCode order by TransactionHistoryId) as grp
from (select th.*,
sum(quantity) over (partition by ProductCode order by TransactionHistoryId
) as running_quantity
from TransactionHistory th
) th;
I'm not sure if this is the same logic as your query. For this query:
The innermost subquery calculates the running quantity.
The middle subquery calculates a group based on the number of times the running quantity has been 0.
The outermost query then calculates the running price.

Filter by Last Record from Table for Particular Date that has repeated Item No in SQL

I want to know how to implement following logic in SQL, I have a table with data in the following format:
Date ItemNo Price
--------------------------
20160101 101 150
20160101 101 155
20160101 101 140
20160101 102 144
20160101 103 147
20160102 101 150
20160102 101 155
20160102 101 140
20160102 102 144
20160102 103 147
so from the above, I want to select all the ItemNo but only to pick the last entry from ItemNo "101" as an example (it can be any ItemNo which has multiple data for same date), so meaning my output table will have the following output:
Date ItemNo Price
--------------------------
20160101 101 140
20160101 102 144
20160101 103 147
20160102 101 140
20160102 102 144
20160102 103 147
Is there any way to accomplish this?!
You can use LAST_VALUE
SELECT DISTINCT
date,
itemno,
LAST_VALUE(price) OVER (PARTITION BY date, itemno ORDER BY itemno)
FROM #test
I don't know how do you define 'last item' - based on your data it's not clear why price 160 is the last item for ItemNo 101 and Date 20160101. But if you have some column with 'Time' or 'Id' you can do it like this:
;with cte as (
select
Date, ItemNo, Price,
row_number() over(partition by Date, ItemNo order by Time desc) as rn
from <table>
)
select
Date, ItemNo, Price
from cte
where
rn = 1
There is no such thing as the "last" item no, unless you have a column that specifies the ordering. If you don't have a unique id, I would suggest something like this:
select Date, ItemNo, Price
from t
where itemno <> 101
union all
select Date, ItemNo, Price
from (select t.*,
row_number() over (partition by date, itemno order by itemno) as seqnum
from t
where itemno = 101
) t
where seqnum = 1;
If you do have a unique id, you can do something like this:
select date, itemno, max(price) as price
from t
group by date,
(case when itemno <> 101 then id end);
Admittedly, this will always return the highest price for itemno, but that does match some definition of "any".

Sql Select from two tables in single query

I have Two Tables as under
SalesDetails PurchaseDetails
Date SaleOrderId ProductId Qty Date PurcsOrderId ProductId Qty
2/2/12 S_1 P_1 4 1/2/12 PO_1 P_1 50
3/2/12 S_2 P_1 5 4/2/12 PO_2 P_1 50
3/2/12 S_2 P_2 7
6/2/12 S_3 P_1 3
9/2/12 S_4 1 5
from these two tables i want to show a report like this
Product Inquiry Report
ProductId: P_1
Date TrId Debit Credit
1/2/12 PO_1 50
2/2/12 S_1 4
3/2/12 S_2 5
4/2/12 PO_2 50
6/2/12 S_3 3
Here Qty column of purchasedetails becomes 'Debit' in the report and Qty in SalesDetails becomes 'Credit' column in Report.In report, the transaction should be shown in order to Date as shown Is there a way to perform this in a single query, if yes, then how? if no, then is there any other solution? Thanks in advance
Use UNION:
SELECT Date, SaleOrderID as TrId, NULL as Debit, Qty as Credit
FROM SalesDetails
UNION
SELECT Date, PurcsOrderId AS TrId, Qty AS Debit, NULL as Credit
FROM PurchaseDetails
ORDER BY Date
SQL Fiddle:
SELECT *
FROM
(
SELECT sa.Date, sa.SaleOrderId AS TRId, Null AS Debit, sa.Qty AS Credit
FROM SalesDetails sa
UNION
SELECT pa.Date, pa.PurcsOrderId AS TRId, pa.Qty AS Debit, Null AS Credit
FROM PurchaseDetails pa
) m
ORDER BY 1
If you would rather the values be 0 then use 0 AS Debit and 0 AS Credit respectively.