Related
I am trying to get a Percentage change column. However, the calculation should always skip (or make NULL the first occurrence and reset for every new address:unit combination. For example address:unit "A" has sold several times over the last 20 years, so get the percentage difference for each year with the first year omitted. However, when we reach the row address:unit "B", reset by skipping or making NULL, and on second row of address:unit "B" begin calculation. And so on with a every other unique address:unit. The information below illustrates what I have written.
Table (input):
address, unit, sale_date, sale_price
123 Sesame St, 11B, 7/2/2005, 250,000
123 Sesame St, 11B, 8/1/2011, 500,000
123 Sesame St, 11B, 3/5/2019, 750,000
200 Jones St, 3W, 11/6/2015, 465,000
200 Jones St, 3W, 4/6/2018, 690,000
Desired output:
address, unit, sale_date, sale_price, percent_change
123 Sesame St, 11B, 7/2/2005, 250,000, NULL
123 Sesame St, 11B, 8/1/2011, 430,000, 0.72
123 Sesame St, 11B, 3/5/2019, 700,000, 0.63
200 Jones St, 3W, 11/6/2015, 465,000, NULL
200 Jones St, 3W, 4/6/2018, 690,000, 0.48
Incorrect Query:
SELECT address, unit, sale_date, sale_price - COALESCE(LAG(sale_price) OVER(ORDER BY address, unit, sale_date) AS percent_change
FROM TABLE
How can I fix query to get the Desired Output?
With lag() window function:
select *,
round(1.0 * (sale_price - lag(sale_price) over (partition by address, unit order by sale_date)) /
lag(sale_price) over (partition by address, unit order by sale_date) percent_change, 2)
from tablename
Or with a CTE to make the code more readable:
with cte as (
select *, lag(sale_price) over (partition by address, unit order by sale_date) prevprice
from tablename
)
select
address, unit, sale_date, sale_price,
round(1.0 * (sale_price - prevprice) / prevprice, 2) percent_change
from cte
See the demo.
Results:
> address | unit | sale_date | sale_price | percent_change
> :------------ | :--- | :------------------ | ---------: | :-------------
> 123 Sesame St | 11B | 02/07/2005 00:00:00 | 250000 | null
> 123 Sesame St | 11B | 01/08/2011 00:00:00 | 430000 | 0.72
> 123 Sesame St | 11B | 05/03/2019 00:00:00 | 700000 | 0.63
> 200 Jones St | 3W | 06/11/2015 00:00:00 | 465000 | null
> 200 Jones St | 3W | 06/04/2018 00:00:00 | 690000 | 0.48
#table1
idno | amount
-------------
1 | 700
2 | 500
#table2
idno | amount1 | amount2 | amount3 | acctno
------------------------------------------
1 | 100 | 200 | 300 | 001
1 | 100 | 200 | 300 | 002
2 | 100 | 200 | 300 | 001
What I want to happen is to distribute the amount from table2 into table 1's amount1,amount2,amount3 respectively then get the remaining balance and apply to the next row. I've tried using CTE but got stucked on passing the running balance to the next row.
Query:
Declare #table2 TABLE (idno varchar(max), amount1 decimal,amount2
decimal,amount3 decimal,acctno varchar(max))
INSERT INTO #table2 VALUES
('1',100,200,300,'001'),
('1',100,200,300,'002'),
('2',100,200,300,'001')
Declare #table1 TABLE (idno varchar(max), amount decimal)
INSERT INTO #table1 VALUES
('1',700),
('2',500);
WITH due AS (SELECT a.idno,a.amount,b.acctno,b.amount1,b.amount2,b.amount3
from #table1 a left join #table2 b on a.idno = b.idno),
payment AS (SELECT *,case when amount-amount1<0 then amount
else amount1 end as amount1pay
,case when amount-amount1<=0 then 0
when amount-amount1-amount2 <0 then amount-amount1
else amount2 end as amount2pay ,
case when amount-amount1-amount2<=0 then 0
when amount-amount1-amount2-amount3<0
then amount-amount1-amount2 else amount3 end as amount3pay
FROM due),
payment2 AS (SELECT SUM(amount-amount1pay-amount2pay-amount3pay)
OVER ( PARTITION BY idno ORDER BY acctno
ROWS UNBOUNDED PRECEDING ) as balance,* FROM payment)
select * from payment2
Current Result
balance | idno | amount | acctno | amount1 | amount2 | amount3 | amount1pay | amount2pay | amount3pay
---------------------------------------------------------------------------------------------------------
100 | 1 | 200 | 001 | 100 | 200 | 300 | 100 | 200 | 300
200 | 1 | 200 | 002 | 100 | 200 | 300 | 100 | 200 | 300
0 | 2 | 500 | 001 | 100 | 200 | 300 | 100 | 200 | 200
Expected Result
balance | idno | amount | acctno | amount1 | amount2 | amount3 | amount1pay | amount2pay | amount3pay
---------------------------------------------------------------------------------------------------------
100 | 1 | 200 | 001 | 100 | 200 | 300 | 100 | 200 | 300
100 | 1 | 200 | 002 | 100 | 200 | 300 | 100 | 0 | 0
0 | 2 | 500 | 001 | 100 | 200 | 300 | 100 | 200 | 200
This seems very messy, as there's a lot of different things going on here. I don't think I quite understand all of the "rules" for how you are distributing this money but this query does produce the expected results (actually it's slightly different, but I think you have a mistake in your table where you show "200" for amount in the first two rows it should be "700")?
WITH Base AS (
SELECT
t1.idno,
t2.acctno,
t1.amount,
t2.amount1,
t2.amount2,
t2.amount3,
ROW_NUMBER() OVER (ORDER BY t1.idno, t2.acctno) AS row_id
FROM
#table1 t1
INNER JOIN #table2 t2 ON t1.idno = t2.idno),
RunningBalance AS (
SELECT
*,
CASE WHEN amount > amount1 + amount2 + amount3 THEN amount - amount1 - amount2 - amount3 ELSE 0 END AS new_balance
FROM
Base),
NewIdno AS (
SELECT
idno,
MIN(row_id) AS first_row_id
FROM
Base
GROUP BY
idno),
NewBalance AS (
SELECT
n.first_row_id AS row_id,
b.amount
FROM
NewIdno n
INNER JOIN Base b ON b.row_id = n.first_row_id),
Amount1 AS (
SELECT
b.row_id,
rb1.new_balance AS balance,
b.idno,
b.amount,
b.acctno,
b.amount1,
b.amount2,
b.amount3,
CASE WHEN ISNULL(n.amount, rb2.new_balance) >= b.amount1 THEN b.amount1 ELSE b.amount1 - ISNULL(n.amount, rb2.new_balance) END AS pay_amount1,
ISNULL(n.amount, rb2.new_balance) - b.amount1 AS carried_forward_1
FROM
Base b
INNER JOIN RunningBalance rb1 ON rb1.row_id = b.row_id
LEFT JOIN RunningBalance rb2 ON rb2.row_id = b.row_id - 1
LEFT JOIN NewBalance n ON n.row_id = b.row_id),
Amount2 AS (
SELECT
*,
CASE WHEN carried_forward_1 >= amount2 THEN amount2 ELSE carried_forward_1 END AS pay_amount2,
carried_forward_1 - CASE WHEN carried_forward_1 >= amount2 THEN amount2 ELSE carried_forward_1 END AS carried_forward_2
FROM
Amount1),
Amount3 AS (
SELECT
*,
CASE WHEN carried_forward_2 >= amount3 THEN amount3 ELSE carried_forward_2 END AS pay_amount3
FROM
Amount2)
SELECT
balance,
idno,
amount,
acctno,
amount1,
amount2,
amount3,
pay_amount1,
pay_amount2,
pay_amount3
FROM
Amount3;
My results are:
balance idno amount acctno amount1 amount2 amount3 pay_amount1 pay_amount2 pay_amount3
100 1 700 001 100 200 300 100 200 300
100 1 700 002 100 200 300 100 0 0
0 2 500 001 100 200 300 100 200 200
Some of the rules I am using:
you start with the amount from table 1 to be distributed, i.e. £700 for idno #1 and £500 for idno #2;
this is assigned to table 2 by acctnos in numerical order;
where there are multiple acctnos you need to carry forward the balance from the previous one, if there's anything left to pay out;
once you start a new idno you can't carry forward any money left from the previous one.
So how does it work?
Step 1 - Order the data and add an index (row_id)
This is just the base data from the two tables, showing how much we have to distribute and the rows we are distributing it over:
idno acctno amount amount1 amount2 amount3 row_id
1 001 700 100 200 300 1
1 002 700 100 200 300 2
2 001 500 100 200 300 3
Step 2 - Work out the running balance
This works out how much money is left if we distributed the entire amount available across each row:
idno acctno amount amount1 amount2 amount3 row_id new_balance
1 001 700 100 200 300 1 100
1 002 700 100 200 300 2 100
2 001 500 100 200 300 3 0
Step 3 - (Interlude) We need to know which row we will be distributing data over first
This is just the first row_id for each idno:
idno first_row_id
1 1
2 3
Step 4 - Slightly wasteful, as we could have done this in the last step
We just need the total to be distributed over each "first" row:
row_id amount
1 700
3 500
Step 5 - This is where we deal with the running balance properly
For each row the rule is that we start with the "new balance" which only exists for the first row of each amount to be distributed. If this isn't the first row then we use the running balance instead, but we take this from the previous row (rb2.row_id = b.row_id - 1). We will always have one or the other of these:
row_id balance idno amount acctno amount1 amount2 amount3 pay_amount1 carried_forward_1
1 100 1 700 001 100 200 300 100 600
2 100 1 700 002 100 200 300 100 0
3 0 2 500 001 100 200 300 100 400
So the carried forward isn't what is to be carried to the next row, it's what is to be carried to the next amount (amount 2 in this case) to be distributed.
Note that this works for your dataset, but it wouldn't work if there were more than two rows per idno. If you were to have more than two rows per idno then you would simply need to add another stage to handle this scenario.
Step 6 - Carry Forward
For each amount we need to take it off the amount carried forward from the previous calculation, giving us the amount we can allocate to this amount, and the amount to be carried forward to the next calculation (which is redundant for amount 3 as there is no amount 4).
Heres the situation... i dont have a unique identifier between tables to do matching, so i am matching on a value that can be present more than one time in both tables. I am feeling like i have have to assign some value and then remove the record from being available for matching when the next record tries to match.
Table 1 has the following data:
MedicationName ID Pt Price SEQ
Drug1 123 AAA 100.00 1
Drug2 345 AAA 200.00 2
Table 2
InjectionName ID PT StarDate EndDate
Drug1 123 AAA 20170101 20991231
Drug1 123 AAA 20160417 NULL
Drug2 345 AAA 20161101 20191231
The result should be this:
Medication MedID Price Injection InjID StartDate EndDate PT Seq
Drug1 123 100.00 Drug1 123 20170101 20991231 AAA 1
*NULL NULL NULL Drug1 123 20160417 NULL AAA NULL*
Drug2 345 200.00 Drug2 345 20161101 20191231 AAA 2
But my result is this:
Medication MedID Price Injection InjID StartDate EndDate PT Seq
Drug1 123 100.00 Drug1 123 20170101 20991231 AAA 1
*Drug1 123 100.00 Drug1 123 20160417 NULL AAA 1*
Drug2 345 200.00 Drug2 345 20161101 20191231 AAA 2
I need to show that the 2nd item from the 2nd table does not have a matching value in table 1 because item 1 already matched against it.
You will need ROW_NUMBER to separate rows out.. try this:
With Table1 As
(
Select 'Drug1' MedicationName , 123 ID ,'AAA' Pt ,100.00 Price,1 SEQ
Union Select 'Drug2' MedicationName , 345 ID ,'AAA' Pt ,100.00 Price,1 SEQ
),
Table2 As
(
Select 'Drug1' InjectionName, 123 ID, 'AAA' PT, 20170101 StarDate, 20991231 EndDate
Union Select 'Drug1' InjectionName, 123, 'AAA', 20160417, NULL
Union Select 'Drug2' InjectionName, 345, 'AAA', 20161101, 20191231
),
Table1_Id As
(
Select
Cast (Row_Number() Over (Partition By MedicationName, ID Order BY MedicationName, Id) as Int) MedicationNumber,
MedicationName, Id, PT, Price, Seq
From Table1
),
Table2_Id As
(
Select
Cast (Row_Number() Over (Partition By InjectionName, ID Order BY InjectionName, Id) as Int) InjectionNumber,
InjectionName, Id, PT, StarDate, EndDate
From Table2
)
Select *
From Table1_Id T1
Full Outer Join Table2_Id T2
On T1.MedicationName = T2.InjectionName
And T1.ID = T2.ID
And T1.MedicationNumber = T2.InjectionNumber
I have a Employee Wages table like this, with their EmpID and their wages.
EmpId | Wages
================
101 | 1280
102 | 1600
103 | 1400
104 | 1401
105 | 1430
106 | 1300
I need to write a Stored Procedure in SQL Server, to group the Employees according to their wages, such that similar salaried people are in groups together and the deviations within the group is as minimum as possible.
There are no other conditions or rules mentioned.
The output should look like this
EmpId | Wages | Group
=======================
101 | 1280 | 1
106 | 1300 | 1
103 | 1400 | 2
104 | 1401 | 2
105 | 1430 | 2
102 | 1600 | 3
You can use a query like the following:
SELECT EmpId, Wages,
DENSE_RANK() OVER (ORDER BY CAST(Wages - t.min_wage AS INT) / 100) AS grp
FROM mytable
CROSS JOIN (SELECT MIN(Wages) AS min_wage FROM mytable) AS t
The query calculates the distance of each wage from the minimum wage and then uses integer division by 100 in order to place records in slices. So all records that have a deviation that is between 0 - 99 off the minimum wage are placed in the first slice. The second slice contains records off by 100 - 199 from the minimum wage, etc.
You can for +-30 deviation as the below:
DECLARE #Tbl TABLE (EmpId INT, Wages INT)
INSERT INTO #Tbl
VALUES
(99, 99),
(100, 101),
(101, 1280),
(102, 1600),
(103, 1400),
(104, 1401),
(105, 1430),
(106, 1300)
;WITH CTE AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY Wages) AS RowId FROM #Tbl )
SELECT
A.EmpId ,
A.Wages ,
DENSE_RANK() OVER (ORDER BY MIN(B.RowId)) [Group]
FROM
CTE A CROSS JOIN CTE B
WHERE
ABS(B.Wages - A.Wages) BETWEEN 0 AND 30 -- Here +-30
GROUP BY A.EmpId, A.Wages
ORDER BY A.Wages
Result:
EmpId Wages Group
----------- ----------- --------------------
99 99 1
100 101 1
101 1280 2
106 1300 2
103 1400 3
104 1401 3
105 1430 3
102 1600 4
I am working on a small reporting application. I have two tables
Agent Table Data
AgentID AgentName
------- ---------
1001 ABC
1002 XYZ
1003 POI
1004 JKL
Report Table Data
ReportID AgentId Labor Mandays Amount SubmitDate
-------- ------- ----- ------- ------ ----------
1 1001 30 10 5000 11/12/2011
2 1001 44 18 8000 11/14/2011
3 1002 33 75 3022 11/12/2011
4 1001 10 10 1500 11/14/2011
5 1002 10 10 1800 11/14/2011
6 1001 10 10 1400 11/14/2011
7 1003 40 40 1500 11/14/2011
8 1003 40 40 1800 11/14/2011
I want to generate a report which gives us output like
ReportID AgentId Labor Mandays Amount SubmitDate
-------- ------- ----- ------- ------ ----------
1 1001 30 10 5000 11/12/2011
3 1002 33 75 3022 11/12/2011
6 1001 10 10 1400 11/14/2011
5 1002 10 10 1800 11/14/2011
8 1003 40 40 1800 11/14/2011
Thanks in Advance
You didn't mention what VERSION of SQL Server you're using - if you're on 2005 or newer, you can use a CTE (Common Table Expression) with the ROW_NUMBER function:
;WITH LastPerAgent AS
(
SELECT
AgentID, ReportID, Labor, Mandays, Amount, SubmitDate,
ROW_NUMBER() OVER(PARTITION BY AgentID,SubmitDate
ORDER BY ReportID DESC) AS 'RowNum'
FROM dbo.Report
)
SELECT
AgentID, ReportID, Labor, Mandays, Amount, SubmitDate,
FROM LastPerAgent
WHERE RowNum = 1
This CTE "partitions" your data by AgentID and SubmitDate, and for each partition, the ROW_NUMBER function hands out sequential numbers, starting at 1 and ordered by ReportID DESC - so the "last" row (with the highest ReportID) for each (AgentID, SubmitDate) pair gets RowNum = 1 which is what I select from the CTE in the SELECT statement after it.
PS: this doesn't work 100% on your input data, since you've not defined how to group and how to eliminate rows.... you might need to adapt this query a bit, based on your requirements...