calculate closing Stock Quantity,price & value by FIFO - sql-server

i am using sql server 2008.
i am calculating envetory as FIFO method like in tally.
please go through
http://accountingexplained.com/financial/inventories/fifo-method
i have table as
create table #Stock
(
StoreId int,
ProductId int,
Transection_Date Date,
transectionType varchar(20),
Unit numeric(18,2),
UnitCost numeric(18,2)
)
insert into #Stock select 1,201, '2014-03-01', 'Beginning Inventory', 68, 14
insert into #Stock select 1,201,'2014-03-05', 'Purchase', 140, 15.50
insert into #Stock select 1,201,'2014-03-09', 'Issue', 94, 0
insert into #Stock select 1,201,'2014-03-11', 'Purchase', 40, 16
insert into #Stock select 1,201,'2014-03-16', 'Purchase', 68, 14
insert into #Stock select 1,201,'2014-03-20', 'Issue', 116, 0
insert into #Stock select 1,201,'2014-03-29', 'Issue', 62, 0
select * from #Stock
drop table #Stock
i am looking sql function or store procedure which will accept parameter
fromDate,toDate and sorteId
and display as given format

How about:
--DROP TABLE #Stock
create table #Stock
(
Transection_Date Date,
transectionType varchar(20),
Unit numeric(18,2),
UnitCost numeric(18,2)
)
--Mar 1 Beginning Inventory 68 units # $15.00 per unit
--5 Purchase 140 units # $15.50 per unit
--9 Sale 94 units # $19.00 per unit
--11 Purchase 40 units # $16.00 per unit
--16 Purchase 78 units # $16.50 per unit
--20 Sale 116 units # $19.50 per unit
--29 Sale 62 units # $21.00 per unit
insert into #Stock select '2014-03-01', 'Beginning Inventory', 68, 15
insert into #Stock select '2014-03-05', 'Purchase', 140, 15.50
insert into #Stock select '2014-03-09', 'Sale', 94, 19
insert into #Stock select '2014-03-11', 'Purchase', 40, 16
insert into #Stock select '2014-03-16', 'Purchase', 78, 16.5
insert into #Stock select '2014-03-20', 'Sale', 116, 19.50
insert into #Stock select '2014-03-29', 'Sale', 62, 21.00
;WITH UnitsCTE
AS
(
-- GET Total Units Left
SELECT SUM(
CASE transectionType
WHEN 'Purchase' Then Unit
When 'Sale' THEN Unit * -1
ELSE Unit END) AS Units
FROM #Stock
), PurchaseCTE
AS
(
-- Get only purchases in reverse order
SELECT Unit, UnitCost, Transection_Date, ROW_NUMBER() OVER (ORDER BY Transection_Date DESC ) AS RN
FROM #Stock
WHERE transectionType <> 'Sale'
),
UnitCost
AS
(
-- Recursive CTE to get number of units left at each price
SELECT CASE WHEN Unit > UnitsCTE.Units THEN UnitsCTE.Units ELSE Unit END As Units, UnitCost
FROM PurchaseCTE
CROSS APPLY UnitsCTE
WHERE RN = 1
UNION ALL
SELECT CASE WHEN P1.Unit > (UnitsCTE.Units - (SELECT SUM(Unit) FROM PurchaseCTE P3 WHERE p3.RN < p1.RN))
THEN CASE WHEN (UnitsCTE.Units - (SELECT SUM(Unit) FROM PurchaseCTE P3 WHERE p3.RN < p1.RN)) < 0 THEN 0
ELSE (UnitsCTE.Units - (SELECT SUM(Unit) FROM PurchaseCTE P3 WHERE p3.RN < p1.RN)) END
ELSE P1.Unit END,
P1.UnitCost
FROM PurchaseCTE P1
INNER JOIN PurchaseCTE P2
ON P1.RN = P2.RN + 1
CROSS APPLY UnitsCTE
)
SELECT SUM(Units), SUM(UnitCost * Units) / SUM(Units) AS UnitCost, SUM(Units * UnitCost) AS TotalCost
FROM UnitCost
WHERE Units > 0

