Replace zero values with last available value in join - sql

SQL-FIDDLE Link
I'm using SQL Server.
Edit: Used a wrong SQL Fiddle before - updated to correct one
The join statement:
select t1.A_NR, t1.V_DATE, t1.AMOUNT T1_AMOUNT, t2.AMOUNT T2_AMOUNT
from Table_1 t1
left join Table_2 t2 on t1.A_NR = t2.A_NR and t1.V_DATE = t2.V_DATE
brings me this table with null values in the T2_Amount row.
+------+----------------------+-----------+-----------+
| A_NR | V_DATE | T1_AMOUNT | T2_AMOUNT |
+------+----------------------+-----------+-----------+
| 1 | 2020-01-01T00:00:00Z | 100 | 100 |
| 1 | 2020-01-02T00:00:00Z | 101 | (null) |
| 1 | 2020-01-03T00:00:00Z | 102 | (null) |
| 2 | 2020-01-01T00:00:00Z | 200 | 200 |
| 2 | 2020-01-02T00:00:00Z | 201 | (null) |
| 2 | 2020-01-03T00:00:00Z | 202 | (null) |
+------+----------------------+-----------+-----------+
I want to replace these values with the last available values from Table_2 like this:
+------+----------------------+-----------+-----------+
| A_NR | V_DATE | T1_AMOUNT | T2_AMOUNT |
+------+----------------------+-----------+-----------+
| 1 | 2020-01-01T00:00:00Z | 100 | 100 |
| 1 | 2020-01-02T00:00:00Z | 101 | 100 | --> value from 01.01.2020
| 1 | 2020-01-03T00:00:00Z | 102 | 100 | --> value from 01.01.2020
| 2 | 2020-01-01T00:00:00Z | 200 | 200 |
| 2 | 2020-01-02T00:00:00Z | 201 | 200 | --> value from 01.01.2020
| 2 | 2020-01-03T00:00:00Z | 202 | 200 | --> value from 01.01.2020
+------+----------------------+-----------+-----------+

One option uses a correlated subquery, or a lateral join:
select t1.a_nr, t1.v_date, t1.amount as t1_amount, t2.*
from table_1 t1
outer apply (
select top (1) t2.amount as t2_amount
from table_2 t2
where t2.a_nr = t1.a_nr and t2.v_date <= t1.v_date
order by t2.v_date desc
) t2
An alternative is to use some gaps-and-island technique: we can put unmatched records in groups along with the latest matched record with a window count, then use a window max to recover the value we want:
select a_nr, v_date, amount as t1_amount,
max(t2_amount) over(partition by a_nr, grp)
from (
select t1.*, t2.amount as t2_amount,
count(t2.amount) over(partition by t1.a_nr order by t1.v_date) as grp
from table_1 t1
left join table_2 t2 on t2.a_nr = t1.a_nr and t2.v_date = t1.v_date
) t

Related

Combining multiple rows into a single row SQL

I have a table like this.
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 8 |
|1 | A | A.1 | 01-05-2020 | 100 | 75 | 1 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 01-01-2020 | 100 | 55 | 11 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 10 |
I need to display a single row based on the latest PricedDate and Sum of Company Fee
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 9 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 21 |
Is it the latest row per InvID you want? I would probably just get the maximum date and the sum in an aggregation query and then join that row:
select
t.invid,
t.client,
t.group,
t.priceddate,
t.totalfee,
t.repricedfee,
agg.sum_fee as companyfee
from
(
select invid, max(priceddate) as max_date, sum(companyfee) as sum_fee
from mytable
group by invid
) agg
join mytable t on t.invid = agg.invid and t.priceddate = agg.max_date
order by t.invid;
just do aggregation
select invId,client,[group],max(priceddate),max(Totalfee),min(repricedFee),sum(companyfee)
from table
group by invId,client,[group]
Try it like this:
select *
, (select sum(CompanyFee) from my_table mt3 group by InvID) CompanyFee
from my_table mt1
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID);
This part will make sure your data is from the row that has the largest PricedDate :
mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)
Also, if it is not enough to group by InvID only you can add other columns there.
Here is a demo
Try this,
declare #CompanyFee= select sum(CompanyFee) from table1
select InvID,Client,Group,PricedDate,TotalFee,RepricedFee,#CompanyFee from table1
where priceddate=max(priceddate)
Try this.
select *
from my_table mt1
cross apply (
select CompanyFee=sum(CompanyFee) from my_table mt3 where mt3.invid=mt1.invid
) as CompanyFeeTbl
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)
You can use window function :
select t.InvID, t.Client, t.Group, t.PricedDate,
t.TotalFee, t.RepricedFee, t.SumCompanyFee as CompanyFee
from(select t.*, sum(t.companyfee) over (partition by t.client, t.invId) as SumCompanyFee,
row_number() over (partition by t.client, t.invId order by t.PricedDate desc) as seq
from table t
) t
where seq = 1;

