Many rows to one row result - sql

I hope I'm explaining this well. I'm struggling with this query:
I have this table that is something like this:
InvoiceNum
Amount
Type - where type could be item, shipping or tax.
So what I want returned is one row per invoice: InvoiceNum, ItemAmount, ShippingAmount, TaxAmount.
Here's an example:
Invoicenum Amount Type
1 $32 Item
1 $2 Shipping
1 $1 Tax
I would want returned:
InvoiceNum ItemAmount ShippingAmount TaxAmount
1 $32 $2 $1

You can summarize rows with group by, and you can select specific rows using case:
select InvoiceNum
, sum(case when Type = 'Item' then Amount end) as ItemAmount
, sum(case when Type = 'Shipping' then Amount end) as ShippingAmount
, sum(case when Type = 'Tax' then Amount end) as TaxAmount
from YourTable
group by
InvoiceNum
The case statement returns null by default, and sum ignores nulls.

You can do this with group by and sum tricks (max works too) as #Andomar shows.
Alternatively, Microsoft SQL Server supports syntax for a PIVOT operation that helps a bit in this type of query. You still have to hard-code the column names though.
SELECT InvoiceNum, [Item] AS ItemAmount, [Shipping] AS ShippingAmount, [Tax] AS TaxAmount
FROM
(SELECT InvoiceNum, Amount, Type FROM InvoiceTable ) i
PIVOT
(
MAX(Amount)
FOR Type IN ([Item], [Shipping], [Tax])
) AS pvt;

Related

How to pivot a table in sql and sum the amounts?

I have a table called test_table. This table looks like below
id
type
value
1
tax
10
1
premium
21
1
tax
3
1
correction
4.5
2
premium
15
I would like to "pivot" this table and make it look like below
id
premium
tax
correction
1
21
13 (=10+3)
4.5
2
15
NULL
NULL
create columns by type (premium, tax and correction)
sum the amounts by type and by id
With my basic sql knowledge, I have no idea how to build this query. Can you help me with this?
You may try the following pivot query:
SELECT
id,
SUM(CASE WHEN type = 'premium' THEN value ELSE 0 END) AS premium,
SUM(CASE WHEN type = 'tax' THEN value ELSE 0 END) AS tax
SUM(CASE WHEN type = 'correction' THEN value ELSE 0 END) AS correction
FROM yourTable
GROUP BY id
ORDER BY id;
Note that the above will report zero for those cells having entry in the source table.
In MS Sql Server, the PIVOT syntax should be sufficiant for this.
select *
from (
select id, [type], value
from test_table
) src
pivot (
sum(value)
for [type] in ([premium], [tax], [correction])
) pvt
order by id

Is there a way to join a numerical column to a alphabetical column in sql?

