Get the sum of details table - sql

I need to retrieve a master record together the sum of its details. Here are the definitions of two tables:
Master table (Master)
ID
Name
Example record:
10 Product
Details table (Details)
master_id
type (values can only be red or blue)
quantity
Example records:
10 red 2
10 red 5
10 red 6
10 blue 7
10 blue 9
I need to have such results:
10 Product 13 (sum for red) 16 (sum for blue)
The database is SQL Server 2017. How can I write a SINGLE query without using a stored procedure?
---UPDATE---
Based on the input from Venkataraman R, here is the solution:
SELECT m.id, m.name ,
SUM(CASE when type='red' then quantity end) as redsum,
SUM(CASE when type='blue' then quantity end) as bluesum
from mydetails as t
inner join mymaster as m
on m.id = t.master_id
GROUP BY m.id, m.name

You can use CONCAT function with group by to get results as single statement.
DECLARE #table table(master_id int, typev varchar(10), quantity int)
DECLARE #master table(id int, name varchar(50))
insert into #master values(10, 'Product10')
insert into #table values
(10,'red', 2),
(10,'red', 5),
(10,'red', 6),
(10,'blue', 7),
(10,'blue', 9);
SELECT m.name ,
SUM(CASE when typev='red' then quantity end) as redsum,
SUM(CASE when typev='blue' then quantity end) as bluesum
from #table as t
inner join #master as m
on m.id = t.master_id
GROUP BY m.name
+-----------+--------+---------+
| name | redsum | bluesum |
+-----------+--------+---------+
| Product10 | 13 | 16 |
+-----------+--------+---------+

If I get you correctly then you are looking for pivot, in that case you can use case expressions as following
select
master_id,
sum(case when type = 'red' then quantity end) as sum_of_red,
sum(case when type = 'blue' then quantity end) as sum_of_blue
from yourTable
group by
master_id
If you simply wants total quantity by type then use the following
select
master_id,
type,
sum(quantity) as total_quantity
from yourTable
group by
master_id,
type

Just use a group by
Select master_id, type, sum(quantity)
From details group by master_id, type
And use pivot to show each product in one line
With data_cte as
(Select master_id, type, sum(quantity)
From details group by master_id, type)
Select * from data_cte pivot (
For type in ('red', 'black'))

Related

Retrieve specific value if set, or general if not

I have to write a query which will import some data from table. Table structure is like:
Item_ID
Plant
Price
I have second table with
Item_ID
Plant
Second table is a key, and I have to match rows from first table to get valid price. Seems to be easy, however:
In first table column plant might determinate specific plant, or have value 'ALL'. What I want to do is retrieve price for given plant, if it is set, or get price for value 'ALL' if there is no row for given plant. In other words:
If first.Plant = second.Plant
return price
Else If first.Plant = 'ALL'
return price
Else
return NULL
I can't use simple ON first.Plant = second.Plant OR first.Plant = 'ALL', because there might be two rows: one for given plant and second for rest with value 'ALL'. I need to return only first price in that case. E.g.
Item_ID | Plant | Price
2 | M1 | 10,0
2 | All | 12,0
1 | All | 9,0
In that case for Item_ID = 2 and Plant = M1 the only valid price = 10, but for Item_ID = 2 and Plant = M2 price = 12, and for any Item_ID = 1 price = 9
I hope You understood something after my explanation ;)
Using ROW_Number and a Common Table Expression you can ensure that ROW_NUMBER is partitioned by Item_ID and Plant and ordered such that if there are two rows then 'All' is second. You then simple select the rows with a row number = 1:
Setup
CREATE TABLE #Price
(
Item_ID int,
Plant Varchar(20),
Price Decimal(6,2)
)
CREATE TABLE #Plant
(
Item_ID int,
Plant VARCHAR(20)
)
INSERT INTO #Price
VALUES (2, 'M1', 10.0),
(2, 'All', 12.0),
(1, 'All', 9.0)
INSERT INTO #Plant
VALUES (2, 'M1'),
(2, 'M2'),
(1, 'M1'),
(1, 'M2')
Here's the query for SQL Server >= 2012
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY IIF(PR.Plant = 'All', 1, 0)) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1
And here's a version using CASE which will work for all SQL Server >= 2008 R2
;WITH CTE
AS
(
SELECT PL.Item_ID, PL.Plant, PR.PRice, ROW_NUMBER() OVER (PARTITION BY Pl.Item_ID, Pl.Plant ORDER BY CASE WHEN PR.Plant = 'All' Then 1 Else 0 End) AS RN
FROM #Plant PL
INNER JOIN #Price PR
ON PL.Item_Id = PR.Item_Id AND (PL.Plant = PR.Plant OR PR.Plant = 'ALL')
)
SELECT Item_Id, Plant, Price
FROM CTE
WHERE RN = 1

