How to extract the balance qty - sql

I hv a table like this:-
TABLE A
--------------------------------
Date | Serial | Qty
--------------------------------
20110101 ABC 1
20110102 ABC -1
20110105 ABC 1
20110106 ABC 1
20110108 ABC -1
I need to know what's the balance qty of ABC (based on FIFO) using SQL & the final output will look something like this:-
Date Serial Qty
20110106 ABC 1
Thanks.

You just want to pick out one record from the table? Like this?
SELECT * FROM your_table WHERE Date = '20110106' AND Serial = 'ABC'

I think you want something like
SELECT serial, max(date), 1 qty, sum( qty*size ) size
FROM tableA
GROUP BY serial
If you can have a negative final output, whatever database engine you're using should have something equivalent to Oracle's SIGN and ABS functions
SELECT serial, max(date), sign( sum( qty*size ) ) qty, abs( sum( qty*size ) ) size
FROM tableA
GROUP BY serial
Note that I'm guessing that you want the total remaining quantity as of the last day and that you get this by netting the positive quantities against the negative quantities for all the rows in the table for a particular SERIAL. I think this is close but I'm not sure how you're getting the DATE value

SELECT MAX(date) AS date, serial, ABS(SUM(qty * size)) AS size, SIGN(SUM(qty * size)) AS qty
FROM your_table
GROUP BY serial

something like this:
select Serial, sum(qty) as Total, Size from table_a group by Serial, Size
would give you:
TABLE A
Serial Total Size
ABC 0 23
ABC 0 22
ABC 1 24
you can, of course, add a where clause to restrict the query further:
select Serial, sum(qty) as Total, Size from table_a where Serial='ABC' and Size=24 group by Serial, Size
would give you information for Serial ABC with Size 24, only:
Serial Total Size
ABC 1 24
or you can filter out anything with Total of zero using a having clause:
select Serial, sum(qty) as Total, Size from table_a group by Serial, Size having sum(qty) <> 0
Serial Total Size
ABC 1 24

Related

SQL Server : percentage calculation with new records as output

