SQL server SUM of MAX in different columns for each item - sql

+---------+--------+-----+---------+-------+-----------+-------+-----------+--------+
| Quot_ID | Art_ID | Qty | Qty_Alt | Price | Price_Alt | Value | Value_Alt | Status |
+---------+--------+-----+---------+-------+-----------+-------+-----------+--------+
| 1000000 | 100000 | 10 | 0 | 100 | 0 | 1000 | 0 | lost |
| 1000000 | 100000 | 0 | 20 | 0 | 90 | 0 | 1800 | lost |
| 1000000 | 100000 | 0 | 30 | 0 | 80 | 0 | 2400 | won |
| 1000000 | 100000 | 0 | 40 | 0 | 70 | 0 | 2800 | lost |
| 1000000 | 200000 | 10 | 0 | 150 | 0 | 1500 | 0 | lost |
| 1000000 | 200000 | 0 | 20 | 0 | 140 | 0 | 2800 | lost |
| 1000000 | 200000 | 0 | 30 | 0 | 130 | 0 | 3900 | lost |
| 1000000 | 200000 | 0 | 40 | 0 | 120 | 0 | 4800 | lost |
+---------+--------+-----+---------+-------+-----------+-------+-----------+--------+
Above an example of a quotation.
I need the percentage 'won' / 'quoted'
'quoted' is defined as the Value (EUR) or ValueAlt (EUR) for each Article_ID where the Qty or Qty_Alt has the maximum value.
In this example both articles 100000 and 200000 are quoted with max value of 40 pieces. The corresponding Values are 2800 + 4800 = 7600 EUR.
The percentage won I am looking for is therefore 100 * 2400 / 7600 = 31.6%
This code
"SELECT a.Quot_ID, (a.Value + a.Value_Alt) FROM Quotations a LEFT JOIN Quotations b ON a.Quot_ID = b.Quot_ID AND (a.Qty + a.Qty_Alt) < (b.Qty + b.Qty_Alt) WHERE (b.Qty + b.Qty_Alt) is NULL ORDER BY a.QuotationCode"
gives me
1000000 2800
1000000 4800
Does any one know how I can change this into one result:
1000000 7600
OK, Almost there:
Thanks to Muhammad I now have
SELECT a.Quot_ID, sum(a.Value + a.Value_Alt) FROM Quotations a LEFT JOIN Quotations b ON a.Quot_ID = b.Quot_ID AND (a.Qty + a.Qty_Alt) < (b.Qty + b.Qty_Alt) WHERE (b.Qty + b.Qty_Alt) is NULL GROUP BY a.Quot_ID ORDER BY a.Quot_ID
Last step is to get the percentage 'won'
In this example 100 * 2400 / 7600 = 31,6%
Any ideas or hints?
Thanks in advance