SQL-Get highest record from group at another table

I have read several answers to related questions but none of them can be applied to this case.
I have a table TableA where several groups are listed, with their score:
GROUP|SCORE
Blue | 0
Green| 0
Red | 0
Orange| 0
On another table TableB, I have the parts of each group and their individual score (status), which can have three different values:
- G (Good)
- A (Average)
- B (Bad)
So tableB is:
GROUP|PART|STATUS
Blue | 3H2| A
Blue | 4NQ| G
Blue | W9X| A
Green| 65D| G
Red | 73F| B
Red | 91G| A
I need to Update the score on TableA in the following way:
If the best status between the parts of the group is G, group score is 3
If the best status between the parts of the group is A, group score is 2
If the best status between the parts of the group is B, group score is 1
I have been a couple of days going around this and I can't find a solution. Thank you guys. Btw, I am using Access 2013.
As I have already mentioned in the comments: Don't store the score redundantly; it is implicit in tableB. And to get your database straight introduce a status table:
STATUS DESCRIPTION SCORE
G Good 3
A Avarage 2
B Bad 1
If you want to select the score for each color group use this query for instance:
select b.colorgroup, max(s.score) as maxscore
from tableb as b
join status as s on s.status = b.status
group by b.colorgroup;
Two alternative ways to write the same query:
select
colorgroup,
(
select max(score)
from status as s
where s.status = b.status
) as maxscore
from tableb as b;
and
select b.colorgroup, s.maxscore
from tableb as b
join
(
select status, max(score) as maxscore
from status
group by status
) as s on s.status = b.status;
(BTW: I called your group colorgroup because GROUP is a reserved name in SQL.)
UPDATE You say you cannot add a table to the database. So you must evaluate the score in the query itself unfortunately. In standard SQL you would use CASE WHEN, which MS Access doesn't feature. MS Access provides IIF instead:
select
colorgroup,
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb
group by colorgroup;
If you even must use the column in tableA and store redundantly, use:
update tablea as a
set score =
(
select
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb as b
where b.colorgroup = a.colorgroup
);
In SQL-Server you could do in following:
QUERY
update a
set a.SCORE = MaxSTATUS
from #a a
join (select GROUP_, MAX(case when b.STATUS_ = 'G' then 3
when b.STATUS_ = 'A' then 2
when b.STATUS_ = 'B' then 1
end) MaxSTATUS
from #b b
group by GROUP_
) b ON a.GROUP_ = b.GROUP_
select * from #a
SAMPLE DATA
CREATE TABLE #a
(
GROUP_ NVARCHAR(60),
SCORE INT
)
INSERT INTO #a VALUES
('Blue',0)
,('Green',0)
,('Red',0)
,('Orange',0)
CREATE TABLE #b
(
GROUP_ NVARCHAR(60),
PART NVARCHAR(60),
STATUS_ NVARCHAR(60),
)
INSERT INTO #b VALUES
('Blue','3H2','A')
,('Blue','4NQ','G')
,('Blue','W9X','A')
,('Green','65D','G')
,('Red','73F','B')
,('Red','91G','A')
OUPUT
GROUP_ SCORE
Blue 3
Green 3
Red 2
Orange 0

How would l write SQL to label quantities until they run out?

