I have 2 tables - Table A(Stock prices with date and symbols) and Table B(Adjustment factor and the effective date *adjustment factor is applicable for all dates before the effective date)
Table A -
date
symbol
price
2021-07-23
IRCON
45
2021-07-23
TIDEWATER
14891
2021-07-22
TIDEWATER
15309
2021-07-22
IRCON
45
2020-04-03
IRCON
91
2020-04-03
TIDEWATER
3182
2020-04-01
IRCON
393
2020-04-01
TIDEWATER
3171
2020-03-31
IRCON
381
2020-03-31
TIDEWATER
3207
Table B -
symbol
effective_date
adjustment_factor
TIDEWATER
2021-07-26
3
IRCON
2021-07-26
2
IRCON
2020-04-03
5
Requirement is -
The adjustment_factor of symbol needs to be applied(as a divisor) to Prices of symbol for all the dates less than effective_date of that adjustment_factor
E.g- For IRCON and adjustment_factor of 2 dated 2021-07-26, all prices of IRCON in Table A earlier than 2021-07-26 need to be divided by 2.
Similarly, for IRCON, adjustment_factor of 5 dated 2021-04-03,all prices of IRCON in Table A earlier than 2021-04-03 need to be divided by 5
(so, effectively all prices of IRCON before 2021-04-03 need to be divided by 2x5=10)
Desired output -
date
symbol
price
adjustment_factor
adjusted_price
2021-07-23
IRCON
45
2
22.38
2021-07-23
TIDEWATER
14891
3
4963.75
2021-07-22
TIDEWATER
15309
3
5103.00
2021-07-22
IRCON
45
2
22.58
2020-04-03
IRCON
91
2
45.43
2020-04-03
TIDEWATER
3182
3
1060.50
2020-04-01
IRCON
393
10=2x5
39.30
2020-04-01
TIDEWATER
3171
3
1057.13
2020-03-31
IRCON
381
10=2x5
38.10
2020-03-31
TIDEWATER
3207
3
1069.13
I have been trying using INNER JOIN, however, I am stuck for prices that need to be divided multiple times. There are some prices that need to be adjusted/divided with 5-6 factors combined/multiplied together. Is it possible to write some query for this in Postgres, maybe using Windows functions? Is there any scalable query to do this?
I am pretty sure there must be a better solution, But till the time you are not finding a better solution, You may try below solution -
SELECT A.date, A.symbol, A.price,
(SELECT COALESCE((SELECT adjustment_factor
FROM B B2
WHERE B1.symbol = B2.symbol
AND B1.effective_date < B2.effective_date), 1)*B1.adjustment_factor adjustment_factor
FROM B B1
WHERE A.symbol = B1.symbol
AND A.date < B1.effective_date
ORDER BY B1.effective_date
LIMIT 1),
ROUND(A.price :: DECIMAL / (SELECT COALESCE((SELECT adjustment_factor
FROM B B2
WHERE B1.symbol = B2.symbol
AND B1.effective_date < B2.effective_date), 1)*B1.adjustment_factor
FROM B B1
WHERE A.symbol = B1.symbol
AND A.date <= B1.effective_date
ORDER BY B1.effective_date
LIMIT 1), 2) adjusted_price
FROM A;
Fiddle Demo.
You can use a lateral join. The real trick is that Postgres (and SQL in general) does not offer a product() aggregation function. However, you can implement one using logs and exponentiation:
select a.*,
b.net_adjustment_factor,
a.price / coalesce(b.net_adjustment_factor, 1)
from a left join lateral
(select exp(sum(ln(adjustment_factor)) over (partition by symbol order by effective_date)) as net_adjustment_factor
from b
where b.symbol = a.symbol and
b.effective_date >= a.date
) b
on 1=1;
Here is a db-fiddle.
Related
Table A
date
flight
airport
2012-10-01
oneway
ATL, GA
2012-10-01
oneway
LAX, CA
2012-10-02
oneway
SAN, CA
2012-10-02
oneway
DTW, MI
2012-10-03
round
SFO, CA
2012-10-04
round
SFO, CA
2012-10-05
round
SFO, CA
Table B
date
temp
precip
2012-10-01
27
0.02
2012-10-02
35
0.00
2012-10-03
66
0.18
2012-10-04
57
0.00
2012-10-05
78
0.24
Table A has about 100k rows and whereas Table B has only about 60 rows
I am trying to query to find total number of flights on cold days and warm days as well as tracking the number of days for either cold or warm.
A cold day is defined when temp from Table B is below (<) 40 and warm otherwise.
In the real data, I have total 10 days that matches the date therefore I need to count for that when aggregating. I tried to get the total count without using CTE but I am keep getting wrong counts.
The expected outcome
Days
Num_of_flight
Num_of_days
cold day
4
2
warm day
3
3
You need a LEFT join of TableB to TableA and aggregation on the result of a CASE expression which returns 'cold' or 'warm':
SELECT CASE WHEN b.temp < 40 THEN 'cold day' ELSE 'warm day' END Days,
COUNT(*) Num_of_flight,
COUNT(DISTINCT a.date) Num_of_days
FROM TableB b LEFT JOIN TableA a
ON a.date = b.date
GROUP BY Days;
See the demo.
I have a table that contains a list of all of the dates of a date range (table a), as well as another table that contains just business dates and their respective asset values (table b). I'd like to query for all values in table a, and join in the asset values from table b. In the event table A's date is not in table B, then bull in the asset value for the max date in table B that's less than or equal to the date value in table A
Table A:
DATE
1/1/2021
1/2/2021
1/3/2021
1/4/2021
1/5/2021
1/6/2021
1/7/2021
1/8/2021
1/9/2021
1/10/2021
Table B:
DATE Assets
1/1/2021 6
1/3/2021 6
1/5/2021 3
1/7/2021 9
1/9/2021 10
Desired Results:
DATE Assets
1/1/2021 6
1/2/2021 6
1/3/2021 5
1/4/2021 5
1/5/2021 10
1/6/2021 10
1/7/2021 9
1/8/2021 9
1/9/2021 2
1/10/2021 2
I've attempted something along the lines of the below with no luck. Any input would be greatly appreciated:
select a.[DATE],
b.[Assets]
from A as a
left join B as B on a.DATE <= b.DATE
One possibility is to use outer apply to get the relevant asset for a given date.
select A.[date], B1.Assets
from TableA A
outer apply (
select top 1 Assets
from TableB B
where B.[date] <= A.[date]
order by [date] desc
) B1
I have two tables containing date ranges that I want to cross multiply in a way to get all distinct ranges. That is, all ranges that have a boundary in one of the tables.
Specifically I have a table with product prices and their validity dates as well as conversion factors with a validity date. I want, as a result, each instance of a specific price/conversion_factor combination and from when to when it was valid:
products:
product_id start_date end_date price_eur
1 2000-01-01 2000-12-31 100
1 2001-01-01 2002-12-31 150
conversion_factors:
start_date end_date dollar_to_eur
1970-01-01 2000-03-31 1.50
2000-04-01 2000-06-30 1.60
2000-07-01 2001-06-30 1.70
2001-07-01 2003-06-30 2.00
result:
product_id start_date end_date price_eur dollar_to_eur
1 2000-01-01 2000-03-31 100 1.50
1 2000-04-01 2000-06-30 100 1.60
1 2000-07-01 2000-12-31 100 1.70
1 2001-01-01 2001-06-30 150 1.70
1 2001-07-01 2002-12-31 150 2.00
So each time one of the tables hits a new date, a new row should be returned. In the result the first two rows reference the validity of the first product row, but split up into two intervals in the conversion_factos table. Similarly the second and third row of the result come from the second conversion factor row, but with different product rows.
Is there any way to do this with a clever join (in PostgreSQL) or do I need to use a PL/pgSQL function?
There are to parts in this, you ask for a smart join and you ask for displaying the correct result. This should answer your problems:
SELECT Greatest(p.start_date, cf.start_date) AS start_date
,Least(p.end_date, cf.end_date) AS end_date
,p.price_eur
,cf.dollar_to_eur
FROM products AS p
JOIN conversion_factors AS cf
ON p.start_date <= cf.end_date AND p.end_date >= cf.start_date
I have a table with some records now want to repeat this table content with some logic. I have two date start date and termination date, means record start from start_date and end on termination date, it will working fine but problem is calculate amount on it,
Logic is amount calculation formula
basesalary / 12 * ( SUTARate / 100 ) * ( x.num+1)
if this amount is less than SUTAMaximumAmount this amount is used, else 0. And one more thing if amount will be remain and year is complete then restart calculation from next year.. x.num is temporary table which hold 90 number from 1 to 90
Table
BaseSalary| S_Date | T_Date | SUTARate| SUTAMaximumAmount |A_S_Percent
48000 | 7-1-2013 | 3-15-2015 | 1.1 | 300 | 5
My result is
DAte amount
2013-07-01 00:00:00.000 44
2013-08-01 00:00:00.000 44
2013-09-01 00:00:00.000 44
2013-10-01 00:00:00.000 44
2013-11-01 00:00:00.000 44
2013-12-01 00:00:00.000 44
2014-01-01 00:00:00.000 36
2014-02-01 00:00:00.000 -8
2014-03-01 00:00:00.000 -52
2014-04-01 00:00:00.000 -96
2014-05-01 00:00:00.000 -140
2014-06-01 00:00:00.000 -184
2014-07-01 00:00:00.000 -228
2014-08-01 00:00:00.000 -272
2014-09-01 00:00:00.000 -316
2014-10-01 00:00:00.000 -360
2014-11-01 00:00:00.000 -404
2014-12-01 00:00:00.000 -448
2015-01-01 00:00:00.000 -492
2015-02-01 00:00:00.000 -536
2015-03-01 00:00:00.000 -580
and I want result like this
Date | Amount
7-1-2013 44
8-1-2013 44
9-1-2013 44
10-1-2013 44
11-1-2013 44
12-1-2013 44
1-1-2014 44
2-1-2014 44
3-1-2014 44
4-1-2014 44
5-1-2014 44
6-1-2014 44
7-1-2014 36
1-1-2015 44
2-1-2015 44
3-1-2015 44
Query
SELECT dateadd(M, (x.num),d.StartDate) AS TheDate,
Round( case when ((convert(float,d.SUTARate)/100* convert(integer,d.BaseSalary) / 12)*(x.num+1)) <=CONVERT(money,d.SUTAMaximumAmount)
then (convert(float,d.SUTARate)/100* convert(integer,d.BaseSalary)* / 12)
else (CONVERT(money,d.SUTAMaximumAmount)-((convert(float,d.SUTARate)/100* (convert(integer,d.BaseSalary) / 12)*x.num)))*Power((1+convert(float,d.AnnualSalaryIncreasePercent)/100),Convert(int,x.num/12)) end, 2) AS Amount,
FROM #Table AS x, myTbl AS d
WHERE (x.num >= 0) AND (x.num <= (DateDiff(M, d.StartDate, d.TerminationDate)) )
temporary table
create TABLE #Table (
num int NOT NULL,
);
;WITH Nbrs ( n ) AS (
SELECT 0 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 99 )
INSERT #Table(num)
SELECT n FROM Nbrs
OPTION ( MAXRECURSION 99 )
this table used as x in above query
I created this SQLFiddle.
-- Numbers table is probably a good idea
WITH Nbrs ( num ) AS
(
SELECT 0 UNION ALL
SELECT 1 + num FROM Nbrs WHERE num < 99
)
-- All columns, except for 'num' come from myTbl
SELECT dateadd(M, (num),S_Date) AS TheDate,
Round(
CASE
WHEN (SUTARate / 100) * (BaseSalary / 12) <= SUTAMaximumAmount
THEN (SUTARate / 100) * (BaseSalary / 12)
ELSE 0
END
, 2) As Amount
-- This may be the number you were trying to multiply
,DatePart(Month, dateadd(M, (num),S_Date)) As PotentialMultiiplier
FROM Nbrs AS x, myTbl AS d
WHERE (num >= 0)
AND (num <= (DateDiff(M, S_Date, T_Date)) )
I am not entirely sure what your goal is, but you are probably on the right track with a numbers table. Because the result you are going for does not change much over time (i.e., nearly every month has an amount of $44), it is difficult to determine the correct code for the query. So, I recommend you provide a different set of data for better result-checking.
If you fiddle with the SQL in the provided link, you can re-post with better code, and then we can better solve your issue.
Updated for more clarity
SQL Sever 2000. I'm trying to make this query slightly more unique.
The Query:
USE MyDatabase
GO
SELECT MAX(x.provider_entry_id) as provider_entry_id, -- this ID is the PK
x.provider_entry_type_id, -- the entry for the specific provider type (the ID)
x.provider_entry, -- the actual provider entry (the ID)
x.provider_entry_visit_dt -- the date the entry was created
FROM tbl_claimant_provider_entry x
JOIN (SELECT p.provider_entry_type_id,
p.provider_entry,
MAX(provider_entry_visit_dt) AS max_date
FROM tbl_claimant_provider_entry p
WHERE provider_entry_clmnt = 4963 -- change this for you user
GROUP BY p.provider_entry_type_id, p.provider_entry) y ON y.provider_entry_type_id = x.provider_entry_type_id
AND y.max_date = x.provider_entry_visit_dt
GROUP BY x.provider_entry_type_id, x.provider_entry, x.provider_entry_visit_dt
returns:
provider_entry_id provider_entry_type_id provider_entry provider_entry_visit_dt
1052 109 1088 2013-01-22 00:00:00.000
1051 109 1665 2013-01-23 00:00:00.000
1049 130 264 2013-01-01 00:00:00.000
1050 130 1126 2013-01-02 00:00:00.000
1045 132 NULL 2013-01-22 00:00:00.000
1047 132 260 2013-01-22 00:00:00.000
1044 132 1115 2013-01-10 00:00:00.000
1048 132 1130 2013-01-22 00:00:00.000
1043 142 1356 2013-01-10 00:00:00.000
I'm looking to narrow this list to show me only one instance of each unique provider_entry_type_id based on the most recent provider_entry_visit_dt
So the results would be (keep in mind that there will not need to be a need for tie breakers for the provider_entry_visit_dt, that's just an error on my part):
provider_entry_id provider_entry_type_id provider_entry provider_entry_visit_dt
1051 109 1665 2013-01-23 00:00:00.000
1050 130 1126 2013-01-02 00:00:00.000
1048 132 1130 2013-01-22 00:00:00.000
1043 142 1356 2013-01-10 00:00:00.000
I think you need to remove the outer GROUP BY clause
SELECT x.*
FROM tbl_claimant_provider_entry x
INNER JOIN
(
SELECT p.provider_entry_type_id,
MAX(created_date) AS max_date
FROM tbl_claimant_provider_entry p
WHERE provider_entry_clmnt = 4963 -- change this for you user ID
GROUP BY p.provider_entry_type_id
) y ON y.provider_entry_type_id = x.provider_entry_type_id AND
y.max_date = x.created_date
You need to remove created_date from the group by statement. You can put a function on it to leave it in the query (i.e. a function like you have for provider_entry_id). For example:
SELECT MAX(x.provider_entry_id) as provider_entry_id, -- this ID is the PK
MAX(x.created_date),
x.provider_entry_type_id, -- the entry for the specific provider type (the ID)
MIN(x.provider_entry) -- the actual provider entry (the ID)
FROM tbl_claimant_provider_entry x
JOIN (SELECT p.provider_entry_type_id,
p.provider_entry,
MAX(created_date) AS max_date
FROM tbl_claimant_provider_entry p
WHERE provider_entry_clmnt = 4963 -- change this for you user ID
GROUP BY p.provider_entry_type_id, p.provider_entry) y ON y.provider_entry_type_id = x.provider_entry_type_id
AND y.max_date = x.created_date
GROUP BY x.provider_entry_type_id
Here's the solution that worked, thank you to all that tried to help:
SELECT b.*
FROM dbo.tbl_claimant_provider_entry AS b
INNER JOIN
(SELECT provider_entry_type_id, MAX(provider_entry_visit_dt) AS maxdate
FROM dbo.tbl_claimant_provider_entry
GROUP BY provider_entry_type_id) AS m ON b.provider_entry_type_id = m.provider_entry_type_id AND b.provider_entry_visit_dt = m.maxdate
WHERE (b.provider_entry_clmnt = 4963)