Simpler way to solve a scenario without cursor - sql

Lets consider one table
Date Type TransId Qty UnitCost Total
01/01/2020 OPEN IN33433 100 12 1200 --IN,UnitCost is fixed
19/03/2020 PUR IN31134 200 12.5 2500 --IN,UnitCost is fixed
21/03/2020 DEL OUT24443 250 12.33 3082.5 --OUT unit cost calculated
25/03/2020 DEL OUT28668 10 12.33 123.3 --OUT unit cost calculated
26/03/2020 SAL OUT35448 23 12.33 283.59 --OUT unit cost calculated
30/03/2020 TRSFR IN83588 12 12.45 149.4 --IN, UnitCost is fixed
The table depicts an inventory transaction records. Lets say IN types are OPEN,PUR,TRSFR,ADJIN and OUT Types are DEL,SAL,ADJOUT
Here the Unit cost for all IN transaction are fixed. Which means we will get this value from somewhere and do not need to worry about it.
But for OUT transactions, the unit costs are calculated. For better understanding of the calculation lets consider an example. The first OUT transaction which is on 21/03/2020 with type DEL. The formula for calculating the unitcost for this OUT transaction is:
sum of all Totals of IN transaction happened before and after any OUT / sum of Qty of all IN transaction happened before and after any OUT
So the unitcost=(1200+2500)/(100+200)=12.33.
And then an IN transaction happened on 30/03/2020 with type TRSFR. For this, as I said earlier the unit cost is fixed. Now for any out transaction happened we will consider the unit cost of 12.45 (149.4/12).
Hope its clear. I need a solution to calculate and get the unit cost without CURSOR. Lets say I need to get the unit cost for a transaction which is supposed to happen on 20/04/2020 of type DEL (which is OUT)