I have the below table as an output of a SQL query
ID Car Type Units Sold
---------------------------
1 Sedan 250
2 SUV 125
3 Total 375
I want a SQL query / procedure to produce below output
ID Car Type Units Sold
--------------------------
1 Sedan 250
2 SUV 125
3 Total 375
4 Sedan_Pct 66.67 (250/375)
5 SUV_Pct 33.33 (125/375)
Please note that Car Type will be increased in future and I want the percentage of each car type which should be appended to current table as '_Pct'.
Typically we might expect to see the percentages as a separate column, not as separate rows. That being said, we can generate the output you want using grouping sets in SQL Server:
WITH cte AS (
SELECT ID, CarType, SUM (UnitsSold) AS UnitsSold
FROM yourTable
GROUP BY
GROUPING SETS((ID, CarType), (CarType), ())
)
SELECT
ID,
COALECSE(CarType, 'Total') AS CarType,
CASE WHEN ID IS NOT NULL OR CarType IS NULL
THEN UnitsSold
ELSE 100.0 * UnitsSold /
SUM(CASE WHEN ID IS NOT NULL THEN UnitsSold END) OVER () END AS PctUnitsSold
FROM cte
ORDER BY
ID DESC,
CASE WHEN CarType IS NULL THEN 0 ELSE 0 END,
CarType;
Demo
A simpler solution will be using Union
SELECT CarType, UnitSold FROM Car
UNION
SELECT 'Total' CarType, SUM(UnitSold) UnitSold FROM Car
UNION
SELECT CarType + '_Pct' AS CarType, UnitSold / (SELECT SUM(UnitSold) FROM Car) * 100 AS UnitSold FROM Car
Might not be ideal in the long run
query for mysql server
use union all in view or stored procedure -
declare #totalunitsold numeric(15,0);
set #totalunitsold = (select unitsold from car where cartype='total')
select cartype,unitsold from car
union all
select cartype + '_pct', (unitsold/#totalunitsold) as pct from car
this may help you
SELECT CarType+'_Pct', UnitSold/TotalSale*100
FROM Car cross join (select sum(UnitSold) TotalSale from Car) X
you can union that with your Table
Don't do it! Just add an additional column, not new rows:
select t.*,
t.units_sold * 100.0 / sum(case when t.car_type = 'Total' then units_sold end) over () as ratio
from (<your query here>) t;
One fundamental reason why you want a different column is because ratio has a different type from units_sold. Everything in a column (even in a result set) should be a similar attribute.

How to return the correspondant date of a Maximum value

I have a table MyTable like this:
Date Qty Price
2017-07-01 2 5.00
2017-07-08 3 4.00
2017-08-08 1 6.00
If I do a query to get the Maximum total cost like this:
SELECT max(Qty * Price) FROM MyTable
I get 12.00 which is correct.
How can I have the corresponding Date which is 2017-07-08?
In case of several equal Maximum values, I would like to have the smallest (oldest) date.
create table MyTable (date datetime, qty int, price int)
go
insert into MyTable(date,qty,price)
values
('2017-07-01', 2, 5.00),
('2017-07-08', 3, 4.00),
('2017-08-08', 1, 6.00);
with q as
(
select *, row_number() over (order by qty*price desc) rn
from MyTable
)
select date, qty, price
from q
where rn = 1;
outputs
date qty price
----------------------- ----------- -----------
2017-07-08 00:00:00.000 3 4
(1 row(s) affected)
If you need exactly one row with max Qty * Price, oldest day
SELECT TOP(1) *
FROM MyTable
ORDER BY Qty * Price DESC, [Date];
If you need all the rows with the same max Qty * Price value
SELECT TOP(1) WITH TIES *
FROM MyTable
ORDER BY Qty * Price DESC;
Just for the fun of it...
SELECT
[Date] = CAST(SUBSTRING(mv.MaxBin, 9, 8) AS DATE),
Qty = CAST(SUBSTRING(mv.MaxBin, 17, 4) AS INT),
Price = CAST(SUBSTRING(mv.MaxBin, 21, 8) AS MONEY)
FROM (
SELECT
MaxBin = MAX(CAST(td.Qty * td.Price AS BINARY(8)) + CAST(td.Date AS BINARY(8)) + CAST(td.Qty AS BINARY(4)) + CAST(td.Price AS BINARY(8)))
FROM
#TestData td
) mv;
Edit: How and why this works...
As you notes in the opening post, “SELECT max(Qty * Price) FROM MyTable” gets you the correct value but that doesn’t help you get the remaining columns from that row.
So the idea is to do the calculation, and then concatenate the other column values to the calculated results so that we can get all the desired columns using the previous logic [MAX(Qty * Price).
Casting things to fixed length binary values allows us to do just that. We know that Qty * Price = ToalPrice or in terms of data types INT * MONEY = MONEY, which is an 8 byte data type… So I know that I can cast any $ amount as a BINARY(8) and know 1) that I’ll always be able to convert it back to money later, 2) the binary value is EXACTLY 8 characters long and 3) I can sort the binary as I would the original value.
Same goes for Date which is a date data type which is 3 bytes long (Yea, I used 8 instead of 3… I’m used to the DATETIME type… my bad on that one)… and so on, with the other columns.
So, by concatenating the binary values together, I can table the MAX of those values, just like you did with max(Qty * Price) but now I have the entire row of data. Now all that has to be done if break the concatenated binary value back into its constituent parts (which is easy because we know exactly how long each section is) using the SUBSTRING function and the cast them back into their original data types.

SQL aggregate rows with same id , specific value in secondary column

