Calculation view for material to material transfer - sql

I have a table Tab1 with two rows
+-------+----------+--------------+----------+--------+
| DOC# | Material | Debit/Credit | Quantity | Amount |
+-------+----------+--------------+----------+--------+
| 12345 | A1 | Credit | 5 | 50 |
| 12345 | B1 | Debit | 5 | 50 |
+-------+----------+--------------+----------+--------+
Desired Result
+-------+---------------+-------------+----------+--------+
| DOC# | From Material | To Material | Quantity | Amount |
+-------+---------------+-------------+----------+--------+
| 12345 | A1 | B1 | 5 | 50 |
+-------+---------------+-------------+----------+--------+
I am working on SAP HANA, writing a calculation view.

You can use conditional aggregation as follows:
Select doc#,
Max(case when debit_credit = 'credit' then material end) as from_material,
Max(case when debit_credit = 'debit' then material end) as to_material,
Quantity, Amount
From t
Group by doc#, quantity, amount

An alternative to aggregation is a self-join. I guess that is easy to understand for beginners. You join the credit rows to the debit rows, as if these were two different tables.
select
doc_number, quantity, amount,
debit.material as from_material,
credit.material as to_material
from (select * from tab1 where credit_debit = 'Credit') as credit
join (select * from tab1 where credit_debit = 'Debit') as debit
using (doc_number, quantity, amount)
order by doc_number, quantity, amount;
It may be that HANA lacks the USING clause. Then the query would become
select
credit.doc_number, credit.quantity, credit.amount,
debit.material as from_material,
credit.material as to_material
from (select * from tab1 where credit_debit = 'Credit') as credit
join (select * from tab1 where credit_debit = 'Debit') as debit
on credit.doc_number = debit.doc_number
and credit.quantity = debit.quantity
and credit.amount = debit.amount
order by credit.doc_number, credit.quantity, credit.amount;

Related

How to create BigQuery this query in retail dataset