I have a table of data which contains user actions such as 'buy' and 'sell' in a column and a separate column which contains the quantity traded for the movement. Is there a way to connect the two columns that would allow me to filter results where, for example, the quantity of the brought good is greater than the quantity of the sold goods.
Example of table
Action
Quantity
Product
Buy
10
abc
Sell
9
abc
short
11
xyz
cover
11
xyz
Thanks in advance.
There might be way more efficient ways to do this, but you can pretty easily join them together as subqueries.
SELECT
product
FROM
(
SELECT * FROM table WHERE action = 'Buy'
) as bought
JOIN
(
SELECT * FROM table WHERE action = 'Sell'
) as sold
ON
bought.quantity > sold.quantity
AND
bought.product = sold.product
Try something like this:
select product.name as product ,
coalesce( bought.quantity , 0 ) as bought ,
coalesce( sold.quantity , 0 ) as sold ,
coalesce( short.quantity , 0 ) as shorted ,
coalesce( covered.quantity , 0 ) as covered
from ( select distinct
t.Product as name
from my_table t
) as product
left join ( select t.Quantity as quantity
from my_table t
where action = 'Buy'
) as bought on bought.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'Sell'
) sold on sold.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'short'
) shorted on shorted.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'cover'
) covered on covered.product = product.name
That should give you a flat result set with 1 row per product, transforming this:
Action
Quantity
Product
Buy
10
abc
Sell
9
abc
short
11
xyz
cover
11
xyz
Into this:
product
bought
sold
shorted
covered
abc
10
9
0
0
xyz
0
0
11
11
Once you have flattened your table, you can filter it however you like with a simple where clause, for instance:
select product.name as product ,
coalesce( bought.quantity , 0 ) as bought ,
coalesce( sold.quantity , 0 ) as sold ,
coalesce( short.quantity , 0 ) as shorted ,
coalesce( covered.quantity , 0 ) as covered
from ( select distinct
t.Product as name
from my_table t
) as product
left join ( select t.Quantity as quantity
from my_table t
where action = 'Buy'
) as bought on bought.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'Sell'
) sold on sold.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'short'
) shorted on shorted.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'cover'
) covered on covered.product = product.name
where bought.quantity > sold.quantity
OR short.quantity > cover.quantity
Another way to get to [arguably] the same result is via group by (but much depends on the cardinality of the data):
select t.product as product,
sum(case t.action when 'Buy' then t.Quantity else 0 end) as bought,
sum(case t.action when 'Sold' then t.Quantity else 0 end) as sold,
sum(case t.action when 'short' then t.Quantity else 0 end) as shorted,
sum(case t.action when 'cover' then t.Quantity else 0 end) as covered
from my_table t
group by t.product
having sum(case t.action when 'Buy' then t.Quantity else 0 end)
> sum(case t.action when 'Sold' then t.Quantity else 0 end)
OR sum(case t.action when 'short' then t.Quantity else 0 end)
> sum(case t.action when 'cover' then t.Quantity else 0 end)
You'll note that with the group by, the filter moves from the where clause to the having clause. That's because the conceptual order of operations for a select statement is
Compute the full, cartesian join of all tables in the from clause.
Apply the join criteria to filter the results set.
Filter that by the criteria in the where clause.
If a group by clause exists, reduce the results set to to one row for each distinct combination of values in the grouping columns and compute the value of any aggregate functions.
Filter that summary result set by the criteria in the having clause.
Finally, if an order by clause exists, order the results set accordingly [without an order by clause, order is not guaranteed, even between 2 subsequent executions of the same exact select statement.]
Some assumptions about your table structure: each product has exactly 4 rows, one for each action, never more or less. You have no time or actionId.
This would get you a view of the data that will be more intuitive for you to work from. If you're still in the design stages, I'd consider changing this table to be more like this anyway, OR update the table to have one row per action (per sale of that product, rather than for all sales of that product).
SELECT
product, action,
buy.quantity AS bought, sell.quantity AS sold,
short.quantity AS shorts, cover.quantity AS covers
FROM
table_name AS buy
JOIN
table_name AS sell ON buy.product = sell.product
JOIN
table_name AS short ON buy.product = short.product
JOIN
table_name AS cover ON buy.product = cover.product
WHERE
buy.action = 'Buy'
AND sell.action = 'Sell'
AND short.action = 'short'
AND cover.action = 'cover';
If you restructure your table this way, or use this as a base query (or encode it as a VIEW), it should be intuitive for you to do the things you ask:
SELECT product
FROM (subquery) AS summary
WHERE bought > sold;
If your table is actually one row per action (as in, one row every time you sell) then you will need to do more work.

How to pivot values into two columns using a CASE statement

I am trying to get two new columns (stock type in this case) and their respective quantities. I have tried to use PIVOT but it seems rather limited in SQL.
Tried to use PIVOT
This is part of a larger query but this is the piece I would like to have return as two columns - one for stock type 'A' and one for ' ' - blank. As it is now this returns two rows - one for each stock type.
SELECT MATERIAL,
CASE
WHEN STOCK_TYPE = 'A'
THEN 'UNCOVERED QTY'
ELSE 'BLANK QTY'
END AS [STOCK TYPE],
SUM(QUANTITY) AS 'QUANITTY'
FROM VW_MRP_ALLOCATION
WHERE STOCK_TYPE IN ('A','')
AND MATERIAL = '011040'
GROUP BY STOCK_TYPE,
MATERIAL
This returns:
MATERIAL STOCK TYPE QUANITTY
------------------ ------------- ---------------------------------------
011040 BLANK QTY 67
011040 UNCOVERED QTY 1301
(2 rows affected)
I would like to return one row for the material with two columns - one for 'Uncovered Quantity' and one for ' Blank Quantity'.
Just use conditional aggregation:
SELECT MATERIAL,
SUM(CASE WHEN STOCK_TYPE = 'A' THEN QUANTITY END) as uncovered_qty,
SUM(CASE WHEN STOCK_TYPE <> 'A' THEN QUANTITY END) as blank_qty
FROM VW_MRP_ALLOCATION
WHERE STOCK_TYPE IN ('A', '') AND MATERIAL = '011040'
GROUP BY MATERIAL;