I'm looking to filter out rows in the database (PostgreSQL) if one of the values in the status column occurs. The idea is to sum the amount column if the unique reference only has a status equals to 1. The query should not SELECT the reference at all if it has also a status of 2 or any other status for that matter. status refers to the state of the transaction.
Current data table:
reference | amount | status
1 100 1
2 120 1
2 -120 2
3 200 1
3 -200 2
4 450 1
Result:
amount | status
550 1
I've simplified the data example but I think it gives a good idea of what I'm looking for.
I'm unsuccessful in selecting only references that only have status 1.
I've tried sub-queries, using the HAVING clause and other methods without success.
Thanks
Here's a way using not exists to sum all rows where the status is 1 and other rows with the same reference and a non 1 status do not exist.
select sum(amount) from mytable t1
where status = 1
and not exists (
select 1 from mytable t2
where t2.reference = t1.reference
and t2.status <> 1
)
SELECT SUM(amount)
FROM table
WHERE reference NOT IN (
SELECT reference
FROM table
WHERE status<>1
)
The subquery SELECTs all references that must be excluded, then the main query sums everything except them
select sum (amount) as amount
from (
select sum(amount) as amount
from t
group by reference
having not bool_or(status <> 1)
) s;
amount
--------
550
You could use windowed functions to count occurences of status different than 1 per each group:
SELECT SUM(amount) AS amount
FROM (SELECT *,COUNT(*) FILTER(WHERE status<>1) OVER(PARTITION BY reference) cnt
FROM tc) AS sub
WHERE cnt = 0;
Rextester Demo

SQL query Splitting a column into Multiple rows divide by percentage

How to get percentage of a column and then inserting it as rows
Col1 item TotalAmount**
1 ABC 5558767.82
2 ABC 4747605.5
3 ABC 667377.69
4 ABC 3844204
6 CTB 100
7 CTB 500.52
I need to create a new column percentage for each item which is I have done as :-
Select item, (totalAmount/select sum(totalAmount) from table1) as Percentage
From table1
Group by item
Col1 item TotalAmount percentage
1 ABC 5558767.82 38
2 ABC 4747605.5 32
3 ABC 667377.69 5
4 ABC 3844204 26
6 CTB 100 17
7 CTB 500.52 83
Now, the complex part I have to calculate another amount by multiplying this percentage to an amount from another table say table2
ii) update the Total amount column by spilt the total amount column of table 1 into 2 rows – 1st row of the new Calculate PledgeAmount and 2nd row – (totalAmount – PledgeAmount)
*Select t1.percentage * t2.new amount as [PledgeAmount]
From table 1 join table2 where t1.item=t2.item*
. e.g. for col1 Amount of 5558767.82 will split into two rows.
Final Result sample for :-
Col1 item TotalAmount Type
1 ABC 363700.00 Pledge
1 ABC 5195067.82 Unpledge
....
I am using Temporary table to do calculations.
One of the way I think is to calculate the Pledged and Unpledged amount as new column and Pivot it but its huge table with hundreds of columns it will not perform fast.
Any other efficient way?
You can use a windowing function to solve this problem -- first in a sub-query calculate the total and then in the main query the percent:
Select *, (totalAmount/total_for_item)*100 as percent_of_total
from (
SELECT t.*,
SUM(totalAmount) OVER (PARTITION BY item) as total_for_item
FROM table t
) sub
First, let's get the total amount per item:
SELECT item, SUM( totalAmount ) as sumTotal
INTO #totalperitem
FROM table1
GROUP BY item
Now it's easy to get to the percentages:
SELECT t1.Col1,
t1.item,
t1.totalAmount,
t1.totalAmount/tpi.sumTotal*100 AS percentage
FROM table1 t1
INNER JOIN #totalperitem tpi on ...
Tricky part: Separate rows with/without match in table2. Can be done with a WHERE NOT EXISTS, or, my preference, with a single outer join:
SELECT t1.item,
CASE WHEN tpledged.item IS NULL
THEN "Unpledged"
ELSE "Pledged"
END,
SUM( t1.totalAmount ) AS amount
FROM table1 t1
LEFT OUTER JOIN table2 tpledged ON t1. ... = tpledged. ...
GROUP BY t1.item,
CASE WHEN tpledged.item IS NULL
THEN "Unpledged"
ELSE "Pledged"
END
The basic trick is to create an artificial column from the presence/absence of records in table2 and to also group by that artificial column.