You can do it using multiple subqueries like below.
DECLARE #SampleTable TABLE
(
Date DATETIME,
Type VARCHAR(100),
TransId VARCHAR(100),
Qty INT,
UnitCost MONEY,
Total MONEY
)
INSERT INTO #SampleTable VALUES
('2020-01-01', 'OPEN', 'IN33433', 100 , 12 , 1200 ), --IN,UnitCost is fixed
('2020-03-19', 'PUR', 'IN31134', 200 , 12.5 , 2500 ), --IN,UnitCost is fixed
('2020-03-21', 'DEL', 'OUT24443', 250 , 12.33 , 3082.5), --OUT unit cost calculated
('2020-03-25', 'DEL', 'OUT28668', 10 , 12.33 , 123.3 ), --OUT unit cost calculated
('2020-03-26', 'SAL', 'OUT35448', 23 , 12.33 , 283.59), --OUT unit cost calculated
('2020-03-30', 'TRSFR', 'IN83588', 12 , 12.45 , 149.4 ) --IN, UnitCost is fixed
SELECT
*,
(SELECT SUM(Total) FROM #SampleTable S2 WHERE S2.Date < S1.Date AND S2.TransId LIKE 'IN%' AND S1.TransId LIKE 'OUT%') AS SumOutTransaction,
(SELECT SUM(Qty) FROM #SampleTable S2 WHERE S2.Date < S1.Date AND S2.TransId LIKE 'IN%' AND S1.TransId LIKE 'OUT%') AS SumQtyTransaction,
CASE
WHEN S1.TransId LIKE 'OUT%' THEN
(
(SELECT SUM(Total) FROM #SampleTable S2 WHERE S2.Date < S1.Date AND S2.TransId LIKE 'IN%' AND S1.TransId LIKE 'OUT%')
/
(SELECT SUM(Qty) FROM #SampleTable S2 WHERE S2.Date < S1.Date AND S2.TransId LIKE 'IN%' AND S1.TransId LIKE 'OUT%')
)
ELSE Total/Qty
END AS CalculatedUnitCost
FROM #SampleTable S1
Apart from this, there could be other more optimized options as well.

Sample data
create table #purchases
(
Date varchar(20),
Type varchar(10),
Transid varchar(100),
Qty int,
UnitCost float,
Total float
)
insert into #purchases values('01/01/2020','OPEN', 'IN33433', 100 , 12 , 1200)
insert into #purchases values('19/03/2020','PUR' , 'IN31134', 200 , 12.5 , 2500)
insert into #purchases values('21/03/2020','DEL' , 'OUT24443', 250 , 12.33 , 3082.5)
insert into #purchases values('25/03/2020','DEL' , 'OUT28668', 10 , 12.33 , 123.3)
insert into #purchases values('26/03/2020','SAL' , 'OUT35448', 23 , 12.33 , 283.59)
insert into #purchases values('30/03/2020','TRSFR' , 'IN83588' , 12 , 12.45 , 149.4)
Query
select ROW_NUMBER() over (order by Date desc) as Rn,* into #temp
from #purchases
declare #latestOut int
select #latestOut=Rn from #temp where Type in ('DEL','SAL','ADJOUT') order by Rn desc
select SUM(Total)/Sum(Qty) as [Output] from #temp where RN<#latestOut

Related

Getting values from multiple tables in one column

Let's say I have three tables:
table orders:
invoice_ID
customer_ID
202201
1000
202202
2000
202203
3000
202204
4000
table department_north
customer_ID
product
price
4000
VW Rabbit
$5000.00
1000
BMW X5
$15.000
table department_south
customer_ID
product
price
3000
Tesla S
$30.000
2000
BMW X3
$20.000
Wanted Result
A table with invoice_id, a new column with all cars that contain '%BMW%', a new column with the attached price
invoice_ID
product_bmw
price_bmw
202201
BMW X5
$5.000
202202
BMW X3
$20.000
I figured out how to get the results for one department table but can't find a statement for both.
SELECT DISTINCT orders.invoice_ID,
department_north.product AS product_BMW,
department_north.price AS price_BMW
FROM orders
JOIN LEFT department_north
ON department_north.customer_ID = order.customer_id
JOIN LEFT department_south
ON department_south.customer_ID = order.customer_id
WHERE department_north.product LIKE '%BMW%'
I would UNION ALL all departments. See following example:
DECLARE #orders TABLE
(
invoice_ID varchar(20),
customer_ID int
);
INSERT #orders VALUES
(202201, 1000),
(202202, 2000),
(202203, 3000),
(202204, 4000);
DECLARE #department_north TABLE
(
customer_ID int,
product nvarchar(20),
price decimal(15,2)
);
INSERT #department_north VALUES
(4000, 'VW Rabbit', 5000),
(1000, 'BMW X5', 15000);
DECLARE #department_south TABLE
(
customer_ID int,
product nvarchar(20),
price decimal(15,2)
);
INSERT #department_south VALUES
(3000, 'Tesla S', 30000),
(2000, 'BMW X3', 20000);
WITH AllDepartments AS
(
SELECT *
FROM #department_north
UNION ALL
SELECT *
FROM #department_south
)
SELECT invoice_ID, product, price
FROM #orders O
JOIN AllDepartments D ON O.customer_ID=D.customer_ID
WHERE product LIKE '%BMW%';
I would use union all like Paweł Dyl's answer above, but would create a single department table and create an extra column, called location or similar and put an 'S' for south and an 'N' for north into it as per below:
create table #department
(
customer_ID int
, product varchar(64)
, price decimal(15,2)
, "location" varchar(64) -- to allow for other locations
)
;
insert into #department values (4000, 'VW Rabbit', 5000.00, 'N');
insert into #department values (1000, 'BMW X5', 15.000, 'N');
insert into #department values (3000, 'Tesla S', 30.000, 'S');
insert into #department values (2000, 'BMW X3', 20.000, 'S');
This means that you are just using the one department table and you have the additional 'location' column for adding east or west if need be. This will reduce the need to create a new database table for each new location added to your list. You could expand this to include city and/or state or whatever depending on the range of the data but you should aim to use only one table for this purpose.
Creating multiple tables based purely on location would not be recommended and think, what would you do if there were many locations e.g. 50 or more? It would be a nightmare to manage this code by creating a separate table for each location.

SQL number greater than select results

I'm struggling to think of a way to do this with T-SQL.
I have a table which is populated every 5 seconds with the prices of three currencies (GBP, EUR & USD)
I've created a trigger (after insert), which selects the last 5 records entered for a given currency:
SELECT TOP 5 Price from dbo.prices where coin='GBP' ORDER BY Date Desc
I want to determine if the last inserted currency price is greater than the selected 5 above, how do i do this?
Thanks
As I guess: there cant be two entries for the same currency at one time. Only one insert per currency per some time (5sec). So this should fit yours requirements:
declare #prices table ([Date] int IDENTITY(1,1) primary key, Price float, coin varchar(3));
insert into #prices (coin, Price) values
('GBP', 3.20),('EUR', 3.14),('USD', 3.14),
('GBP', 3.17),('EUR', 3.16),('USD', 3.11),
('GBP', 3.14),('EUR', 3.13),('USD', 3.16),
('GBP', 3.15),('EUR', 3.12),('USD', 3.17),
('GBP', 3.16),('EUR', 3.17),('USD', 3.11),
('GBP', 3.15),('EUR', 3.14),('USD', 3.12),
('GBP', 3.19),('EUR', 3.14),('USD', 3.16)
select
case
when NEW.Price > PREV.Price Then 'yes'
else 'No'
end as CURR_JUMP_UP
from
(
select top 1 COALESCE(Price,0) Price, [Date]
from #prices where coin='GBP' order by [Date] desc
) NEW
cross apply
(
select MAX(Price) Price from
(
select top 5 Price
from #prices
where coin='GBP' and [Date]<NEW.[Date]
order by [Date] desc
) t
) PREV
Try this query:
DECLARE #AmountLastFiveEntry DECIMAL= (SELECT TOP 5 SUM(Price) FROM dbo.prices WHERE
ID NOT IN (SELECT TOP 1 ID
FROM dbo.prices where coin='GBP' ORDER BY Date Desc) where coin='GBP' ORDER BY Date Desc)
IF #AmountLastFiveEntry<(SELECT TOP 1 Price
FROM dbo.prices where coin='GBP' ORDER BY Date Desc)
BEGIN
SELECT #AmountLastFiveEntry --To do task
END
Trigger part is confusing
This will report if the latest price is higher (or equal) to the largest of the prior 5.
declare #currency table (iden int IDENTITY(1,1) primary key, exchange smallint, coin tinyint);
insert into #currency (coin, exchange) values
(1, 1)
, (1, 2)
, (1, 3)
, (1, 4)
, (1, 5)
, (1, 6)
, (2, 1)
, (2, 2)
, (2, 3)
, (2, 4)
, (2, 5)
, (2, 3);
select cccc.coin, cccc.exchange
, case when cccc.rn = cccc.rne then 'yes'
else 'no'
end as 'high'
from ( select ccc.iden, ccc.coin, ccc.exchange, ccc.rn
, ROW_NUMBER() over (partition by ccc.coin order by ccc.exchange desc, ccc.rn) rne
from ( select cc.iden, cc.coin, cc.exchange, cc.rn
from ( select c.iden, c.coin, c.exchange
, ROW_NUMBER() over (partition by coin order by iden desc) as rn
from #currency c
) cc
where cc.rn <= 6
) ccc
) cccc
where cccc.rn = 1
order by cccc.coin

Calculate year over year increase SQL Server 2008R2

Below is my table structure. I need to calculate rent for length of lease for each properties:
Let's look at PropertyID = 12077:
Area = 1280
StartDate = 2023-02-01
EndDate = 2027-10-31
BaseRent = 21.53
RentIncreasePercent = .04 (4 percent)
IncreaseRepeatMonths = 12 months (NOTE: First 12 months there won't be any increase)
Since this property lease starts and ends between year 2023 and 2028, I'd like to know (in separate row per year) amount of rent to be collected each year. This would take percent increase every 12 months (compound rent increase) into consideration.
Example:
21.53 * 1280 would give rent for first 12 months. However, lease started in February so year 2023 total rent amount would be = ((21.23 * 1280)/12) * 11
For year 2024, first month rent would be = (21.23 * 1280)/12 because rent only increases every 12 months. For next 11 months of 2024, rent would be ((12.23 * 1.04 * 1280)/12)* 11.
For year 2025, first month rent would be (12.23 * 1.04 *1280)/12). However, next 11 months of 2025 would be ((12.72 * 1.04 * 1280)/12)*11. 12.72 comes from compound increase.
How would I go about coming up with a view to do this? Most confusing part to me is not knowing how to accommodate for lease start date when it is not starting on January.
declare #table table
(
PropertyID int,
area int,
StartDate date,
EndDate date,
BaseRent decimal(12,2),
RentIncreaseBasis varchar(30),
RentIncreasePercent decimal(5,2),
IncreaseRepeatMonths int
)
insert #table values (12076, 5627, '2024-01-01', '2028-12-31', '16.52', '% Increase', 0.03, 12)
insert #table values (12077, 1280, '2023-02-01', '2027-10-31', '21.53', '% Increase', 0.04, 12)
insert #table values (12078, 1000, '2017-03-01', '2025-11-30', '23.52', '% Increase', 0.01, 12)
insert #table values (12079, 2000, '2020-02-01', '2024-09-30', '15.57', '% Increase', 0.05, 12)
insert #table values (12080, 3000, '2018-05-01', '2020-08-31', '18.58', '% Increase', 0.04, 12)
insert #table values (12081, 4000, '2019-08-01', '2020-12-31', '22.56', '% Increase', 0.03, 12)
insert #table values (12082, 5000, '2017-02-01', '2028-03-31', '19.53', '% Increase', 0.02, 12)
select * from #table
I recommend to use a calendar table which containts all the months from your table.
I hope my example will work in SQL 2008.
-- here is your code
-- the calendar table
DECLARE #MonthCalendar table(
[Month] date PRIMARY KEY
)
DECLARE #MinDate date,#MaxDate date
-- get min and max date
SELECT
#MinDate=MIN(StartDate),
#MaxDate=MAX(EndDate)
FROM #table
-- fill the calendar table
;WITH monthCTE AS(
SELECT CAST(#MinDate AS date) [Month]
UNION ALL
SELECT DATEADD(MONTH,1,[Month])
FROM monthCTE
WHERE [Month]<#MaxDate
)
INSERT #MonthCalendar([Month])
SELECT [Month]
FROM monthCTE
OPTION(MAXRECURSION 0);
-- final query
SELECT
*,
(BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12 MonthRentAmount,
(1+RentIncreasePercent*IncreaseCount) TotalPercent
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
-- query for total amounts by PropertyIDs and Years
SELECT
PropertyID,
YEAR(StartDate) [Year],
SUM((BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12) YearRentAmount
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
GROUP BY PropertyID,YEAR(StartDate)
ORDER BY PropertyID,[Year]

SQL rounding to decimal places

I have a dataset which i need to do a calculation on price and round to decimal places. But the results aren't quite what is expected. The calculation is in the case statement
CREATE TABLE #Temp ( ID INT IDENTITY(1,1), Price DECIMAL(7,2) )
INSERT INTO #TEMP ( Price )
VALUES ( 119.99 )
, ( 48.99 )
SELECT
ID
, Price
, CASE WHEN Price > 10 THEN CONVERT( DECIMAL(7,2), Price * 1.08 - 0.05 ) END AS RRP
FROM #Temp
DROP TABLE #Temp
with the results
ID Price RRP
1 119.99 129.54
2 48.99 52.86
I need to get the 129.54 to 129.55 and the 52.86 to 52.85 within the same case statement if that is possible to match up with another data set
Are you rounding to the nearest 5 cents because your country has gotten rid of the penny? This is the only logic that we can seem to follow with your expected results.
SELECT ID
,Price
,CASE
WHEN Price > 10
THEN ROUND(CONVERT(DECIMAL(7, 2), Price * 1.08 - 0.05) * 20, 0) / 20
END AS RRP
FROM #Temp
Assuming you want to round to the nearest 5 cents or nearst 5 in 100ths decimal position.
INSERT INTO #TEMP ( Price )
VALUES ( 119.99 )
, ( 48.99 )
SELECT
ID
, Price
, Price * 1.08 - 0.05
, CASE WHEN Price > 10 THEN CONVERT( DECIMAL(7,2), round((Price * 1.08 - 0.05)*20,0)/20) END AS RRP
FROM #Temp
DEMO:http://rextester.com/QIM12944

Subquery: how to retrieve the last non-zero value from a column?

Considering a table customerBalance with 3 columns: name, date and balance. Suppose a set of records like:
cus_name cus_date cus_balance
John 06/14/2011 1000
John 06/15/2011 500
John 06/16/2011 0
Mary 06/14/2011 3000
Mary 06/15/2011 2800
Mary 06/16/2011 0
How to create a SQL query which returns, for the date 6/16/2011 instead 0, the last non-zero value based on date (in sample, $500 for John and $2800 for Mary)?
I'm trying to do it using a subquery which uses Max function to retrieve the last date with non-zero value, but I didn't succeed. This example is quite "nonsensical", but I really need to do an operation like this in my dataset. Thanks!
Note: If you can specify the DB and version this query can be improved.
Try this:
SELECT *
FROM customers
WHERE (cus_name, cus_date)
IN
(
SELECT cus_name, MAX(cus_date)
FROM customers
WHERE cus_balance <> 0
GROUP BY cus_name
)
Update: Alternate version:
SELECT a.*
FROM customers a,
(
SELECT cus_name, MAX(cus_date)
FROM customers
WHERE cus_balance <> 0
GROUP BY cus_name
) b
WHERE a.cus_name = b.cus_name
AND a.cus_date = b.cus_date
Here it goes:
CREATE Table #temp
(
Cus_Name VARCHAR(200) NULL,
Cus_Date Char(8) NULL,
Cus_Balance INT NULL
)
INSERT INTO #temp VALUES ('John' , '20110614' ,1000 )
INSERT INTO #temp VALUES ('John' , '20110615' , 500 )
INSERT INTO #temp VALUES ('John' , '20110616' , 0 )
INSERT INTO #temp VALUES ('Mary' , '20110614' ,3000 )
INSERT INTO #temp VALUES ('Mary' , '20110615' ,2800 )
INSERT INTO #temp VALUES ('Mary' , '20110616' , 0 )
SELECT
T.Cus_Name ,
MIN(t.Cus_Balance)
FROM #temp t
WHERE t.Cus_Balance <>0
GROUP BY t.Cus_Name
DROP TABLE #temp