Sum or join rows by criteria - sql

Suppose I have a table like this:
Table1
EmployeeID Month sym Quantity V_M BudjetCode
222222 1 40 133.35 1214 800000
222222 2 40 178.50 115 800000
222222 3 40 150.33 215 800000
222222 4 40 186.37 315 800000
222222 5 40 127.38 415 800000
222222 6 40 153.00 515 800000
222222 7 40 178.50 615 800000
222222 7 40 8.50 615 700052015
222222 8 40 187.00 715 800000
And I would like to change this table to:
Table 2
EmployeeID Month sym Quantity V_M BudjetCode
222222 1 40 133.35 1214 800000
222222 2 40 178.50 115 800000
222222 3 40 150.33 215 800000
222222 4 40 186.37 315 800000
222222 5 40 127.38 415 800000
222222 6 40 161.5 515 800000
222222 7 40 178.50 615 800000
222222 8 40 187.00 715 800000
How?
See the row in table 1 where the BudjetCode is unusual?
well, for this row I would like to add the Quantity value (8.5) to the row there V_M is one less than the V_M is the original row (where the budjetCode is 700052015).
In the example, in the original it was 615 so one less is 515 (615 means date 6.15 and 515 means 5.15) and to that I need to add the quantity (8.5 to 153 = 161.5)
I was thinking of "over partition":
select [EmployeeID],[Month],[Sym],
sum([Quantity])
over (partition by [EmployeeID], [V_M]-1 where???) as b
from table1
where [Sym] = '40' and [EmployeeID] = 222222
order by [Month]
But I don't know how to sum this up usiing the criteria of the budjetCode starts with "700".
comment : Don't to this for the first row.
Update:
EmployeeID month Quantity V_M MS_BudjetCode
22222 1 40 133.35 1214 88888888
22222 2 40 178.50 115 88888888
22222 3 40 150.33 215 88888888
22222 4 40 186.37 315 88888888
22222 5 40 127.38 415 88888888
22222 6 40 153.00 515 88888888
22222 7 40 8.50 615 700000000
22222 7 40 178.50 615 88888888
22222 8 40 187.00 715 88888888
Output:
22222 2 40 178.50 115 88888888
22222 4 40 186.37 315 88888888
22222 8 40 187.00 715 88888888
22222 3 40 151.33 215 88888888
22222 3 40 151.33 215 88888888
22222 3 40 151.33 215 88888888
22222 3 40 149.33 215 88888888
22222 3 40 149.33 215 88888888
22222 5 40 127.38 415 88888888
22222 6 40 152.00 515 88888888
22222 6 40 154.00 515 88888888
22222 6 40 152.00 515 88888888
22222 6 40 154.00 515 88888888
22222 6 40 154.00 515 88888888
22222 6 40 152.00 515 88888888
22222 6 40 161.50 515 88888888
22222 7 40 178.50 615 88888888
22222 1 40 133.35 1214 88888888
Query:
SELECT t1.EmployeeID, t1.Month, t1.sym,
t1.Quantity + COALESCE(t2.Quantity, 0),
t1.V_M, t1.BudjetCode
FROM Table1 AS t1
LEFT JOIN Table1 AS t2
ON t1.EmployeeID = t2.EmployeeID AND t1.V_M = t2.V_M - 100 AND
SUBSTRING(t2.BudjetCode,1,3) = '700'
WHERE SUBSTRING(t1.BudjetCode,1,3) <> '700' and sym='40' and EmployeeID = 22222

This may be a solution, i think we cant simply subtract 1 with month, need to consider january december cases also. i had included that checking also. here budgetcode is distinguished with length, you can change it if needed.
;WITH CTETable1 AS
(
SELECT
EmployeeID,[Month],sym,
CASE WHEN LEN(BudjetCode)>6 THEN Quantity ELSE 0 END Quantity,
CASE WHEN LEN(BudjetCode)>6 THEN
(CASE WHEN LEFT(V_M,LEN(V_M)-2)=1 THEN 12 ELSE LEFT(V_M,LEN(V_M)-2)-1 END)*100+
(CASE WHEN LEFT(V_M,LEN(V_M)-2)=1 THEN RIGHT(V_M,2)-1 ELSE RIGHT(V_M,2) END) ELSE 0 END V_M,
BudjetCode
FROM Table1
)
SELECT
T1.EmployeeID,T1.[Month],T1.sym,T1.Quantity+ISNULL(T2.Quantity,0) Quantity,
T1.V_M,T1.BudjetCode
FROM Table1 T1
LEFT JOIN CTETable1 T2 ON T1.EmployeeID=T2.EmployeeID AND T1.V_M=T2.V_M
WHERE LEN(T1.BudjetCode)=6