Joining onto a table that doesn't have ranges, but requires ranges

Trying to find the best way to write this SQL statement.
I have a customer table that has the internal credit score of that customer. Then i have another table with definitions of that credit score. I would like to join these tables together, but the second table doesn't have any way to link it easily.
The score of the customer is an integer between 1-999, and the definition table has these columns:
Score
Description
And these rows:
60 LOW
99 MED
999 HIGH
So basically if a customer has a score between 1 and 60 they are low, 61-99 they are med, and 100-999 they are high.
I can't really INNER JOIN these, because it would only join them IF the score was 60, 99, or 999, and that would exclude anyone else with those scores.
I don't want to do a case statement with the static numbers, because our scores may change in the future and I don't want to have to update my initial query when/if they do. I also cannot create any tables or functions to do this- I need to create a SQL statement to do it for me.
EDIT:
A coworker said this would work, but its a little crazy. I'm thinking there has to be a better way:
SELECT
internal_credit_score
(
SELECT
credit_score_short_desc
FROM
cf_internal_credit_score
WHERE
internal_credit_score = (
SELECT
max(credit.internal_credit_score)
FROM
cf_internal_credit_score credit
WHERE
cs.internal_credit_score <= credit.internal_credit_score
AND credit.internal_credit_score <= (
SELECT
min(credit2.internal_credit_score)
FROM
cf_internal_credit_score credit2
WHERE
cs.internal_credit_score <= credit2.internal_credit_score
)
)
)
FROM
customer_statements cs
try this, change your table to contain the range of the scores:
ScoreTable
-------------
LowScore int
HighScore int
ScoreDescription string
data values
LowScore HighScore ScoreDescription
-------- --------- ----------------
1 60 Low
61 99 Med
100 999 High
query:
Select
.... , Score.ScoreDescription
FROM YourTable
INNER JOIN Score ON YourTable.Score>=Score.LowScore
AND YourTable.Score<=Score.HighScore
WHERE ...
Assuming you table is named CreditTable, this is what you want:
select * from
(
select Description, Score
from CreditTable
where Score > 80 /*client's credit*/
order by Score
)
where rownum = 1
Also, make sure your high score reference value is 1000, even though client's highest score possible is 999.
Update
The above SQL gives you the credit record for a given value. If you want to join with, say, Clients table, you'd do something like this:
select
c.Name,
c.Score,
(select Description from
(select Description from CreditTable where Score > c.Score order by Score)
where rownum = 1)
from clients c
I know this is a sub-select that executed for each returning row, but then again, CreditTable is ridiculously small and there will be no significant performance loss because of the the sub-select usage.
You can use analytic functions to convert the data in your score description table to ranges (I assume that you meant that 100-999 should map to 'HIGH', not 99-999).
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 60 score, 'Low' description from dual union all
3 select 99, 'Med' from dual union all
4 select 999, 'High' from dual
5 )
6 select description,
7 nvl(lag(score) over (order by score),0) + 1 low_range,
8 score high_range
9* from x
SQL> /
DESC LOW_RANGE HIGH_RANGE
---- ---------- ----------
Low 1 60
Med 61 99
High 100 999
You can then join this to your CUSTOMER table with something like
SELECT c.*,
sd.*
FROM customer c,
(select description,
nvl(lag(score) over (order by score),0) + 1 low_range,
score high_range
from score_description) sd
WHERE c.credit_score BETWEEN sd.low_range AND sd.high_range