Want to JOIN fourth table in query

I have four tables:
mls_category
points_matrix
mls_entry
bonus_points
My first table (mls_category) is like below:
*--------------------------------*
| cat_no | store_id | cat_value |
*--------------------------------*
| 10 | 101 | 1 |
| 11 | 101 | 4 |
*--------------------------------*
My second table (points_matrix) is like below:
*----------------------------------------------------*
| pm_no | store_id | value_per_point | maxpoint |
*----------------------------------------------------*
| 1 | 101 | 1 | 10 |
| 2 | 101 | 2 | 50 |
| 3 | 101 | 3 | 80 |
*----------------------------------------------------*
My third table (mls_entry) is like below:
*-------------------------------------------*
| user_id | category | distance | status |
*-------------------------------------------*
| 1 | 10 | 20 | approved |
| 1 | 10 | 30 | approved |
| 1 | 11 | 40 | approved |
*-------------------------------------------*
My fourth table (bonus_points) is like below:
*--------------------------------------------*
| user_id | store_id | bonus_points | type |
*--------------------------------------------*
| 1 | 101 | 200 | fixed |
| 2 | 102 | 300 | fixed |
| 1 | 103 | 4 | per |
*--------------------------------------------*
Now, I want to add bonus points value into the sum of total distance according to the store_id, user_id and type.
I am using the following code to get total distance:
SELECT MIN(b.value_per_point) * d.total_distance FROM points_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category
) d ON b.store_id = d.store_id AND b.maxpoint >= d.total_distance
The above code is correct to calculate value, now I want to JOIN my fourth table.
This gives me sum (60*3 = 180) as total value. Now, I want (60+200)*3 = 780 for user 1 and store id 101 and value is fixed.
i think your query will be like below
SELECT Max(b.value_per_point)*( max(d.total_distance)+max(bonus_points)) FROM mls_point_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category group by store_id
) d ON b.store_id = d.store_id inner join bonus_points bp on bp.store_id=d.store_id
DEMO fiddle

merging two sets of data and figuring out which is new and which is old