Will this solve your problem?
DELETE
FROM
table
WHERE BudjetCode='700052015'
AND
UPDATE table
SET Quantity = (Quantity+8.5)
WHERE Month = 6;

I think you can do what you want using a simple LEFT JOIN operation:
SELECT t1.EmployeeID, t1.Month, t1.sym,
t1.Quantity + COALESCE(t2.Quantity, 0),
t1.V_M, t1.BudjetCode
FROM Table1 AS t1
LEFT JOIN Table1 AS t2
ON t1.EmployeeID = t2.EmployeeID AND t1.V_M = t2.V_M - 100 AND
SUBSTRING(t2.BudjetCode,1,3) = '700'
WHERE SUBSTRING(t1.BudjetCode,1,3) <> '700'
The query select all table rows but the 'unusual' ones and joins them with 'unusual' rows one day (or is it maybe hour?) ahead. Quantity returned is the sum of the value of 'normal' row plus the value of the related 'unusual' row (if such one really exists).
Expression:
t1.V_M = t2.V_M - 100
implements one less relation between V_Mof t1 and t2. You may change it to suit your actual needs.
Demo here

Related

Stored Procedure Select from 3 tables

I have three tables in my database Sales, SalesPeople and Appliances.
Sales
SaleDate EmployeeID AppID Qty
---------- ---------- ----- -----------
2010-01-01 1412 150 1
2010-01-05 3231 110 1
2010-01-03 2920 110 2
2010-01-13 1412 100 1
2010-01-25 1235 150 2
2010-01-22 1235 100 2
2010-01-12 2920 150 3
2010-01-14 3231 100 1
2010-01-15 1235 300 1
2010-01-03 2920 200 2
2010-01-31 2920 310 1
2010-01-05 1412 420 1
2010-01-15 3231 400 2
SalesPeople
EmployeeID EmployeeName CommRate BaseSalary SupervisorID
---------- ------------------------------ ----------- ----------- ------------
1235 Linda Smith 15 1200 1412
1412 Anne Green 12 1800 NULL
2920 Charles Brown 10 1150 1412
3231 Harry Purple 18 1700 1412
Appliances
ID AppType StoreID Cost Price
---- -------------------- ------- ------------- -------------
100 Refrigerator 22 150 250
110 Refrigerator 20 175 300
150 Television 27 225 340
200 Microwave Oven 22 120 180
300 Washer 27 200 325
310 Washer 22 280 400
400 Dryer 20 150 220
420 Dryer 22 240 360
How can I obtain this result? (That displays the profitability of each of the salespeople ordered from the most profitable to the least. Gross is simply the sum of the quantity of items sold multiplied by the price. Commission is calculated from the gross minus the cost of those items (i.e. from
qty*(price-cost)). Net profit is the total profit minus commission.)
Name Gross Commission Net Profit
------------- ----- ---------- ---------
Charles Brown 2380 83.5 751.5
Linda Smith 1505 83.25 471.75
Harry Purple 990 65.7 299.3
Anne Green 950 40.2 294.8
My attempt:
CREATE PROC Profitability AS
SELECT
sp.EmployeeName, (sum(s.Qty) * a.Price) as [Gross],
[Gross] - a.Cost, as [Commision],
SOMETHING as [Net Profit]
FROM
Salespeople sp, Appliances a, Sales s
WHERE
s.AppID = a.ID
AND sp.EmployeeID = s.EmployeeID
GROUP BY
sp.EmployeeName
GO
EXEC Profitability
Simple rule: Never use commas in the FROM clause. Always use explicit JOIN syntax.
In addition to fixing the JOIN syntax, your query needs a few other enhancements for the aggregation functions:
SELECT sp.EmployeeName, sum(s.Qty * a.Price) as Gross,
SUM(s.Qty * (a.Price - a.Cost)) * sp.CommRate / 100.0 as Commission,
SUM(s.Qty * (a.Price - a.Cost)) * (1 - sp.CommRate / 100.0) as NetProfit
FROM Sales s JOIN
Salespeople sp
ON sp.EmployeeID = s.EmployeeID JOIN
Appliances a
ON s.AppID = a.ID
GROUP BY sp.EmployeeName sp.CommRate
ORDER BY NetProfit DESC;

Trying to do sum math using Partition By and Row_Number

I'm trying to add a few columns to my table and I'm a bit of the way there but not clear why it's failing. This is an example starting table...
Date Name Amount
1/2/2015 Andy 148
2/5/2015 Andy 188
2/11/2015 Andy 154
1/15/2015 John 136
2/5/2015 John 176
1/7/2015 John 134
1/19/2015 John 251
2/21/2015 Carlos 120
2/15/2015 Carlos 211
1/8/2015 Carlos 120
1/2/2014 Andy 151
2/5/2014 Andy 281
2/11/2014 Andy 298
1/15/2014 John 292
2/5/2014 John 134
1/7/2014 John 281
1/19/2014 John 101
2/21/2014 Carlos 137
2/15/2014 Carlos 108
1/8/2014 Carlos 292
I want to take the above table and...
1) Sort by Year, Name, Then Value
2) Based on #1, Add the "Ordered" column which gives a number for each set of Year and Name where Value is set sorted ascending
3) Multiplied column is multiplying Amount by Ordered
4) Sum the multiplied column and a the sum to each set
Result...
Date Year Name Amount Ordered Multiplied Sum
1/2/2014 2014 Andy 151 1 151 1607
2/5/2014 2014 Andy 281 2 562 1607
2/11/2014 2014 Andy 298 3 894 1607
2/15/2014 2014 Carlos 108 1 108 1258
2/21/2014 2014 Carlos 137 2 274 1258
1/8/2014 2014 Carlos 292 3 876 1258
1/19/2014 2014 John 101 1 101 2380
2/5/2014 2014 John 134 2 268 2380
1/7/2014 2014 John 281 3 843 2380
1/15/2014 2014 John 292 4 1168 2380
1/2/2015 2015 Andy 148 1 148 1020
2/11/2015 2015 Andy 154 2 308 1020
2/5/2015 2015 Andy 188 3 564 1020
1/8/2015 2015 Carlos 120 1 120 993
2/21/2015 2015 Carlos 120 2 240 993
2/15/2015 2015 Carlos 211 3 633 993
1/7/2015 2015 John 134 1 134 1938
1/15/2015 2015 John 136 2 272 1938
2/5/2015 2015 John 176 3 528 1938
1/19/2015 2015 John 251 4 1004 1938
I have everything but the last column as I keep getting the error...
'Invalid expression near Row_Number'.
SQL for 'Ordered'...
ROW_NUMBER() OVER ( Partition BY Name, DATEPART(YEAR, Date) ORDER BY Amount ) AS 'Ordered'
SQL for 'Multiplied'...
Amount * Ordered AS Multiplied
Now I could be thinking of this naively but I thought I could just do add a line like this...
sum(Multiplied) OVER ( Partition BY Name, DATEPART(YEAR, Date) ORDER BY Amount ) AS 'Sum'
But I keep getting the error mentioned. Any ideas how to handle? I'm welcome to hearing other ways of handling the data. I only care about the last column
If your syntax worked, it would produce a cumulative sum. That doesn't appear to be what you want.
I think you can do what you want with a subquery:
select t.*,
(seqnum * amount) as multiplied,
sum(seqnum * amount) over (partition by name, year(date)) as thesum
from (select t.*,
row_number() over (partition by name, year(date) order by date) as seqnum
from table t
) t;

How will i join/combine these two table to get one result