I have a table with user retail transactions. It includes sales and cancels. If Qty is positive - it sells, if negative - cancels. I want to attach cancels to the most appropriate sell. So, I have tables likes that:
| CustomerId | StockId | Qty | Date |
|--------------+-----------+-------+------------|
| 1 | 100 | 50 | 2020-01-01 |
| 1 | 100 | -10 | 2020-01-10 |
| 1 | 100 | 60 | 2020-02-10 |
| 1 | 100 | -20 | 2020-02-10 |
| 1 | 100 | 200 | 2020-03-01 |
| 1 | 100 | 10 | 2020-03-05 |
| 1 | 100 | -90 | 2020-03-10 |
User with ID 1 has the following actions: buy 50 -> return 10 -> buy 60 -> return 20 -> buy 200 -> buy 10 - return 90. For each cancel row (with negative Qty) I find the previous row (by Date) with positive Qty and greater than cancel Qty.
So I need to create BigQuery queries to create table likes this:
| CustomerId | StockId | Qty | Date | CancelQty |
|--------------+-----------+-------+------------+-------------|
| 1 | 100 | 50 | 2020-01-01 | -10 |
| 1 | 100 | 60 | 2020-02-10 | -20 |
| 1 | 100 | 200 | 2020-03-01 | -90 |
| 1 | 100 | 10 | 2020-03-05 | 0 |
Does anybody help me with these queries? I have created one candidate query (split cancel and sales, join them, and do some staff for removing), but it works incorrectly in the above case.
I use BigQuery, so any BQ SQL features could be applied.
Any ideas will be helpful.
You can use the following query.
;WITH result AS (
select t1.*,t2.Qty as cQty,t2.Date as Date_t2 from
(select *,ROW_NUMBER() OVER (ORDER BY qty DESC) AS [ROW NUMBER] from Test) t1
join
(select *,ROW_NUMBER() OVER (ORDER BY qty) AS [ROW NUMBER] from Test) t2
on t1.[ROW NUMBER] = t2.[ROW NUMBER]
)
select CustomerId,StockId,Qty,Date,ISNULL(cQty, 0) As CancelQty,Date_t2
from (select CustomerId,StockId,Qty,Date,case
when cQty < 0 then cQty
else NULL
end AS cQty,
case
when cQty < 0 then Date_t2
else NULL
end AS Date_t2 from result) t
where qty > 0
order by cQty desc
result: https://dbfiddle.uk
You can do this as a gaps-and-islands problem. Basically, add a grouping column to the rows based on a cumulative reverse count of negative values. Then within each group, choose the first row where the sum is positive. So:
select t.* (except cancelqty, grp),
(case when min(case when cancelqty + qty >= 0 then date end) over (partition by customerid grp) = date
then cancelqty
else 0
end) as cancelqty
from (select t.*,
min(cancelqty) over (partition by customerid, grp) as cancelqty
from (select t.*,
countif(qty < 0) over (partition by customerid order by date desc) as grp
from transactions t
) t
from t
) t;
Note: This works for the data you have provided. However, there may be complicated scenarios where this does not work. In fact, I don't think there is a simple optimal solution assuming that the returns are not connected to the original sales. I would suggest that you fix the data model so you record where the returns come from.
The below query seems to satisfy the conditions and the output mentioned.The solution is based on mapping the base table (t) and having the corresponding canceled qty row alongside from same table(t1)
First, a self join based on the customer and StockId is done since they need to correspond to the same customer and product.
Additionally, we are bringing in the canceled transactions t1 that happened after the base row in table t t.Dt<=t1.Dt and to ensure this is a negative qty t1.Qty<0 clause is added
Further we cannot attribute the canceled qty if they are less than the Original Qty. Therefore I am checking if the positive is greater than the canceled qty. This is done by adding a '-' sign to the cancel qty so that they can be compared easily. -(t1.Qty)<=t.Qty
After the Join, we are interested only in the positive qty, so adding a where clause to filter the other rows from the base table t with canceled quantities t.Qty>0.
Now we have the table joined to every other canceled qty row which is less than the transaction date. For example, the Qty 50 can have all the canceled qty mapped to it but we are interested only in the immediate one came after. So we first group all the base quantity values and then choose the date of the canceled Qty that came in first in the Having clause condition HAVING IFNULL(t1.dt, '0')=MIN(IFNULL(t1.dt, '0'))
Finally we get the rows we need and we can exclude the last column if required using an outer select query
SELECT t.CustomerId,t.StockId,t.Qty,t.Dt,IFNULL(t1.Qty, 0) CancelQty
,t1.dt dt_t1
FROM tbl t
LEFT JOIN tbl t1 ON t.CustomerId=t1.CustomerId AND
t.StockId=t1.StockId
AND t.Dt<=t1.Dt AND t1.Qty<0 AND -(t1.Qty)<=t.Qty
WHERE t.Qty>0
GROUP BY 1,2,3,4
HAVING IFNULL(t1.dt, '0')=MIN(IFNULL(t1.dt, '0'))
ORDER BY 1,2,4,3
fiddle
Consider below approach
with sales as (
select * from `project.dataset.table` where Qty > 0
), cancels as (
select * from `project.dataset.table` where Qty < 0
)
select any_value(s).*,
ifnull(array_agg(c.Qty order by c.Date limit 1)[offset(0)], 0) as CancelQty
from sales s
left join cancels c
on s.CustomerId = c.CustomerId
and s.StockId = c.StockId
and s.Date <= c.Date
and s.Qty > abs(c.Qty)
group by format('%t', s)
if applied to sample data in your question - output is

Get MAX and MIN in a row SQL

;WITH CTE AS
(
SELECT * FROM
(
SELECT CandidateID, t_Candidate.Name, ISNULL(CAST(AVG(Rate) AS DECIMAL(12,2)),0) AS Rate, t_Ambassadors.Name AS CN
FROM t_Vote INNER JOIN t_Candidate
ON t_Vote.CandidateID = t_Candidate.ID
INNER JOIN t_Ambassadors
ON t_Vote.AmbassadorID = t_Ambassadors.ID
GROUP BY Rate, CandidateID, t_Candidate.Name, t_Ambassadors.Name
)MySrc
PIVOT
(
AVG(Rate)
FOR CN IN ([Jean],[Anna],[Felicia])
)AS nSrc
)SELECT CandidateID, Name, CAST([Jean] AS DECIMAL(12,2)) AS AHH ,CAST([Anna] AS DECIMAL(12,2)) AS MK,CAST([Felicia] AS DECIMAL(12,2)) AS DIL, CAST(([Jean] + [Anna] + [Felicia])/3 AS DECIMAL(12,2)) AS Total
FROM CTE
GROUP BY Cte.CandidateID, cte.Name, cte.[Jean], cte.[Anna], cte.[Felicia]
I have solved my previous problem with the above query. I created a new question because I have new problem. How do I get the MAX and MIN rate in a row?
The following is the result I get from the above query:
| CandidateID | Name | AHH | MK | DIL | Total |
|-------------|------|-------|------|------|-------|
| CID1 | Jay | 7.00 | 3.00 | 3.00 | 4.33 |
| CID2 | Mia | 2.00 | 9.00 | 7.00 | 6.00 |
What I want to achieve is this:
| CandidateID | Name | AHH | MK | DIL | Total |
|-------------|------|-------|------|------|-------|
| CID1 | Jay | 7.00 | 3.00 | 3.00 | 3.00 |
| CID2 | Mia | 2.00 | 9.00 | 7.00 | 7.00 |
So what happened on the 2nd result is that, it removed the Highest and Lowest score/rate from the row and Get the average of remaining rate/score. AHH, MK and DIL are not the only Voters, there are 14 of them, I just took the 3 first to make it short and clearer.
I believe you're looking by something like the following (though I'm using case aggregation rather than a pivot).
Essentially, it does the same thing your query does except that it uses a row number to figure out the highest and lowest and exclude them from the final "total" (in the case of a tie, it'll just select one of them, but you can use RANK() instead of row_number() if you don't want to include tied highest/lowest in the average):
WITH CTE AS
(
SELECT CandidateID,
Name,
CN,
Rate,
Lowest = ROW_NUMBER() OVER (PARTITION BY CandidateID, Name ORDER BY Rate),
Highest = ROW_NUMBER() OVER (PARTITION BY CandidateID, Name ORDER BY Rate DESC)
FROM
(
SELECT CandidateID,
t_Candidate.Name,
CN = t_Ambassadors.Name,
Rate = ISNULL(CAST(AVG(Rate) AS DECIMAL(12,2)),0)
FROM t_Vote
JOIN t_Candidate
ON t_Vote.CandidateID = t_Candidate.ID
JOIN t_Ambassadors
ON t_Vote.AmbassadorID = t_Ambassadors.ID
GROUP BY CandidateID, t_Candidate.Name, t_Ambassadors.Name
) AS T
)
SELECT CandidateID,
Name,
AHH = MAX(CASE WHEN CN = 'Jean' THEN Rate END),
MK = MAX(CASE WHEN CN = 'Anna' THEN Rate END),
DIL = MAX(CASE WHEN CN = 'Felicia' THEN Rate END), -- and so on and so forth for each CN
Total = AVG(CASE WHEN Lowest != 1 AND Highest != 1 THEN Rate END)
FROM CTE
GROUP BY CandidateID, Name;
EDIT: It is possible to do this using PIVOT, but unless I'm mistaken, it becomes a matter of working out the average of the ones that aren't highest and lowest before pivoting, which becomes a bit more convoluted. It's all around easier to use case aggregation, IMO.

How to return all rows with MAX value meeting a condition of another field in SQL?

I have the following costs table:
+--------+------+-----------+
| Year | ID | Amount |
+--------+------+-----------+
| 1960 | 1 | 100 |
| 1960 | 2 | 200 |
| 1960 | 3 | 200 |
| 1960 | 4 | 150 |
| 1961 | 1 | 300 |
| 1961 | 2 | 200 |
| 1961 | 3 | 100 |
| 1961 | 4 | 300 |
+---------+------+----------+
I want all ID’s having the MAX Amount by Year. For example, for 1960, I want rows with ID's 2 and 3. For 1961, I want rows with ID's 1 and 4.
SELECT Year, ID, Amount FROM costs WHERE Amount = (SELECT MAX(Amount) FROM costs);
The above gets me all MAX values across all Years. But I want a condition that only gets me the max Amount values per year. How do I add an condition to only select records with Year = 1960?
Please try this with below query.This is tested. Its working fine.
By clicking on the below link you can see your expected result in live which you want.
SQL Fiddle Live Demo
SELECT
t1.*
FROM
costs t1
WHERE
t1.amount = (
SELECT
MAX(t2.amount)
FROM
costs t2
WHERE
t2. `year` = t1. `year`
);
Try this....It should work
SELECT
*
FROM
costs
WHERE
(YEAR, amount) IN (
SELECT
YEAR,
max(amount)
FROM
costs
GROUP BY
YEAR
);
One option which should run on all major databases is to use a subquery which finds the max amounts for each year to select the records you want:
SELECT c1.*
FROM costs c1
INNER JOIN
(
SELECT Year, MAX(Amount) AS MaxAmount
FROM costs
GROUP BY Year
) c2
ON c1.Year = c2.Year AND
c1.Amount = c2.MaxAmount
Another way to do this would be to use a correlated subquery:
SELECT c1.*
FROM costs c1
WHERE c1.Amount = (SELECT MAX(c2.Amount) FROM costs c2 WHERE c2.Year = c1.Year)
I expect that joining (the first option) would be the fastest method for larger tables, especially if you have proper indices would could be used.
SELECT Year , ID , Amount
FROM #Table T1
JOIN
(
SELECT MAX(Amount) Amount,Year
FROM #Table
GROUP BY Year
) A ON A.Year = T1.Year AND A.Amount = T1.Amount

SQL Display details and count fields through checks

I have created an Oracle database which contains information regarding renting flats/apartments. I am trying to create a SQL script which will, first, count how many students have not paid their first invoice (so there should be an "is not null" check on this field) and, second, display their details.
Here is an example of the data the table is called INVOICE:
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
| INVOICEID | PRICE | PAYMENTMETHOD | FIRSTREMINDER | SECONDREMINDER | RENTID | PAYMENTSTATUS | DATESENT |
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
| 1 | 415 | Visa | 10/FEB/2016 | - | 1 | Paid | 15/MAR/2016 |
| 2 | 600 | Cash | 15/FEB/2016 | - | 2 | Unpaid | 12/MAR/2016 |
| 3 | 750 | Visa | 10/FEB/2016 | 15/MAR/2016 | 1 | Paid | 15/MAR/2016 |
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
Using this data, the SQL statement should return the details for number 2 in the table and count them.
I'd be grateful if anyone could help with this because I have no clue where to start. SQL is not my main programming language.
It seems this should suffice...
To get the count:
select count(*) from invoice where paymentstatus = 'Unpaid'
To get the details for those rows:
select * from invoice where paymentstatus = 'Unpaid'
You mentioned something about an "is not null" check "on this field" - what field are you referring to? Isn't the paymentstatus column sufficient?
The query below enumerates the invoice #'s per RENTID and allows you to select 1st invoices that were unpaid:
select * from (
select *,
row_number() over (partition by rentid order by invoiceid) invoice_number
from mytable
) t where invoice_number = 1 and paymentstatus = 'Unpaid'
If you just want the count, then replace select * with select count(*)
select count(*) from (
select *,
row_number() over (partition by rentid order by invoiceid) invoice_number
from mytable
) t where invoice_number = 1 and paymentstatus = 'Unpaid'
Another way that checks whether secondreminder is null to determine whether an invoice is a 1st invoice
select count(*)
from mytable
where secondreminder is null
and paymentstatus = 'Unpaid'

SQL join from the same table

How do I get grand total for orders made by credit card only from such a table :
order_id | meta_name | meta_value
___________________________________
1 | type | credit
1 | total | 1030.00
...
2 | type | check
2 | total | 930.00
..
3 | type | credit
3 | total | 330.00
what is the best way to describe such select operation if you are to search the Internet for a solution to this problem.
suppose I am MySQL.
You can do this with either join or group by. Here is the join method:
select ttype.order_id, cast(ttotal.meta_value as money)
from table ttype join
table ttotal
on ttype.order_id = ttotal.order_id and
ttype.meta_name = 'type' and
ttype.meta_value = 'credit' and
ttotal.meta_name = 'total';
If yo could have more than one total for an order, then you would still want to aggregate:
select ttype.order_id, sum(cast(ttotal.meta_value as money))
from table ttype join
table ttotal
on ttype.order_id = ttotal.order_id and
ttype.meta_name = 'type' and
ttype.meta_value = 'credit' and
ttotal.meta_name = 'total'
group by ttype.order_id