I have two sets of data with the same fields:
+----+---------+-------------+
| PK | myCDKey | DateCreated |
+----+---------+-------------+
| 1 | 131048 | 8/18/2014 |
| 2 | 131049 | 8/18/2014 |
| 3 | 131050 | 8/18/2014 |
| 4 | 131051 | 8/18/2014 |
| 5 | 131052 | 8/18/2014 |
| 6 | 131053 | 8/18/2014 |
| 7 | 131054 | 8/18/2014 |
| 8 | 131055 | 8/18/2014 |
| 9 | 131058 | 8/18/2014 |
| 10 | 131059 | 8/18/2014 |
+----+---------+-------------+
and
+----+---------+-------------+
| PK | myCDKey | DateCreated |
+----+---------+-------------+
| 11 | 131048 | 8/19/2014 |
| 12 | 131049 | 8/19/2014 |
| 13 | 131053 | 8/19/2014 |
| 14 | 131054 | 8/19/2014 |
| 15 | 131055 | 8/19/2014 |
| 16 | 131058 | 8/19/2014 |
| 17 | 131059 | 8/19/2014 |
| 18 | 111111 | 8/19/2014 |
| 19 | 222222 | 8/19/2014 |
| 20 | 333333 | 8/19/2014 |
+----+---------+-------------+
The output that I would like to have is something like this:
+----+---------+------------+
| PK | myCDKey | Delete/Add |
+----+---------+------------+
| 3 | 131050 | delete |
| 4 | 131051 | delete |
| 5 | 131052 | delete |
| 18 | 111111 | add |
| 19 | 222222 | add |
| 20 | 333333 | add |
+----+---------+------------+
The output shows me that when comparing the two dates, the most recent actions were that 3 of the CDs were deleted and 3 were added.
Is there already an out of the box way to do this perhaps with the merge function?
Thank you to #Linger for pointing out that I should explain how we know that they were added/deleted.
Added: if the myCDKey exists in the most recent date, but not the
previous date, then it is added.
Deleted: if the myCDKey exists in the previous date, but not in the
most recent
Please note that when comparing 2 data sets, we will ONLY have 2 dates (as in the example here we have only 8/18 and 8/19)
SQL Fiddle:
SELECT m1.PK, m1.myCDKey, 'delete' AS `DELETE/ADD`
FROM MyTable1 m1
WHERE m1.myCDKey NOT IN
(
SELECT t2.myCDKey
FROM MyTable2 t2
)
UNION
SELECT m2.PK, m2.myCDKey, 'add' AS `DELETE/ADD`
FROM MyTable2 m2
WHERE m2.myCDKey NOT IN
(
SELECT t1.myCDKey
FROM MyTable1 t1
);
Or you could do something like (SQL Fiddle):
SELECT m1.PK, m1.myCDKey, 'delete' AS `DELETE/ADD`
FROM MyTable1 m1
LEFT JOIN MyTable2 m2 ON m1.myCDKey = m2.myCDKey
WHERE m2.myCDKey IS NULL
UNION
SELECT m2.PK, m2.myCDKey, 'add' AS `DELETE/ADD`
FROM MyTable1 m1
RIGHT JOIN MyTable2 m2 ON m1.myCDKey = m2.myCDKey
WHERE m1.myCDKey IS NULL
select y.pk, x.mycdkey, 'delete' as delete_or_add
from (select mycdkey
from tbl1 except
select mycdkey from tbl2
) x
join tbl1 y
on x.mycdkey = y.mycdkey
union all
select y.pk, x.mycdkey, 'add' as delete_or_add
from (select mycdkey
from tbl2 except
select mycdkey from tbl1
) x
join tbl2 y
on x.mycdkey = y.mycdkey
Fiddle: http://sqlfiddle.com/#!3/ac1a7/6/0
Do this :
minus Tbl1 from Tbl2 : added records , add Delete/Add as 'add'
union
minus Tbl2 from Tbl1 : deleted records, add Delete/Add as 'delete'
use except in sql server
the version using a FULL JOIN would be:
select coalesce(m1.PK,m2.PK) PK
, coalesce(m1.myCDKey, m2.myCDKey) myCDKey
, case
when m1.PK is null then 'add'
when m2.PK is null then 'delete' else 'error'
end as action
from MyTable1 m1
FULL OUTER JOIN MyTable2 m2 ON m1.myCDKey = m2.myCDKey
WHERE m1.PK is null or m2.PK is null
The fiddle is here: SQL Fiddle

SQL Join on tables with from to entries