Table 1
Code Name1 type BalanceDue id1 id2 id3 id4 id5 emp
2600 intl-Airfare 1 2.38 120 410 510 603 7060513 null
1100 intl-travel 1 2.66 120 420 540 602 7060513 null
2400 intl-Meals 1 1.50 120 420 520 602 7060513 null
4100 Transpo 2 19.70 110 210 510 601 null
4100 Transpo 2 13.25 110 210 500 601 null
4100 Transpo 2 17.38 110 210 500 600 null
3600 Dom travel 3 25.11 110 210 500 600 55713 null
Table 2
Code Details Total type code1 code2 code3 code4 code5 emp
4100 no#233 Emp1-Parking 11.39 2 110 210 510 601 null null
4100 no#231 Jes-Parking 6.83 2 110 210 510 601 null null
4100 no#232 Jes-TransExp 1.48 2 110 210 510 601 null null
4100 no#234 Emp2-TollFee 0.23 2 110 210 500 601 null null
4100 no#239 Emp2-Parking 1.82 2 110 210 500 601 null null
4100 no#240 Emp3-Parking 2.96 2 110 210 500 601 null null
4100 no#252 Emp5-TollFee 8.24 2 110 210 500 601 null null
4100 no#235 Jay-TollFee 4.90 2 110 210 500 600 null null
4100 no#243 Jay-TransExp 12.48 2 110 210 500 600 null null
I want this as a result:
if type is 1 then display all values in table 1 which has type 1
same as if type is 3
if type is 2 then display all values in table 2 considering code1 to code5
Result
Code Details type Total code1 code2 code3 code4 code5 emp
2600 intl-Airfare 1 2.38 120 410 510 603 7060513 null
1100 intl-travel 1 2.66 120 420 540 602 7060513 null
2400 intl-Meals 1 1.50 120 420 520 602 7060513 null
4100 no#233 Emp1-Parking 2 11.39 110 210 510 601 null null
4100 no#231 Jes-Parking 2 6.83 110 210 510 601 null null
4100 no#232 Jes-TransExp 2 1.48 110 210 510 601 null null
4100 no#234 Emp2-TollFee 2 0.23 110 210 500 601 null null
4100 no#239 Emp2-Parking 2 1.82 110 210 500 601 null null
4100 no#240 Emp3-Parking 2 2.96 110 210 500 601 null null
4100 no#252 Emp5-TollFee 2 8.24 110 210 500 601 null null
4100 no#235 Jay-TollFee 2 4.90 110 210 500 600 null null
4100 no#243 Jay-TransExp 2 12.48 110 210 500 600 null null
3600 Dom travel 3 25.11 110 210 500 600 55713 null
Sometimes, id5 has a value and sometimes other columns were null.
sorry, i hope i explained it clearly :(
Thanks everyone!
Your desired results look like a UNION (The UNION clause lets your "merge" together more queries in a single, final result set) similar to this:
SELECT Code, Name1 as Details, type, BalanceDue as Total,id1 as code1, id2 as code2,
id3 as code3, id4 as code4, id5 as code5, emp
FROM Table1 WHERE type IN (1, 3) -- could also be WHERE type = 1 OR type = 3
UNION
SELECT Code, Details, type, Total, code1, code2, code3, code4, code5, emp
FROM Table2 WHERE type = 2

Stock on the fly missing values with no sales

I have the following queries,
QryStockOnHand
SELECT QrySaleTot.Item, QrySaleTot.ProductID, [QryStockLevel].[Stock]-[QrySaleTot].[Quantity] AS StockOnHand
FROM QryStockLevel INNER JOIN QrySaleTot ON QryStockLevel.ProductID = QrySaleTot.ProductID;
QrySaleTot
SELECT TblProduct.Item, Sum(TblTotalSale.Size) AS Quantity, TblProduct.ProductID
FROM TblProduct INNER JOIN TblTotalSale ON TblProduct.[ProductID] = TblTotalSale.[ProductID]
GROUP BY TblProduct.Item, TblProduct.ProductID;
QryStockLevel
SELECT TblStock.ProductID, Sum(TblStock.StockLevel) AS Stock, TblProduct.Item
FROM TblStock INNER JOIN TblProduct ON TblStock.ProductID = TblProduct.ProductID
GROUP BY TblStock.ProductID, TblProduct.Item;
When I run the QryStockonHand and no sales of a product have been made then the porduct does not appear in the result of the query...
Sample Data
TblStock
StockID ProductID StockLevel
138 1 528
139 3 528
140 5 528
141 9 528
142 7 528
143 18 80
144 30 72
145 34 72
146 33 72
147 32 200
148 22 80
149 19 80
150 23 80
151 20 80
TblProduct
ProductID Item Price StockDelivery PriceSmall Large Small
1 Carling £2.50 528 £1.40 2 1
3 Carlsburg £2.70 528 £1.60 2 1
5 IPA £2.30 528 £1.20 2 1
7 StrongBow £2.80 528 £1.65 2 1
9 RevJames £2.45 528 £1.30 2 1
11 Becks £2.90 72 1
12 WKDBlue £2.80 72 1
13 WKDRed £2.80 72 1
14 SmirnoffIce £2.80 72 1
TblTotalSale
TotalSalesID ProductID SalePrice Day Time Size
576 1 £1.40 19/02/2012 15:34:24 1
528 1 £2.50 09/02/2012 14:44:44 2
530 1 £1.40 09/02/2012 14:44:44 1
565 1 £2.50 19/02/2012 15:34:24 2
567 1 £1.40 19/02/2012 15:34:24 1
570 3 £2.70 19/02/2012 15:34:24 2
571 3 £1.60 19/02/2012 15:34:24 1
577 3 £2.70 19/02/2012 15:34:24 2
578 3 £1.60 19/02/2012 15:34:24 1
533 3 £2.70 09/02/2012 14:44:44 2
534 3 £1.60 09/02/2012 14:44:44 1
Any Idea why... I guess it is a null thing, where it is seeing the no sales as a non existent thing, instead of a zero sales.... any idea how i could fix it?
Thanks
Sam
Instead of an inner join, use a left outer join, which will tell it to grab all of the rows from the left hand table on the join, instead of an inner join, which returns only rows which have values in both tables.
I don't know the QryStockLevel fields, but your query should look something like this:
SELECT QryStockLevel.Item, QryStockLevel.ProductID, [QryStockLevel].[Stock]-NZ([QrySaleTot].[Quantity],0) AS StockOnHand
FROM QryStockLevel LEFT OUTER JOIN QrySaleTot ON QryStockLevel.ProductID = QrySaleTot.ProductID;
Note the NZ function to handle a null on the Quantity when qrysaletot does not have a row.

SQL query self join

I am working on a query for a report in Oracle 10g.
I need to generate a short list of each course along with the number of times they were offered in the past year (including ones that weren't actually offered).
I created one query
SELECT coursenumber, count(datestart) AS Offered
FROM class
WHERE datestart BETWEEN (sysdate-365) AND sysdate
GROUP BY coursenumber;
Which produces
COURSENUMBER OFFERED
---- ----------
ST03 2
PD01 1
AY03 2
TB01 4
This query is all correct. However ideally I want it to list those along with COURSENUMBER HY and CS in the left column as well with 0 or null as the OFFERED value. I have a feeling this involves a join of sorts, but so far what I have tried doesn't produce the classes with nothing offered.
The table normally looks like
REFERENCE_NO DATESTART TIME TIME EID ROOMID COURSENUMBER
------------ --------- ---- ---- ---------- ---------- ----
256 03-MAR-11 0930 1100 2 2 PD01
257 03-MAY-11 0930 1100 12 7 PD01
258 18-MAY-11 1230 0100 12 7 PD01
259 24-OCT-11 1930 2015 6 2 CS01
260 17-JUN-11 1130 1300 6 4 CS01
261 25-MAY-11 1900 2000 13 6 HY01
262 25-MAY-11 1900 2000 13 6 HY01
263 04-APR-11 0930 1100 13 5 ST03
264 13-SEP-11 1930 2100 6 4 ST03
265 05-NOV-11 1930 2100 6 5 ST03
266 04-FEB-11 1430 1600 6 5 ST03
267 02-JAN-11 0630 0700 13 1 TB01
268 01-FEB-11 0630 0700 13 1 TB01
269 01-MAR-11 0630 0700 13 1 TB01
270 01-APR-11 0630 0700 13 1 TB01
271 01-MAY-11 0630 0700 13 1 TB01
272 14-MAR-11 0830 0915 4 3 AY03
273 19-APR-11 0930 1015 4 3 AY03
274 17-JUN-11 0830 0915 14 3 AY03
275 14-AUG-09 0930 1015 14 3 AY03
276 03-MAY-09 0830 0915 14 3 AY03
SELECT
coursenumber,
COUNT(CASE WHEN datestart BETWEEN (sysdate-365) AND sysdate THEN 1 END) AS Offered
FROM class
GROUP BY coursenumber;
So, as you can see, this particular problem doesn't need a join.
I think something like this should work for you, by just doing it as a subquery.
SELECT distinct c.coursenumber,
(SELECT COUNT(*)
FROM class
WHERE class.coursenumber = c.coursenumber
AND datestart BETWEEN (sysdate-365) AND sysdate
) AS Offered
FROM class c
I like jschoen's answer better for this particular case (when you want one and only one row and column out of the subquery for each row of the main query), but just to demonstrate another way to do it:
select t1.coursenumber, nvl(t2.cnt,0)
from class t1 left outer join (
select coursenumber, count(*) cnt
from class
where datestart between (sysdate-365) AND sysdate
group by coursenumber
) t2 on t1.coursenumber = t2.coursenumber