Related

What is the most effcient way to replace values in a specific column of a table for this specific scenario?

I am using SQL Server 2014 and I have a table in my database called t1 (extract of only 2 columns shown below):
ResaID StayDate
100 2020-02-03
100 2020-02-04
100 2020-02-05
120 2020-04-06
120 2020-04-07
120 2020-04-08
120 2020-04-09
120 2020-04-10
I need to change the dates in the StayDate column based on the following information (extract shown exactly as provided):
ID StartDate EndDate
100 2020-06-04 2020-06-06
120 2021-03-01 2021-03-05
I have started writing my T-SQL query as follows (but it is getting quite tedious as I have to do it for more than 100 ResaID!):
USE MyDatabase
UPDATE t1
SET StayDate = CASE WHEN ResaID = 100 and StayDate = '2020-02-03' THEN '2020-06-04'
WHEN ResaID = 100 and StayDate = '2020-02-04' THEN '2020-06-05'
WHEN ResaID = 100 and StayDate = '2020-02-05' THEN '2020-06-06'
...
ELSE StayDate
END
Is there a more efficient way to tackle this problem?
You can use recursive approach :
with r_cte as (
select id, convert(date, startdate) as startdate, convert(date, enddate) as enddate
from ( values (100, '2020-06-04', '2020-06-06'),
(120, '2021-03-01', '2021-03-03')
) t(id, startdate, enddate)
union all
select id, dateadd(day, 1, startdate), enddate
from cte c
where startdate < enddate
), r_cte_seq as (
select r.cte.*, row_number() over(partition by id order by startdate) as seq
from r_cte
), cte_seq as (
select t1.*, row_number() over (partition by ResaID order by staydate) as seq
from t1
)
update cs
set cs.staydate = rc.startdate
from cte_seq cs inner join
r_cte_seq rc
on rc.id = cs.ResaID and rc.seq = cs.seq;
Here is my approach to this problem. I would use a numbers table to generate a record for each date in the new range for each reservation ID. I would then partition this data by reservation ID, ordered by the date. Doing the same partition logic on the existing data will allow records to be properly joined together.
I would then do a DELETE operation followed by an INSERT operation. This would leave you with the appropriate amount of records. The only manual thing that would need to be done is to populate the auxiliary data for reservations with expanded date ranges. I expanded one of your new ranges to show this scenario.
I've marked where the setup for this demo ends in the code below. Everything below that is my intended solution that should be able to be implemented with your real tables.
--Ranges Table
DECLARE #ranges TABLE
(
ID INT
,StartDate DATETIME
,EndDate DATETIME
)
DECLARE #t1 TABLE
(
ResaID INT
,StayDate DATETIME
,ColA INT
,ColB NVARCHAR(100)
,ColC BIT
)
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
VALUES
(100, '2020-02-03', 1, 'A', 0)
,(100, '2020-02-04', 100, 'B', 1)
,(100, '2020-02-05', 255, 'C', 1)
,(120, '2020-04-06', 34, 'D', 1)
,(120, '2020-04-07', 67, 'E', 0)
,(120, '2020-04-08', 87, 'F', 0)
,(120, '2020-04-09', 545, 'G', 1)
,(120, '2020-04-10', 288, 'H', 0)
INSERT INTO #ranges
(
ID
,StartDate
,EndDate
)
VALUES
(100, '2020-06-04', '2020-06-07')
,(120, '2021-03-01', '2021-03-05')
--END DEMO SETUP
DROP TABLE IF EXISTS #numbers
DROP TABLE IF EXISTS #newRecords
--GENERATE NUMBERS TABLE
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b) -- 5*10000
SELECT ROW_NUMBER() OVER (ORDER BY n) as Num
INTO #numbers
FROM e4
ORDER BY n;
;with oldData --PARTITION THE EXISTING RECORDS
AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ResaID ORDER BY STAYDATE) as ResPartID
FROM #t1
)
,newRanges --GENERATE YOUR NEW RANGES AND PARITITION
AS
(
select
r.ID
,CAST(n.num as DATETIME) as StayDate
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY n.num) as ResPartID
from #ranges r
inner join #numbers n on CAST(r.StartDate as INT) <= n.Num AND CAST(r.EndDate as INT) >= n.Num
)
SELECT n.ID
,n.StayDate
,o.ColA
,o.ColB
,o.ColC
into #newRecords
FROM newRanges n
left join oldData o on n.ID = o.ResaID and n.ResPartID = o.ResPartID
--DELETE OLD RECORDS
DELETE t
FROM #t1 t
inner join #ranges r on t.ResaID = r.ID
--INSERT NEW DATA
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
SELECT
ID
,StayDate
,ColA
,ColB
,ColC
FROM #newRecords
SELECT * FROM #t1
The following code converts the t1 dates into ranges and then uses the corresponding range dates to calculate new StayDate values. You can swap out the final select for one of the commented statements to see what is going on in the CTEs. The final select can be replaced with an update if you want to change the original table data.
-- Thanks to Aaron Hughes for setting up the sample data.
-- I changed the DateTime columns to Date .
--Ranges Table
DECLARE #ranges TABLE
(
ID INT
,StartDate DATE
,EndDate DATE
)
DECLARE #t1 TABLE
(
ResaID INT
,StayDate DATE
,ColA INT
,ColB NVARCHAR(100)
,ColC BIT
)
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
VALUES
(100, '2020-02-03', 1, 'A', 0)
,(100, '2020-02-04', 100, 'B', 1)
,(100, '2020-02-05', 255, 'C', 1)
,(120, '2020-04-06', 34, 'D', 1)
,(120, '2020-04-07', 67, 'E', 0)
,(120, '2020-04-08', 87, 'F', 0)
,(120, '2020-04-09', 545, 'G', 1)
,(120, '2020-04-10', 288, 'H', 0)
INSERT INTO #ranges
(
ID
,StartDate
,EndDate
)
VALUES
(100, '2020-06-04', '2020-06-07')
,(120, '2021-03-01', '2021-03-05');
with
-- Calculate the date range for each stay in #t1 .
ResaRanges as (
select ResaId, Min( StayDate ) as ResaStartDate, Max( StayDate ) as ResaEndDate
from #t1
group by ResaId ),
-- Match up the #t1 date ranges with the #ranges date ranges.
CombinedRanges as (
select RR.ResaId, RR.ResaStartDate, RR.ResaEndDate, DateDiff( day, RR.ResaStartDate, RR.ResaEndDate ) + 1 as ResaDays,
R.StartDate, R.EndDate, DateDiff( day, R.StartDate, R.EndDate ) + 1 as RangeDays,
DateDiff( day, RR.ResaStartDate, R.StartDate ) as DaysOffset
from ResaRanges as RR inner join
#ranges as R on R.ID = RR.ResaId )
-- Calculate the new StayDate values for all #t1 ranges that are not longer than the corresponding #range .
-- The difference between range starting dates is added to each StayDate .
select T.ResaId, T.StayDate, DateAdd( day, CR.DaysOffset, T.StayDate ) as NewStayDate
from #t1 as T inner join
CombinedRanges as CR on CR.ResaID = T.ResaID
where CR.RangeDays >= CR.ResaDays;
-- To see the steps you can use one of the following select staements to view the intermediate results:
-- select * from ResaRanges;
-- select * from CombinedRanges;

How to count number per month and then take average of it in same select statement

How to calculate total average per month in case like below?:
We have 9 claimID's. so Aveage would be 9/ 6 distinct months = 1.5
DECLARE #TestTable TABLE (claimid int, DateClosed datetime)
INSERT INTO #TestTable
VALUES (111, '01-01-2018'), (222, '01-03-2018'), (333, '01-12-2018'),
(444, '07-03-2018'), (555, '08-15-2018'), (666, '09-13-2018'),
(777, '04-03-2019'), (888, '05-01-2019'), (999, '07-01-2018'),
(1000, NULL), (1100, NULL), (1200, NULL)
SELECT
ClaimID,
CAST(DateClosed AS DATE) AS DateClosed,
COUNT(ClaimID) CountClaimID,
COUNT(claimid) OVER (PARTITION BY MONT(DateClosed), YEAR(DateClosed)) AS CountPerMonth
FROM
#TestTable
GROUP BY
ClaimID, DateClosed
Perhaps something like this
Example
SELECT ClaimID
,cast(DateClosed AS date) AS DateClosed
,count(ClaimID) CountClaimID
,count(claimid) OVER ( PARTITION BY Month(DateClosed), year(DateClosed)) AS CountPerMonth
,case when DateClosed is null then 0 else count(DateClosed) over () / (select 0.0+count(distinct left(cast(DateClosed as date),7)) from #TestTable) end AS TotalAverage
FROM #TestTable
GROUP BY ClaimID,DateClosed
Returns

Retrieve records based on preference

I have a table with sample data below.
PatId NetType
100 In
100 Out
100 NA
101 Out
101 NA
102 NA
103 In
When there are multiple netTypeid for same patient return only top one prioritized by( In,Out,NA) as order. What i am trying to do when there are In/Out/NA available for a patid then should return back only In, when there is Out/NA available for a patid then it should return back only In.If no duplicate just return back as is. Output for above scenario should be
PatId NetType
100 In
101 Out
102 NA
103 In
Use row_number() to order your table by NetType
select
PatId, NetType
from (
select
PatId, NetType
, row_number() over (partition by PatId order by case NetType when 'In' then 1 when 'Out' then 2 else 3 end) rn
from
myTable
) t
where
rn = 1
Similar to uzi
DECLARE #T AS TABLE (PatId int, NetType varchar(20));
insert into #t values
(100, 'In')
, (100, 'Out')
, (100, 'NA')
, (101, 'Out')
, (101, 'NA')
, (102, 'NA')
, (103, 'In');
DECLARE #O AS TABLE (ord int primary key, NetType varchar(20));
insert into #O values (1, 'In'), (2, 'Out'), (3, 'NA');
select tt.PatId, tt.NetType
from ( select t.*
, ROW_NUMBER() over (partition by PatId order by o.ord) as rn
from #t t
join #O o
on t.NetType = o.NetType
) tt
where tt.rn = 1;

SQL - Filter on dates X number of days apart from the previous

I have a table containing orders. I would like to select those orders that are a certain number of days apart for a specific client. For example, in the table below I would like to select all of the orders for CustomerID = 10 that are at least 30 days apart from the previous instance. With the starting point to be the first occurrence (07/05/2014 in this data).
OrderID | CustomerID | OrderDate
==========================================
1 10 07/05/2014
2 10 07/15/2014
3 11 07/20/2014
4 11 08/20/2014
5 11 09/21/2014
6 10 09/23/2014
7 10 10/15/2014
8 10 10/30/2014
I would want to select OrderIDs (1,6,8) since they are 30 days apart from each other and all from CustomerID = 10. OrderIDs 2 and 7 would not be included as they are within 30 days of the previous order for that customer.
What confuses me is how to set the "checkpoint" to the last valid date. Here is a little "pseudo" SQL.
SELECT OrderID
FROM Orders
WHERE CusomerID = 10
AND OrderDate > LastValidOrderDate + 30
i came here and i saw #SveinFidjestøl already posted answer but i can't control my self after by long tried :
with the help of LAG and LEAD we can comparison between same column
and as per your Q you are looking 1,6,8. might be this is helpful
SQL SERVER 2012 and after
declare #temp table
(orderid int,
customerid int,
orderDate date
);
insert into #temp values (1, 10, '07/05/2014')
insert into #temp values (2, 10, '07/15/2014')
insert into #temp values (3, 11, '07/20/2014')
insert into #temp values (4, 11, '08/20/2014')
insert into #temp values (5, 11, '09/21/2014')
insert into #temp values (6, 10, '09/23/2014')
insert into #temp values (7, 10, '10/15/2014')
insert into #temp values (8, 10, '10/30/2014');
with cte as
(SELECT orderid,customerid,orderDate,
LAG(orderDate) OVER (ORDER BY orderid ) PreviousValue,
LEAD(orderDate) OVER (ORDER BY orderid) NextValue,
rownum = ROW_NUMBER() OVER (ORDER BY orderid)
FROM #temp
WHERE customerid = 10)
select orderid,customerid,orderDate from cte
where DATEDIFF ( day , PreviousValue , orderDate) > 30
or PreviousValue is null or NextValue is null
SQL SERVER 2005 and after
WITH CTE AS (
SELECT
rownum = ROW_NUMBER() OVER (ORDER BY p.orderid),
p.orderid,
p.customerid,
p.orderDate
FROM #temp p
where p.customerid = 10)
SELECT CTE.orderid,CTE.customerid,CTE.orderDate,
prev.orderDate PreviousValue,
nex.orderDate NextValue
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
where CTE.customerid = 10
and
DATEDIFF ( day , prev.orderDate , CTE.orderDate) > 30
or prev.orderDate is null or nex.orderDate is null
GO
You can use the LAG() function, available in SQL Server 2012, together with a Common Table Expression. You calculate the days between the customer's current order and the customer's previous order and then query the Common Table Expression using the filter >= 30
with cte as
(select OrderId
,CustomerId
,datediff(d
,lag(orderdate) over (partition by CustomerId order by OrderDate)
,OrderDate) DaysSinceLastOrder
from Orders)
select OrderId, CustomerId, DaysSinceLastOrder
from cte
where DaysSinceLastOrder >= 30 or DaysSinceLastOrder is null
Results:
OrderId CustomerId DaysSinceLastOrder
1 10 NULL
6 10 70
3 11 NULL
4 11 31
5 11 32
(Note that 1970-01-01 is chosen arbitrarily, you may choose any date)
Update
A slighty more reliable way of doing it will involve a temporary table. But the original table tbl can be left unchanged. See here:
CREATE TABLE #tmp (id int); -- set-up temp table
INSERT INTO #tmp VALUES (1); -- plant "seed": first oid
WHILE (##ROWCOUNT>0)
INSERT INTO #tmp (id)
SELECT TOP 1 OrderId FROM tbl
WHERE OrderId>0 AND CustomerId=10
AND OrderDate>(SELECT max(OrderDate)+30 FROM tbl INNER JOIN #tmp ON id=OrderId)
ORDER BY OrderDate;
-- now list all found entries of tbl:
SELECT * FROM tbl WHERE EXISTS (SELECT 1 FROM #tmp WHERE id=OrderId)
#tinka shows how to use CTEs to do the trick, and the new windowed functions (for 2012 and later) are probably the best answer. There is also the option, assuming you do not have a very large data set, to use a recursive CTE.
Example:
declare #customerid int = 10;
declare #temp table
(orderid int,
customerid int,
orderDate date
);
insert into #temp values (1, 10, '07/05/2014')
insert into #temp values (2, 10, '07/15/2014')
insert into #temp values (3, 11, '07/20/2014')
insert into #temp values (4, 11, '08/20/2014')
insert into #temp values (5, 11, '09/21/2014')
insert into #temp values (6, 10, '09/23/2014')
insert into #temp values (7, 10, '10/15/2014')
insert into #temp values (8, 10, '10/30/2014');
with datefilter AS
(
SELECT row_number() OVER(PARTITION BY CustomerId ORDER BY OrderDate) as RowId,
OrderId,
CustomerId,
OrderDate,
DATEADD(day, 30, OrderDate) as FilterDate
from #temp
WHERE CustomerId = #customerid
)
, firstdate as
(
SELECT RowId, OrderId, CustomerId, OrderDate, FilterDate
FROM datefilter
WHERE rowId = 1
union all
SELECT datefilter.RowId, datefilter.OrderId, datefilter.CustomerId,
datefilter.OrderDate, datefilter.FilterDate
FROM datefilter
join firstdate
on datefilter.CustomerId = firstdate.CustomerId
and datefilter.OrderDate > firstdate.FilterDate
WHERE NOT EXISTS
(
SELECT 1 FROM datefilter betweens
WHERE betweens.CustomerId = firstdate.CustomerId
AND betweens.orderdate > firstdate.FilterDate
AND datefilter.orderdate > betweens.orderdate
)
)
SELECT * FROM firstdate

Unpivot table data

Sno Water Milk
1 50 100
2 22 120
3 11 142
i have this table.Now i want result like
Sno Type Qnty
1 Water 83
2 Milk 362
How can I please tell me.
SQL Server 2008 does not support this kind of statement.
You can achieve that in 2 ways:
using temporary table (variable type table)
DECLARE #products TABLE(Sno INT, Water INT, Milk INT)
INSERT INTO #products
VALUES (1, 50, 100), (2, 22, 120), (3, 11, 142)
SELECT ROW_NUMBER() OVER(ORDER BY SUM(Qnty)) AS RowNo, Product, SUM(Qnty) AS Qnty
FROM (
SELECT Product, Qnty
FROM (
SELECT *
FROM #products
) AS pvt
UNPIVOT (Qnty FOR Product IN ([Water],[Milk])) AS unpvt
) AS T
GROUP BY Product</pre>
or
;WITH T AS
(
SELECT Sno, Water, Milk
FROM (
SELECT 1 AS Sno, 50 AS Water, 100 AS Milk
UNION ALL
SELECT 2, 22, 120
UNION ALL
SELECT 3, 11, 142
) t (Sno, Water, Milk))
SELECT Sno = ROW_NUMBER() OVER(ORDER BY SUM(Upvt.Qnty)),
upvt.Type,
Qnty = SUM(Upvt.Qnty)
FROM T
UNPIVOT
( Qnty
FOR Type IN ([Water], [Milk])
) upvt
GROUP BY upvt.Type
ORDER BY Qnty;</pre>
Please,refer MSDN documentation.
The first step is to UNPIVOT your data:
WITH T AS
( SELECT Sno, Water, Milk
FROM (VALUES (1, 50, 100), (2, 22, 120), (3, 11, 142)) t (Sno, Water, Milk)
)
SELECT upvt.Sno,
upvt.Type,
Upvt.Qnty
FROM T
UNPIVOT
( Qnty
FOR Type IN ([Water], [Milk])
) AS upvt;
Which will give:
Sno Type Qnty
1 Water 50
1 Milk 100
2 Water 22
2 Milk 120
3 Water 11
3 Milk 142
You can then apply normal aggregation to this result:
WITH T AS
( SELECT Sno, Water, Milk
FROM (VALUES (1, 50, 100), (2, 22, 120), (3, 11, 142)) t (Sno, Water, Milk)
)
SELECT Sno = ROW_NUMBER() OVER(ORDER BY SUM(Upvt.Qnty)),
upvt.Type,
Qnty = SUM(Upvt.Qnty)
FROM T
UNPIVOT
( Qnty
FOR Type IN ([Water], [Milk])
) AS upvt
GROUP BY upvt.Type
ORDER BY Qnty;
Giving:
Sno Type Qnty
1 Water 83
2 Milk 362
EDIT
Based on your comment that you are using SQL Server 2005, not 2008 as indicated below is a full working example that will work on 2005:
DECLARE #T TABLE (Sno INT, Water INT, Milk INT);
INSERT #T (Sno, Water, Milk) VALUES (1, 50, 100);
INSERT #T (Sno, Water, Milk) VALUES(2, 22, 120);
INSERT #T (Sno, Water, Milk) VALUES(3, 11, 142);
SELECT Sno = ROW_NUMBER() OVER(ORDER BY SUM(Upvt.Qnty)),
upvt.Type,
Qnty = SUM(Upvt.Qnty)
FROM #T AS T
UNPIVOT
( Qnty
FOR Type IN ([Water], [Milk])
) AS upvt
GROUP BY upvt.Type
ORDER BY Qnty;
It was the table valued constructor I was using to create the sample data that was causing the error in 2005, nothing to do with the actual query that unpivots then sums the data.
This gives the result you asked for but I get the idea you want something a little more advanced. If so please elaborate.
SELECT 'water', sum(water) FROM table_name
UNION ALL
SELECT 'milk', sum(milk) FROM table_name

Resources