I have two tables that I want to join. However the information is written as from to in the first table.
My first table looks like this:
No. | Date | From entry | To Entry |
+---+------------+------------+----------+
1 | 21.12.2013 | 3 | 10
My second table looks like this:
| Entry | Code |
+--------+-------+
| 3 | 1 |
| 4 | 0 |
| 5 | 2 |
| 6 | 3 |
| 7 | 1 |
| 8 | 0 |
| 9 | 6 |
| 10 | 1 |
I want to join both based on the from to information. The result should look like this:
| Entry | Code | Date |
+--------+-------+------------+
| 3 | 1 | 21.12.2013 |
| 4 | 0 | 21.12.2013 |
| 5 | 2 | 21.12.2013 |
| 6 | 3 | 21.12.2013 |
| 7 | 1 | 21.12.2013 |
| 8 | 0 | 21.12.2013 |
| 9 | 6 | 21.12.2013 |
| 10 | 1 | 21.12.2013 |
I have no idea how to achieve this with t-sql.
You just need to specify correct condition for the INNER JOIN
SELECT *
FROM table1
INNER JOIN table2 ON table2.Entry >= table1.[From entry]
AND table2.Entry <= table1.[To entry]
Or you may consider using LEFT JOIN if you want to return all records from table1 no matter if they have related records in table2.
Try this: (INNER JOIN and BETWEEN AND)
SELECT
Entry, Code, Date
FROM
Table1 T1 INNER JOIN
Table2 T2
ON T2.Entry BETWEEN T1.[From entry] AND T1.[To entry]
try this
Select table2.*,
table1.date
from table2
left join table1 on
table2.entry between table1.fromentry and table1.toentry
SQLFIDDLE
This should work
SELECT B.Entry,B.Code,A.Date
FROM Table1 A CROSSJOIN Table2 B
WHERE B.Entry >= A.[From Entry] AND B.Entry <= A.[To Entry]
SELECT Entry, Code, (SELECT T1.Date
FROM FirstTable T1
WHERE T1.Entry BETWEEN T2.[From Entry]
AND T2.[To Entry]) AS Date
FROM SecondTable T2

SQL Query to Join Two Tables Based On Closest Timestamp

I need to retrieve the records from dbo.transaction (transaction of all users-more than one transaction for each user) that having timestamp which is closest to the time in dbo.bal (current balance details of each user-only one record for each user)
ie, the resultant records should equal to the no of records in the dbo.bal
Here i tried the below query, am getting only the records less than the time in dbo.bal. But there are some record having timestamp greater than and closest to dbo.bal.time
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
WHERE TIMESTAMP <= dbo.bal.time
ORDER BY TIMESTAMP DESC) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC
here is my table structure,
dbo.transaction
---------------
| uid| userId | description| timestamp | credit | transactionBal
-------------------------------------------------------------------------
| 1 | 101 | buy credit1| 2012-01-25 03:23:31.624 | 100 | 500
| 2 | 102 | buy credit5| 2012-01-18 03:13:12.657 | 500 | 700
| 3 | 103 | buy credit3| 2012-01-15 02:16:34.667 | 300 | 300
| 4 | 101 | buy credit2| 2012-01-13 05:34:45.637 | 200 | 300
| 5 | 101 | buy credit1| 2012-01-12 07:45:21.457 | 100 | 100
| 6 | 102 | buy credit2| 2012-01-01 08:18:34.677 | 200 | 200
dbo.bal
-------
| uid| userId | balance | time |
-----------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 |
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 |
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 |
And the result should be like,
| Id | userId | balance | time | credit | transactionBal
-----------------------------------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 | 200 | 300
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 | 200 | 200
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 | 300 | 300
Please help me.. Any help is must appreciated...Thankyou
It would be helpful if you posted your table structures, but ...
I think your inner query needs a join condition. (That is not actually in your question)
Your ORDER BY clause in the inner query could be ABS(TIMESTAMP - DB0.BAL.TIME). That should give you the smallest difference between the 2.
Does that help ?
Based on the follwing Sql Fiddle http://sqlfiddle.com/#!3/7a900/15 I came up with ...
SELECT
bal.uid,
bal.userId,
bal.balance,
bal.time,
trn.timestamp,
trn.description,
datediff(ms, bal.time, trn.timestamp)
FROM
money_balances bal
JOIN money_transaction trn on
trn.userid = bal.userid and
trn.uid =
(
select top 1 uid
from money_transaction trn2
where trn2.userid = trn.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
)
WHERE
bal.time IS NOT NULL
ORDER BY
bal.time DESC
I cannot vouch for its performance because I know nothing of your data, but I believe it works.
I have simplified my answer - I believe what you need is
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
and from that you can derive all the datasets you need.
for example :
with matched_credits as
(
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
)
select
*
from
matched_credits mc
join money_balances mb on
mb.uid = mc.baluid
join money_transaction trn on
trn.uid = mc.tranuid
Try:
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
ORDER BY abs(datediff(ms, dbo.bal.time, TIMESTAMP))) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC