How to update USING sum and subquery - sql

I have a SQL Server table similar to this:
InkitemNo
CapacityUnit
NewInk
OldInk
ReturnInk
ProdQty
Description
UsedInk
204
Machine1
5
2
0
4000
Next
?
223
machine2
4
3
1
8000
NULL
?
204
Machine2
0
0
0
5000
Next
?
224
Machine2
4
0
2
3000
Next
?
I'm trying to write a query with this formula:
Example to get 1 row used ink
(5 + 2 -2 )* 4000/ 12000 = 1,67
to get 2 row used ink
(4 + 3 - 1) = 6
to get 3 row usedink
(5 + 2 - 2) * 5000 / 12000 = 2,08
to get 4 row usedink
(5 + 2 - 2) * 3000 / 12000 = 1,25
(NewInk + OldInk - ReturnInk) * ProdQty / Sum(ProdQty)
This formula used when the criteria is
CapacityUnit & InkItemNo is same
Description is not NULL
To get the result of used ink, I used this query
update InkEstimationSave =
(NewInk + OldInk - ReturnInk) * ProdQty / Sum(ProdQty]
but it does not work.

Based on your logic query you are looking for is
fiddle link
;with cte as
(
select *, SUM(ProdQty) OVER (partition by InkItemNo, Capacityunit) as denom
from yourtable
)
update cte
set UsedInk =
(newInk+OldInk - ReturnInk) * ProdQty
/denom
*1.00
where Description is NOT NULL
select * from Yourtable

Related

What is the best way to duplicate rows based on multiple columns?

I have the following table below:
ID Start_Repeat_1 End_Repeat_1 Start_Repeat_2 End_Repeat_2
A 3 7 2 5
B 1 4 2 5
My goal is to duplicate "A" 5 times and "B" 4 times with the output below
ID Repeat_1 Repeat_2
A 3 2
A 4 3
A 5 4
A 6 5
A 7 NULL
B 1 2
B 2 3
B 3 4
B 4 5
The logic is that "A" needs to be duplicated with numbers between 3 and 7 in one column and numbers between 2 and 5 in another column so it needs to be duplicated at least 5 times.
The version below would also do. The order between the two columns does not matter.
ID Repeat_1 Repeat_2
A 3 2
A 7 5
A 6 3
A 5 NULL
A 4 4
Can someone help me with this using SQL Server 2018?
The dataset size is about 10,000 rows and each row is duplicated at most 10 times with a total of 10 columns like this
ID Repeat_1 Repeat_2 Repeat_3 Repeat_4 Repeat_10
A 3 2 1 1 1
B 7 5 1 1 1
You need to make use of a tally table. In the following solution, I have use a recursive cte to generate one.
Alternatively you can use a recursive cte to generate one on the fly
-- Tally Table
create table tally
(
n int
)
-- generate 1000 numbers for tally table
with cte as
(
select n = 0
union all
select n = n + 1
from cte
where n < 1000
)
insert into tally (n)
select n
from cte
The cross apply is to find the maximum difference between the repeat_1 or 2 etc.
-- The query
select t.ID,
Repeat_1 = case when n.n <= (End_Repeat_1 - Start_Repeat_1)
then t.Start_Repeat_1 + n.n
end,
Repeat_2 = case when n.n <= (End_Repeat_2 - Start_Repeat_2)
then t.Start_Repeat_2 + n.n
end,
Repeat_3 = case when n.n <= (End_Repeat_3 - Start_Repeat_3)
then t.Start_Repeat_3 + n.n
end
from tbl t
cross apply
(
select m = max(d)
from (
values
(End_Repeat_1 - Start_Repeat_1 + 1),
(End_Repeat_2 - Start_Repeat_2 + 1),
(End_Repeat_3 - Start_Repeat_3 + 1)
) n (d)
) m
inner join tally n on n.n >= 0
and n.n < m.m
dbfiddel demo
you can use recursive cte :
with cte as (
select * from test
union all
select Id
, case when Start_Repeat_1 + 1 > End_Repeat_1 then null else Start_Repeat_1 + 1 end
,End_Repeat_1
,case when Start_Repeat_2+ 1 > End_Repeat_2 then null else Start_Repeat_2+ 1 end
,End_Repeat_2
from cte
where Start_Repeat_1 <= End_Repeat_1 and Start_Repeat_2 <= End_Repeat_2
)
select ID,Start_Repeat_1,Start_Repeat_2
from cte
where coalesce(Start_Repeat_1,Start_Repeat_2) is not null
order by ID
ID | Start_Repeat_1 | Start_Repeat_2
:- | -------------: | -------------:
A | 3 | 2
A | 4 | 3
A | 5 | 4
A | 6 | 5
A | 7 | null
B | 1 | 2
B | 2 | 3
B | 3 | 4
B | 4 | 5
db<>fiddle here

Select 4 rows at random with minimum values in sqlite

I have the following table:
addition
question answer box
1 + 1 2 0
1 + 2 3 2
1 + 3 4 1
1 + 4 5 2
1 + 5 6 3
1 + 6 7 1
I'm trying to select 4 rows with a minimum box value:
SELECT *, MIN(box) FROM {table} ORDER BY RANDOM() LIMIT 4;
However, it returns only one row.
Sounds like you want a cartesian product (CROSS JOIN) of two tables: the first table being what you presented above, and the second being the minimum value for column box.
Try this
SELECT * FROM {table}
CROSS JOIN
(SELECT MIN(box) from {table})
ORDER BY RANDOM() LIMIT 4;
Notice the subquery in the second half of the CROSS JOIN.

Update next record based on last updated record

I'm struggling to find a solution to update the calculation in the same table based on last updated records order by select column.
I have tried LEAD & LAG, but can't give complete solution of my problem.
Here is my table:
ID PID QTY INVQTY **Desired Result** Calculation
1 1 2 112 110 (112 - 2)
2 1 2 112 108 (110 - 2)
3 1 1 112 107 (108 - 1)
4 1 1 112 106 (107 - 1)
5 1 4 112 102 (106 - 4)
6 1 2 112 100 (102 - 2)
7 1 12 112 88 (100 - 12)
8 1 5 112 83 (88 - 5)
9 2 1 2 1 (2 - 1)
10 2 2 2 -1 (1 - 2)
11 2 3 2 -4 (-1 - 3)
I tried below query but didn't succeed.
select *,
(LAG(a.invqty - a.Qty, 0) OVER (PARTITION BY a.pid ORDER BY a.id)) - (LAG(a.Qty, 1, 0) OVER (PARTITION BY a.pid ORDER BY a.id))
from #RunTotalTestData a
order by a.pid, a.id
I can use a while loop, but there are more than 50K records. I want to do it in the fastest possible way.
Hmmm . . . If you want a select, then you want a cumulative sum:
select rttd.*,
(invqty -
sum(qty) over (partition by pid order by id)
) as desired_result
from #RunTotalTestData rttd;
If you actually want an update (as the title to the question suggests), you can use this in an updatable CTE>

SQL. How to combine two records with the same ID into one line based on value's in a column

SQL Server 2012: how to combine two records with the same ID (TransportOrder) into one line based on value's in a column (PalletType)?
Example: order 678 has two lines with 1 europallet en 3 BetweenEuropallet. So de order takes only 1 TransportEuropalletPlace.
The output should be in the case of order 678, one line, telling there is total 4 europallets (sum 1 + 3 from two lines) and 1 TransportEuropalletPlace (SUM 1 + 0)
How to achieve this with SQL query?
Original output:
TransportOrder PalletType Quantity TransportEuropalletPlace
--------------------------------------------------------------------------
123 Minipallet 1 0.5
345 Europallet 1 1
678 Europallet 1 1
678 BetweenEuropallet 3 0
900 Europallet 2 2
Output needed for order 678:
TransportOrder PalletType Quantity TransportEuropalletPlace
--------------------------------------------------------------------------
123 Minipallet 1 0.5
345 Europallet 1 1
678 Europallet 4 1
900 Europallet 2 2
This should get you started:
SELECT
TransportOrder,
SUM(ISNULL(Europallet,0) + ISNULL(BetweenEuropallet, 0)) as 'Pallets'
FROM Table_Name
GROUP BY TransportOrder
You should group by the column TransportOrder, as in:
select
transportorder,
max(pallettype) as pallettype,
sum(quantity) as quantity,
sum(transporteuropalletplace) as transporteuropalletplace
from my_table
group by transportorder

SQL - Check for price changes over multiple periods

I am using an MS Access database and am trying to make a query that provides an overview of securities for which the price changed by more than XX% during the last XY consecutive months. I have tried all kind of subqueries but cannot get my head around this.
Please find below a simplified example. The PriceTable contains three attributes: a period, a security id and the price of the security in that period. I am looking for a query that provides me per the last period (in this case 201210) all securities having a price change of more than plus or minus XX% (in this case 3%) in the last XY (in this case 3) months. The three columns on the right hand provide some calculations to further clarify this:
Delta is the price change from one period to the other ((PT-PT-1)/PT-1)
Delta>Threshold: checks whether the change is larger than (plus or minus) 3% (parameter XX)
Counter: checks whether the price change is larger than 3% for 3 (parameter XY) consecutive months
In the example below the query should only show productID number 1.
PriceTable Supporting calculations
+--------+------+-------+--------+-----------------+---------+
+ Period |SecID | Price | Delta% | Delta>Threshold | Counter |
+--------+------+-------+--------+-----------------+---------+
| 201206 | 1 | 105 | 0% | N | 0 |
| 201207 | 1 | 100 | -4.76% | Y | 1 |
| 201208 | 1 | 95 | -5% | Y | 2 |
| 201209 | 1 | 90 | -5.26% | Y | 3 |
| 201210 | 1 | 85 | -5.56% | Y | 4 |
| 201207 | 2 | 95 | 0% | N | 0 |
| 201208 | 2 | 100 | 5.26% | Y | 1 |
| 201209 | 2 | 103 | 3% | N | 0 |
| 201210 | 2 | 99 | -3.88% | Y | 1 |
+--------+------+-------+--------+-----------------+---------+
I hope someone can help me out!
Thanks in advance,
Paul
I don't have Access to hand, but here's a query for SQL Server:
The inner 'h' table is pretty much your helper table. the outer bit joins on 3 periods, and displays if the count with threshold 'Y' is 3
The way I did it you also need functions for working out the next period, and the number of periods between two end points. These should be fairly easy to write in VBA. You could also create a period table with a sequence number to work around this:
-- Function that works out the next period
-- i.e. if you supply 201112, it will return 201201
Create Function dbo.NextPeriod(#Period As Int) Returns Int As
Begin
Declare
#Month int,
#Ret int = Null
If #Period Is Not Null
Begin
Set #Month = #Period - 100 * (#Period / 100)
If #Month < 12
Set #Ret = #Period + 1
Else
Set #Ret = #Period - #Month + 101
End
Return #Ret
End;
-- Function that works out how many periods between the two endpoints
-- dbo.PeriodCount(201112, 201201) = 1
Create Function dbo.PeriodCount(#StartPeriod As Int, #EndPeriod As Int) Returns Int As
Begin
Declare
#StartMonth int,
#EndMonth int,
#StartYear int,
#EndYear int,
#Ret int = Null
If #StartPeriod Is Not Null And #EndPeriod Is Not Null
Begin
Set #StartMonth = #StartPeriod - 100 * (#StartPeriod /100)
Set #StartYear = (#StartPeriod - #StartMonth) / 100
Set #EndMonth = #EndPeriod - 100 * (#EndPeriod / 100)
Set #EndYear = (#EndPeriod - #EndMonth) / 100
Set #Ret = (12 * #EndYear + #EndMonth) - (12 * #StartYear + #StartMonth)
End
Return #Ret
End;
-- Show periods that are the start of a run
-- of #Periods periods with threshold
-- of at least #Threshold
Declare #Threshold Decimal(10, 2) = 3
Declare #Periods int = 3
Select
p0.SecurityID,
p0.Period
From
PriceTable p0
Inner Join (
Select
p1.*,
100 * (p1.Price - p2.Price) / p2.Price As Delta,
Case When Abs(100 * (p1.Price - p2.Price) / p2.Price) > #Threshold Then 'Y' Else 'N' End As OverThreshold
From
PriceTable p1
Left Outer Join
PriceTable p2
On p1.SecurityID = p2.SecurityID And
p1.Period = dbo.NextPeriod(p2.Period)
) h
On p0.SecurityID = h.SecurityID And
dbo.PeriodCount(p0.Period, h.Period) Between 0 And (#Periods - 1) And
h.OverThreshold = 'Y'
Group By
p0.SecurityID,
p0.Period
Having
Count(*) = #Periods
Order By
p0.SecurityID,
p0.Period;
This shows you how the method works, you can simplify it like so:
Declare #Threshold Decimal(10, 2) = 3
Declare #Periods int = 3
Select
p0.SecurityID,
p0.Period
From
PriceTable p0
Inner Join
PriceTable p1
On p0.SecurityID = p1.SecurityID And
dbo.PeriodCount(p0.Period, p1.Period) Between 0 And (#Periods - 1)
Inner Join
PriceTable p2
On p1.SecurityID = p2.SecurityID And
p1.Period = dbo.NextPeriod(p2.Period)
Where
Abs(100 * (p1.Price - p2.Price) / p2.Price) > #Threshold
Group By
p0.SecurityID,
p0.Period
Having
Count(*) = #Periods
Order By
p0.SecurityID,
p0.Period;
http://sqlfiddle.com/#!3/8eff9/2
#Laurence: please find below the code
Public Function NextPer(Nperiod As Long) As Long
Dim Month As Long
If Not IsNull(Nperiod) Then
Month = 100 * ((Nperiod / 100) - Round(Nperiod / 100, 0))
If Month < 12 Then
NextPer = Nperiod + 1
Else
NextPer = Nperiod - Month + 101
End If
End If
End Function
Public Function PCount(SPeriod As Long, EPeriod As Long) As Long
Dim SMonth As Long
Dim EMonth As Long
Dim SYear As Long
Dim EYear As Long
If Not IsNull(SPeriod) And Not IsNull(EPeriod) Then
SMonth = 100 * ((SPeriod / 100) - Round(SPeriod / 100, 0))
SYear = (SPeriod - SMonth) / 100
EMonth = 100 * ((EPeriod / 100) - Round(EPeriod / 100, 0))
EYear = (EPeriod - EMonth) / 100
PCount = (12 * EYear + EMonth) - (12 * SYear + SMonth)
End If
End Function
And the QUERY (the parameters are for the moment hardcoded)
SELECT p0.SecurityID, p0.Period
FROM (PriceTable AS p0
INNER JOIN PriceTable AS p1 ON (p0.SecurityID = p1.SecurityID)
AND (PCount(p0.Period,p1.Period)>=0) AND (PCount(p0.Period,p1.Period)<=2))
INNER JOIN PriceTable AS p2 ON (p1.SecurityID = p2.SecurityID)
AND (p1.Period = NextPer(p2.Period))
WHERE Abs(100*(p1.Price-p2.Price)/p2.Price)>0.03
GROUP BY p0.SecurityID, p0.Period
HAVING Count(*) = 3
ORDER BY p0.SecurityID asc , p0.Period asc;
+1 for your intention of trying to get this in query itself without UDFs. Out of extreme interest I have put some effort to find a solution. I admit following code is not the most efficient code. (with all those IIFs, the performance is not that great)
Getting first 5 columns as per your above table are pretty straightforwad. I have saved that in qryDelta. I find the tricky part of the question is to have Counter in the same results table. Second query qryCounter will give you the final table as you expected.
qryDelta
SELECT a.period, a.secid, a.price,
iif(isnull(ROUND((a.price-b.price)/b.price*100,2)),0,
ROUND((a.price-b.price)/b.price*100,2)) AS Delta,
iif(abs((a.price-b.price)/b.price)*100>3,"Y","N") AS Threshold,
SUM(iif(abs((a.price-b.price)/b.price)*100>3,1,0)) AS [Counter]
FROM tbldelta AS a LEFT JOIN tbldelta AS b
ON (a.secid = b.secid) AND (a.period = b.period + 1)
GROUP BY a.period, a.secid, a.price,
iif(isnull(ROUND((a.price-b.price)/b.price*100,2)),0,
ROUND((a.price-b.price)/b.price*100,2)),
iif(abs((a.price-b.price)/b.price)*100>3,"Y","N")
ORDER BY a.secid, a.period;
Results:
qryCounter
SELECT q.period, q.secid, q.price, q.delta, q.threshold,
SUM(iif(q.counter=0,0,1)) AS Counter
FROM qryDelta q
LEFT JOIN tblDelta t
ON q.secid = t.secid
AND (t.period < q.period)
GROUP BY q.secid, q.period, q.price, q.delta, q.threshold
Results:
However I too faced the issue with SecId = 2, Period = 201208 with a total = 2. So I changed my query conditions. Now the results seem to show the cumulative periodic count properly except for SectID = 2, Period = 201210 total = 3. Perhpas you guys could throw some light to this. Out of most of the experiments done, it seems more or less a bug on JOIN and between dates that we are trying to put as coditions here.
PS:
If you have decided to build user defined functions (UDF), then you may consider two things. Are you using Excel as front end or Access as front end. Then you have to provide necessary arrangements to call your Access UDF & query from Excel. If you are only using Access as both front and back end, then ofcourse using a UDF would be much easier to handle.
I solved it using just SQL. Here's how I did.
First of all, we need a query that, for each rows, shows the distance in rows from the last period:
Period SecID Price Row
===============================
201206 1 105 4
201207 1 100 3
201208 1 95 2
201209 1 90 1
201210 1 85 0
201207 2 95 3
201208 2 100 2
201209 2 103 1
201210 2 99 0
we will call it PriceTable_Ordered:
SELECT
PriceTable.Period,
PriceTable.SecID,
PriceTable.Price,
(select count(*) from PriceTable PriceTable_1
where PriceTable_1.SecID = PriceTable.SecID
AND PriceTable_1.Period > PriceTable.Period) AS Row
FROM PriceTable;
Now to calculate the Delta, and showing if the Delta is more than the threesold, we can use this query that we will call PriceTable_Total1:
SELECT
PriceTable_Ordered.*,
PriceTable_Ordered_1.Price,
(PriceTable_Ordered.Price-PriceTable_Ordered_1.Price)/(PriceTable_Ordered_1.Price) AS Delta,
iif((ABS(Delta*100)>3),"Y","N") AS DeltaThreesold
FROM
PriceTable_Ordered LEFT JOIN PriceTable_Ordered AS PriceTable_Ordered_1
ON (PriceTable_Ordered.SecID = PriceTable_Ordered_1.SecID)
AND (PriceTable_Ordered.[Row]=PriceTable_Ordered_1.[Row]-1);
And this returns:
Period SecID Price1 Row Price2 Delta DeltaThreesold
=========================================================
201206 1 105 4 N
201207 1 100 3 105 -4,76 Y
201208 1 95 2 100 -0,05 Y
201209 1 90 1 95 -5,26 Y
201210 1 85 0 90 -5,55 Y
201207 2 95 3 N
201208 2 100 2 95 5,26 Y
201209 2 103 1 100 0,03 N
201210 2 99 0 103 -3,88 Y
Now we can create PriceTable_Total2 based on PriceTable_Total1:
SELECT
PriceTable_Total1.Period,
PriceTable_Total1.SecID,
PriceTable_Total1.PriceTable_Ordered.Price,
PriceTable_Total1.Delta,
PriceTable_Total1.DeltaThreesold,
PriceTable_Total1.Row,
(select min(row) from PriceTable_Total1 PriceTable_Total1_1
where PriceTable_Total1.SecID = PriceTable_Total1_1.SecId
and PriceTable_Total1.Row < PriceTable_Total1_1.Row
and PriceTable_Total1_1.DeltaThreesold="N") AS MinN,
IIf([DeltaThreesold]="Y",[MinN]-[row],0) AS CountRows
FROM PriceTable_Total1;
we select all the columns of PriceTable_Total1, then for each row we count the minimum row number > than current row where threesold is "N". If current row is over threesold, the count we need is just this difference, otherwise it's 0. Here's the result:
Period SecID Price Delta DelTh Row MinN CountRows
========================================================
201206 1 105 N 4 0
201207 1 100 -4,76 Y 3 4 1
201208 1 95 -0,05 Y 2 4 2
201209 1 90 -5,26 Y 1 4 3
201210 1 85 -5,55 Y 0 4 4
201207 2 95 N 3 0
201208 2 100 5,26 Y 2 3 1
201209 2 103 0,03 N 1 3 0
201210 2 99 -3,88 Y 0 1 1
You can then hide the columns that you don't need. This query should work even if we cross the year and even if some periods are missing.
SELECT PriceTable_Total2.Period, PriceTable_Total2.SecID
FROM PriceTable_Total2
WHERE (PriceTable_Total2.Period=
(select max(period)
from PriceTable
where PriceTable.SecID=PriceTable_Total2.SecID)
AND (PriceTable_Total2.[CountRows])>=3);
this will return:
Period SecID
201210 1
and that means that only SecID 1 is over threesold in the last period for more than 3 months.
I hope this answer is correct, it was nice to try to solve it!!