splitting the values in one column and a set of rows evenly and proportionally among other rows

I know, I confused you.
I have this data:
For the three items that are NULL, I need to:
Add them (1828.94 + 772.90 + 0.00).
Split this total among each ItemID evenly and proportionally based on quantity for each.
Note that there are ItemIDs that are the same but this is OK.
Basically the end result will be the same columns without the 3 rows that have ItemID = NULL and the Amount column will be increased by some because I'm splitting the amount among the ItemIDs.
I'm having a really hard time doing this without having to do a bunch of loops.
Can anyone give me a hand?
You can get the apportioned amount with this query:
select t.*,
x.AmountToSplit * t.qty / x.TotalQty as AmountToAdd
from t cross join
(select sum(case when itemId is null then amount end) as AmountToSplit,
sum(case when itemId is not null then Qty end) as TotalQty
from t
) x
where t.itemId is not null;
If you actually want to update the amounts, then use this as an updatable CTE:
with toupdate as (
select t.*,
x.AmountToSplit * t.qty / x.TotalQty as AmountToAdd
from t cross join
(select sum(case when itemId is null then amount end) as AmountToSplit,
sum(case when itemId is not null then Qty end) as TotalQty
from t
) x
where t.itemId is not null
)
update toupdate
set Amount = Amount + AmountToAdd;
Would this work for what you're trying to do?
DECLARE #NullAmounts MONEY,
#RowCount INT
SELECT #NullAmounts = SUM(CASE WHEN ItemID IS NULL THEN Amount ELSE 0 END),
#RowCount = COUNT(*)
FROM Table
UPDATE Table
SET Amount = Amount + (#NullAmounts / #RowCount)
WHERE
ItemID IS NOT NULL
Of course, after you've run the update, you can DELETE the rows so you don't have them return in a SELECT statement.
DELETE Table
WHERE ItemID IS NULL

sql query: subtract from results the corresponding USD, YEN value which has Type='r'

I need help with a query. Consider the following table:
I need to select first the sum of each Code from table. I am doing it with simple sum and group by statement. Then I have to subtract the results from each code sum where type='r'
1) Say for first part of query, we will get 2 rows from SUM (one with total USD and one with total YEN)
2) Now I need to subtract from these results the corresponding USD, YEN value which has Type='r'
I have to do it inside SQL and not a stored procedure.
Why not use a WHERE statement to say WHERE Type != 'r' so that those values never even get added to sum in the first place...
SELECT `Code`, SUM(`Amount`) AS `Total`
FROM `Table`
WHERE `Type` != 'r'
GROUP
BY `Code`;
Something like that.
select code, l.amount - r.amount
from
(select code, sum(amount) as amount from my_table group by code) l
left join (select code, sum(amount) as amount from my_table where type = 'r' group by code) r
on l.code = r.code
You can do this in a single, simple query:
select
code,
sum(case when type = 'r' then (-1 * amount) else amount end) as sum
from
yourtable
group by
code
Basically, you're changing the sign of the rows that have type = 'r', so when you sum all rows for a particular code you'll get the correct answer.
Does it have to be a single query?
I'd say SUM the total, then SUM the subcategory where Type='r', then subtract one from the other.
You could do this in one line of SQL, but I'm pretty sure it would be either joining the table with itself or using a subquery. Either way, it's doing the same amount of work as the above.
Try:
select code,
sum(amount) gross_total,
sum(case when type = 'r' then amount else 0 end) type_r_total,
sum(case when type != 'r' then amount else 0 end) net_total
from yourtable
group by code;
to see the overall totals, type R only totals and non-type R totals for each currency on one row per currency, in a single pass.