SELECT a.Quot_ID, sum(a.Value + a.Value_Alt) , (a.Value_alt/(sum (a.value+a.value_alt))*100 as percentage FROM Quotations a LEFT JOIN Quotations b ON a.Quot_ID = b.Quot_ID AND (a.Qty + a.Qty_Alt) < (b.Qty + b.Qty_Alt) WHERE (b.Qty + b.Qty_Alt) is NULL GROUP BY a.Quot_ID ORDER BY a.QuotationCode

Related

SQL showing summary after each column value changes

I have sales data for branches. I want a SQL which will give me summary of sales data for each branchId as below, of course BranchId(s) are huge so I have to make it dynamic (I can't use Union). I am stuck how to add a summary row after every branch change dynamically.
+ ---------+--------+---------+-----------+
| BranchId | CashIn | CashOut | CardSales |
+ ---------+--------+---------+-----------+
| 1 | 1000 | 500 | 50 |
| 1 | 500 | 2500 | 100 |
| 1 | 1000 | 200 | 200 |
| Totals | 2500 | 3200 | 350 |
| 5 | 100 | 500 | 500 |
| Totals | 100 | 500 | 500 |
| 7 | 100 | 100 | 100 |
| 7 | 200 | 300 | 400 |
| Totals | 300 | 400 | 500 |
+ ---------+--------+---------+-----------+
A brute force method is to do the aggregation and then interleave the results:
select (case when is_base = 1 then to_char(BranchId)
else replace('Total ([BranchId])', '[BranchId]', BranchId)
end) as BranchId, CashIn, CashOut, CardSales
from ((select BranchId, CashIn, CashOut, CardSales, 1 as is_base
from t
) union all
(select BranchId, sum(CashIn), sum(CashOut), sum(CardSales), 0 as is_base
from t
group by BranchId
)
) t
order by t.BranchId, is_base desc;
Here is a db<>fiddle.

How to get value as string in SQL Server

I have a table tbl_Marks:
Std_id | sub_id | term_I | term_2
--------+---------+-----------+-------
std_1 | 1 | 40 | 50
std_1 | 2 | 30 | 40
std_1 | 3 | 20 | 30
std_1 | 4 | 10 | 50
std_2 | 1 | 50 | 50
std_2 | 2 | 50 | 50
std_2 | 3 | 50 | 50
std_2 | 4 | 50 | 50
How can I get result like this:
Std_id | sub_id | term_I | term_2 | total | status | PROMOTION_status
--------+---------+-----------+--------+--------+--------+------------------
std_1 | 1 | 40 | 50 | 90 | PASS | REPEATER
std_1 | 2 | 30 | 40 | 70 | PASS | REPEATER
std_1 | 3 | 20 | 20 | 40 | FAIL | REPEATER
std_1 | 4 | 10 | 50 | 60 | PASS | REPEATER
Note : if total value is less than 50 of any sub_id
std_2 | 1 | 50 | 50 | 100 | PASS | PROMOTED
std_2 | 2 | 50 | 50 | 100 | PASS | PROMOTED
std_2 | 3 | 50 | 50 | 100 | PASS | PROMOTED
std_2 | 4 | 50 | 50 | 100 | PASS | PROMOTED
Note: if total value is greater than 50 or equal of each sub_id
Please help!
Use CASE.
select t.*,
t.term_I + t.term_2 as total,
case when t.term_I + t.term_2 >= 50 then 'pass' else 'fail' end as status,
case when t.std_id = 'std_2' then 'PRMOTED' else 'REAPEATER' end as promotion_status
from tbl_marks t
Following SQL Select statement can help
Instead of SQL CTE expression you can also use subselect statements
But the main trick in this query is using MIN aggregation function with Partition by clause
This helps you to check if there is a lower than 50 mark for that student, if so it makes all records for that student as REPEATER status
;with cte as (
select
*,
term_1 + term_2 as total
from tbl_Marks
)
select
*,
case when (term_1 + term_2) >= 50 then 'PASS' else 'FAIL' end as status,
case when (
min(total) over (partition by Std_id)
) >= 50 then 'PROMOTED' else 'REPEATER' end as PROMOTION_status
from cte
I hope it helps,

Return the row with the value of the previous row within the same group (Oracle Sql)

I have a tabel that looks like this:
|--------+------+---------|------|
| Head | ID | Amount | Rank |
|--------+------+---------|------|
| 1 | 10 | 1000 | 1 |
| 1 | 11 | 1200 | 2 |
| 1 | 12 | 1500 | 3 |
| 2 | 20 | 3400 | 1 |
| 2 | 21 | 3600 | 2 |
| 2 | 22 | 4200 | 3 |
| 2 | 23 | 1700 | 4 |
|--------+------+---------|------|
I want a new column (New_column) that does the following:
|--------+------+---------|------|------------|
| Head | ID | Amount | Rank | New_column |
|--------+------+---------|------|------------|
| 1 | 10 | 1000 | 1 | 1000 |
| 1 | 11 | 1200 | 2 | 1000 |
| 1 | 12 | 1500 | 3 | 1200 |
| 2 | 20 | 3400 | 1 | 3400 |
| 2 | 21 | 3600 | 2 | 3400 |
| 2 | 22 | 4200 | 3 | 3600 |
| 2 | 23 | 1700 | 4 | 4200 |
|--------+------+---------|------|------------|
Within each Head number, if rank is not 1, takes the amount of row within the Head number with Rank number before it (Rank 2 takes the amount of Rank 1 within the same Head and Rank 3 takes the amount of Rank 2 within the same Head and so on...)
I know how to fix it with a For loop in other programming languages but Don't know how to do it with SQL.
I think you basically want lag():
select t.*,
lag(amount, 1, amount) over (partition by head order by rank) as new_column
from t;
The three-argument form of lag() allows you to provide a default value.
You can join the same table(subquery) on rank-1 of derived table.
select t1.*,case when t1.rank=1 then amount else t2.amount new_amount
from your_table t1 left join (select Head,ID,Amount,Rank from your_table) t2
on t1.head=t2.head and t1.rank=t2.rank-1
You can use this update:
UPDATE your_table b
SET New_column = CASE WHEN rank = 1 then Amount
ELSE (select a.Amount FROM your_table a where a.ID = b.ID and a.rank = b.rank-1) END

SQL - Add Column With Count()

I want to simply add a column to the results given below that comes from "select count(*) ... group by possession". So it should still retain the same number of rows, and have this added column. I was told to look into a lateral join but I don't understand how to do it especially in the context of my query which has the CTE
QUERY
select
*
from (
with possession_change as (
select
(lag(possession,1) over (order by id)) as last_possession,
possession,
clock
from plays
where
game_id in (583615)
and league = 3
and period in (0,1)
)
select * from possession_change
) stuff
;
RESULTS
last_possession | possession | clock
-----------------+------------+-------
| 0 | 3600
0 | 0 | 3600
0 | 0 | 3600
0 | 0 | 3600
0 | 1 | 3561
1 | 1 | 3561
1 | 1 | 3561
1 | 1 | 3449
1 | 1 | 3449
1 | 0 | 3396
0 | 0 | 3396
0 | 0 | 3396
DESIRED RESULTS
last_possession | possession | clock | possession_count
-----------------+------------+-------
| 0 | 3600 | 7
0 | 0 | 3600 | 7
0 | 0 | 3600 | 7
0 | 0 | 3600 | 7
0 | 1 | 3561 | 5
1 | 1 | 3561 | 5
1 | 1 | 3561 | 5
1 | 1 | 3449 | 5
1 | 1 | 3449 | 5
1 | 0 | 3396 | 7
0 | 0 | 3396 | 7
0 | 0 | 3396 | 7
You can use count over:
select
lag(possession,1) over (order by id) as last_possession,
possession,
clock,
count(*) over (partition by possession) cnt
from plays
where
game_id in (583615)
and league = 3
and period in (0,1)

Inventory stock calculation query or/and optimization

Remains - all transactions of products
Stock - Stock of products
DimDate - Time table
Calculation of stock:
take the date for which needed calculate stock, example date 20050115 product -1 warehouse - 6
take next month first day example 20050201 and from Stock table take one row with weight, stock, cost for the same product and warehouse.
Sum all transactions in Remains from selected date till month end and subtract from taken row of Stock table
For first day next month I'm using this:
CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime)))-1),DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime))),112)
Example Remains:
| TIMEKEY | PRODUCT | WAREHOUSE | COST | STOCK | WEIGHT |
|----------|---------|-----------|------|-------|--------|
| 20050114 | 1 | 6 | 100 | 5 | 15 |
| 20050118 | 1 | 6 | 200 | 10 | 30 |
| 20050125 | 1 | 6 | -100 | -5 | -15 |
Stock:
| TIMEKEY | PRODUCT | WAREHOUSE | COST | STOCK | WEIGHT |
|----------|---------|-----------|------|-------|--------|
| 20050201 | 1 | 6 | 515 | 55 | 53 |
| 20050301 | 2 | 8 | 20 | 3 | 2 |
| 20050301 | 3 | 9 | 16 | 2 | 3 |
| 20050301 | 4 | 10 | 49 | 8 | 5 |
| 20050401 | 5 | 26 | 59 | 9 | 10 |
Result (my query) calculated stock by day for product 1, warehouse 6 and first month:
| DATEKEY | PRODUCT | WAREHOUSE | WEIGHT | STOCK | COST |
|----------|---------|-----------|--------|--------|--------|
| 20050101 | 1 | 6 | 23 | 45 | 315 |
| 20050102 | 1 | 6 | 23 | 45 | 315 |
| 20050103 | 1 | 6 | 23 | 45 | 315 |
| 20050104 | 1 | 6 | 23 | 45 | 315 |
| 20050105 | 1 | 6 | 23 | 45 | 315 |
| 20050106 | 1 | 6 | 23 | 45 | 315 |
| 20050107 | 1 | 6 | 23 | 45 | 315 |
| 20050108 | 1 | 6 | 23 | 45 | 315 |
| 20050109 | 1 | 6 | 23 | 45 | 315 |
| 20050110 | 1 | 6 | 23 | 45 | 315 |
| 20050111 | 1 | 6 | 23 | 45 | 315 |
| 20050112 | 1 | 6 | 23 | 45 | 315 |
| 20050113 | 1 | 6 | 23 | 45 | 315 |
| 20050114 | 1 | 6 | 38 | 50 | 415 |
| 20050115 | 1 | 6 | 38 | 50 | 415 |
| 20050116 | 1 | 6 | 38 | 50 | 415 |
| 20050117 | 1 | 6 | 38 | 50 | 415 |
| 20050118 | 1 | 6 | 68 | 60 | 615 |
| 20050119 | 1 | 6 | 68 | 60 | 615 |
| 20050120 | 1 | 6 | 68 | 60 | 615 |
| 20050121 | 1 | 6 | 68 | 60 | 615 |
| 20050122 | 1 | 6 | 68 | 60 | 615 |
| 20050123 | 1 | 6 | 68 | 60 | 615 |
| 20050124 | 1 | 6 | 68 | 60 | 615 |
Problem / Question
The stock of product is not ended so I need to show result for full month(or the range I select in where clause), not just till 24 day. IF this immpossible, maybe somebody could help with current query optimization I'm using for calculation stock. I'm Using ms sql 2008r2
SQLFIDDLEExample
SELECT t.DateKey, r.Product, r.Warehouse,
(SELECT [Weight]
FROM Stock
WHERE Timekey = CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime)))-1),DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime))),112)
AND Product = r.Product
AND Warehouse = r.Warehouse)-SUM(r.Weight) AS [Weight],
(SELECT Stock
FROM Stock
WHERE Timekey = CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime)))-1),DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime))),112)
AND Product = r.Product
AND Warehouse = r.Warehouse)-SUM(Stock) as Stock,
(SELECT Cost
FROM Stock
WHERE Timekey = CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime)))-1),DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime))),112)
AND Product = r.Product
AND Warehouse = r.Warehouse)-SUM(Cost) AS Cost
FROM DimDate t, Remains r
WHERE t.DateKey < r.Timekey
AND r.Timekey <= CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime)))-1),DATEADD(mm,1,CAST(convert(varchar,t.DateKey) as datetime))),112)
AND t.DateKey >= 20050101
and r.Product = '1'
and r.Warehouse = 6
GROUP BY t.DateKey, r.Product, r.Warehouse
Hard to tell if it's faster on a small data set, but the following looks to have a much better query plan. The approach is to only join on to the stock table once instead of three times.
with x as (
select
t.DateKey,
r.Product,
r.Warehouse,
sum(r.Weight) as Weight,
sum(Stock) as Stock,
sum(Cost) as Cost,
(t.DateKey / 100
+ Case t.DateKey / 100 % 100 when 12 Then 89 else 1 end)
* 100 + 1 as TimeKey
from
DimDate t,
Remains r
where
t.DateKey < r.Timekey and
r.Timekey <= (
t.DateKey / 100
+ Case t.DateKey / 100 % 100 when 12 Then 89 else 1 end)
* 100 + 1 and
t.DateKey >= 20050101 and
r.Product = 1 and
r.Warehouse = 6
group by
t.DateKey,
r.Product,
r.Warehouse
) select
x.DateKey,
x.Product,
x.Warehouse,
s.Weight - x.Weight as Weight,
s.Stock - x.Stock as Stock,
s.Cost - x.Cost as Cost
From
x
left outer join
Stock s
on s.TimeKey = x.TimeKey and
s.Product = x.Product and
s.Warehouse = x.Warehouse;
Example SQLFiddle
if you have the chance to redesign this schema, bear in mind that the date datatype only uses three bytes, compared to four for an int.
It's probably not contributing much to the overall query cost, but you could store the "start of next month" value as a computed column on the DimDate table. It makes the query a bit easier to read.
create table DimDate (
[DateKey] int,
StartOfNextMonthKey as (
DateKey / 100
+ Case DateKey / 100 % 100 when 12 Then 89 else 1 end
) * 100 + 1 persisted
);
then the query is
with x as (
select
t.DateKey,
r.Product,
r.Warehouse,
sum(r.Weight) AS [Weight],
sum(Stock) as Stock,
sum(Cost) AS Cost,
t.StartOfNextMonthKey
from
DimDate t,
Remains r
where
t.DateKey < r.Timekey AND
r.Timekey <= t.StartOfNextMonthKey and
t.DateKey >= 20050101 and
r.Product = 1 and
r.Warehouse = 6
group by
t.DateKey,
r.Product,
r.Warehouse,
t.StartOfNextMonthKey
) select
x.DateKey,
x.Product,
x.Warehouse,
s.Weight - x.Weight as Weight,
s.Stock - x.Stock as Stock,
s.Cost - x.Cost as Cost
From
x
left outer join
Stock s
on s.TimeKey = x.StartOfNextMonthKey and
s.Product = x.Product and
s.Warehouse = x.Warehouse;
Example SQLFiddle