I would like to label quantities (in the quantity table) using the labels assigned (see label assignment table) until the quantity goes to 0. Then I know that I am done labeling that particular ID.
label assignment table is as follows:
ID | Label | Quantity
1 aaa 10
1 bbb 20
2 ccc 20
And my quantity table:
ID | Total Quantity
1 60
2 20
And I would like to get the following result:
ID | Label | Quantity
1 aaa 10 (read from reference table, remaining 50)
1 bbb 20 (read from reference table, remaining 30)
1 [NULL] 30 (no label in reference table, remaining 0)
2 ccc 20 (read from reference table, remaining 0)
You can do it with a simple JOIN and UNION operation so as to include 'not covered' quantities:
SELECT la.ID, la.Label, la.Quantity
FROM label_assignment AS la
INNER JOIN quantity AS q ON la.ID = q.ID
UNION
SELECT q.ID, NULL AS Label, q.TotalQuantity - la.TotalQuantity
FROM quantity AS q
INNER JOIN (
SELECT ID, SUM(Quantity) AS TotalQuantity
FROM label_assignment
GROUP BY ID
) AS la ON q.ID = la.ID AND q.TotalQuantity > la.TotalQuantity
Demo here
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT t.Id,
CASE WHEN o.Overflowed = 1 THEN NULL ELSE t.Label END AS Label,
CASE WHEN o.Overflowed = 1 THEN t.QuantityStillNeeded
WHEN t.QuantityStillNeeded < 0 THEN t.Quantity + t.QuantityStillNeeded
ELSE t.Quantity END AS Quantity
FROM (
SELECT p.Id, p.Label, p.Quantity,
MAX(p.Label) OVER (PARTITION BY p.Id) AS LastLabel,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
INNER JOIN (VALUES (0), (1)) o(Overflowed)
ON t.LastLabel = t.Label AND t.QuantityStillNeeded > 0 OR Overflowed = 0
WHERE t.QuantityStillNeeded > -t.Quantity; -- Remove this if you want labels with
-- 0 quantity used, but you'll need to tweak
-- the CASE expression for Quantity
The subquery calculates a set of used up labels and how many items remain afterward. If there is any quantity remaining after the last label, then we need to insert a row in the result set. To do this, I join on a two-element table but the join condition is only true when we are at the last label and there is quantity remaining. This is probably a confusing way to do this, and we could combine the UNION from George's answer with the subquery from mine to avoid this Overflow table.
Here's the changed (and probably preferable) query:
SELECT Id,
Label,
CASE WHEN QuantityStillNeeded < 0 THEN Quantity + QuantityStillNeeded
ELSE Quantity END AS Quantity
FROM (SELECT p.Id, p.Label, p.Quantity,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
WHERE t.QuantityStillNeeded > -t.Quantity
UNION ALL
SELECT q.Id, NULL AS Label, q.TotalQuantity - la.TotalQuantity AS Quantity
FROM #QuantityRequired AS q
INNER JOIN (
SELECT Id, SUM(Quantity) AS TotalQuantity
FROM #PerLabelQuantity
GROUP BY Id) la ON q.ID = la.ID
WHERE q.TotalQuantity > la.TotalQuantity
Simplest answer I think, after getting ideas from the other answers: Just create a "FAKE" label for the missing amount:
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
SELECT *
FROM #PerLabelQuantity
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT *
FROM #QuantityRequired
-- MAKE A FAKE LABEL LET'S CALL IT [NULL] WITH THE AMOUNT THAT IS NOT LABELED
-- i.e. WITH THE REMAINING AMOUNT
-- Probably should be done by copying the original data and the following
-- into a temp table but this is just for proof of concept
INSERT INTO #PerLabelQuantity( Id, Label, Quantity )
SELECT q.ID,
NULL,
ISNULL(q.TotalQuantity - p.TotalQuantityLabeled, q.TotalQuantity)
FROM #QuantityRequired q
LEFT JOIN (SELECT p.ID, SUM(Quantity) AS TotalQuantityLabeled
FROM #PerLabelQuantity p
GROUP BY p.Id) p ON
p.ID = q.ID
AND q.TotalQuantity - p.TotalQuantityLabeled > 0
SELECT *
FROM #PerLabelQuantity p

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle

SQL query getting data

In SQL Server 2000:
hello i have a table with the following structure:
sku brand product_name inventory_count
------ ------ ------------- ---------------
c001 honda honda car 1 3
t002 honda honda truck 1 6
c003 ford ford car 1 7
t004 ford ford truck 1 8
b005 honda honda bike 5 9
b006 ford ford bike 6 18
I'm using the following SQL query
select distinct left(sku,1) from products
this would return the following:
c
t
b
and then ...
c = car
t = truck
b = bike
this works great,
Now I want to get just one product example for each of the categories with the greatest INVENTORY_COUNT
so that it returns the data as:
c, "ford car 1"
t, "ford truck 1"
b, "ford bike 6"
what SQL query would i run to get that data??
i want the item with the greatest INVENTORY_COUNT for each category.. left(sku,1)
thanks!!
You could join the table on itself to filter out the rows with less than maximum inventory:
select left(a.sku,1), max(a.product_name), max(a.inventory_count)
from YourTable a
left join YourTable more_inv
on left(a.sku,1) = left(more_inv.sku,1)
and a.inventory_count < more_inv.inventory_count
where more_inv.sku is null
group by left(a.sku,1)
The WHERE condition on more_inv.sku is null filters out rows that don't have the highest inventory for their one letter category.
Once we're down to rows with the maximum inventory, you can use max() to get the inventory_count (it'll be the same for all rows) and another max() to get one of the products with the highest inventory_count. You could use min() too.
im using the following sql query which works,
SELECT DISTINCT left(field1,1) as cat , MAX(sku) as topproduct FROM products where inventory_count > 0 GROUP BY left(sku,1)
i just need to add in there an ..order by inventory_count
Using SQL Server 2005 you can try this
DECLARe #Table TABLE(
sku VARCHAR(50),
brand VARCHAR(50),
product_name VARCHAR(50),
inventory_count INT
)
INSERT INTO #Table SELECT 'c001', 'honda', 'honda car 1', 3
INSERT INTO #Table SELECT 't002', 'honda', 'honda truck 1', 6
INSERT INTO #Table SELECT 'c003', 'ford', 'ford car 1', 7
INSERT INTO #Table SELECT 't004', 'ford', 'ford truck 1', 8
INSERT INTO #Table SELECT 'b005', 'honda', 'honda bike 5', 9
INSERT INTO #Table SELECT 'b006', 'ford', 'ford bike 6', 18
SELECT LEFT(sku,1),
product_name
FROM (
SELECT *,
ROW_NUMBER() OVER( PARTITION BY LEFT(sku,1) ORDER BY inventory_count DESC) ORDERCOUNT
FROm #Table
) SUB
WHERE ORDERCOUNT = 1
OK Then you can try
SELECT LEFT(sku,1),
*
FROm #Table t INNER JOIN
(
SELECT LEFT(sku,1) c,
MAX(inventory_count) MaxNum
FROM #Table
GROUP BY LEFT(sku,1)
) sub ON LEFT(t.sku,1) = sub.c and t.inventory_count = sub.MaxNum
For mysql:
SELECT LEFT(sku,1), product_name FROM Table1 GROUP BY LEFT(sku,1)
For MS SQL 2005 (maybe also works in 2000?):
SELECT LEFT(sku,1), MAX(product_name) FROM Table1 GROUP BY LEFT(sku,1)
Try this
declare #t table (sku varchar(50),brand varchar(50),product_name varchar(50),inventory_count int)
insert into #t
select 'c001','honda','honda car 1',3 union all
select 't002','honda','honda truck 1',6 union all
select 'c004','ford','ford car 1',7 union all
select 't004','ford','ford truck 1',8 union all
select 'b005','honda','honda bike 5',9 union all
select 'b006','ford','ford bike 6',18
Query:
select
x.s + space(2) + ',' + space(2) + '"' + t.product_name + '"' as [Output]
from #t t
inner join
(
SELECT left(sku,1) as s,MAX(inventory_count) ic from #t
group by left(sku,1)
) x
on x.ic = t.inventory_count
--order by t.inventory_count desc
Output
c , "ford car 1"
t , "ford truck 1"
b , "ford bike 6"
In general, might there not be more than one item with max(inventory_count)?
To get max inventory per cateogry, use a subquery, (syntax will depend on your database):
SELECT LEFT(sku,1) as category, MAX(inventory_count) as c
FROM Table1
GROUP BY LEFT(sku,1)
SORT BY LEFT(sku,1)
This will give you a table of max_inventory by category, thus:
b,18
c,7
t,8
So now you know the max per category. To get matching products, use this result as
a subquery and find all products in the given cateogry that match the given max(inventory_count):
SELECT t1.*
FROM Table1 AS t1,
(SELECT LEFT(sku,1) AS category, MAX(inventory_count) AS c
FROM Table1
GROUP BY LEFT(sku,1)
) AS t2
WHERE LEFT(t1.sku,1) = t2.category AND t2.c = t1.inventory_count
Sorry, the code above may/may not work in your database, but hope you get the idea.
Bill
PS -- probably not helpful, but the table design isn't really helping you here. If you have control over the schema, would help to separate this into multiple tables.