Join that will return the same amount of records found on the left table - sql

I am having some trouble trying to figure out how to write a sql statement that will return the same amount of records found in the left table.
For instance we have two tables, Transactions and Partners. Due to how the tables were originally designed there does not exist a way to retrieve an exact matching pair. IE. A transaction could have many partners that it relates to.
What I am looking to do is display all the transactions with a Partner ID. If a transaction has more than one matching Partner ID then I need to take the first occurrence of the match and throw away the rest. If a transaction does not have a matching Partner ID, I still need to display it, but with an empty or null value for the Partner ID.
Transaction Table
Transaction ID | ID 1 | ID 2
-------------- +---------+----------
T1 | A | 1
T2 | C | 3
T3 | B | 1
T4 | D | 4
T5 | A | 2
Partner Table
Transaction ID | ID 1 | ID 2
---------------+---------+----------
P1 | A | 1
P2 | B | 2
P3 | C | 3
P4 | C | 3
P5 | D | 4
Desired Results
Transaction ID| ID 1 | ID 2 | Partner ID
--------------+---------+----------+-----------
T1 | A | 1 | P1
T2 | C | 3 | P3
T3 | B | 1 | Null
T4 | D | 4 | P5
T5 | A | 2 | Null
I feel like I need some form of outer join to make sure no transactions are not queried, but I cant decipher how to make sure no duplicate transactions are displayed.
Thanks

SQL tables have no concept of "first" record, without some column to specify the ordering. But, the basic idea of what you want uses left join and row_number():
select t.*, p.partnerid
from transaction t left join
(select p.*,
row_number() over (partition by id1, id2 order by partnerid) as seqnum
from partner p
) p
on t.id1 = p.id1 and t.id2 = p.id2 and p.seqnum = 1;
This version takes "first" to mean "lowest value of partner id".

You can use an outer join with row_number:
select *
from (
select t1.transactionid, t1.id1, t1.id2, t2.transactionid as partnerid,
row_number() over (partition by t1.transactionid order by t2.transactionid) rn
from Transaction t1
left join Partner t2 on t1.id1 = t2.id1 and t1.id2 = t2.id2
) t
where rn = 1
This selects all records from transaction table and then only 1 from the partner table if it exists.

I'm not sure how you know you don't want P4 partnered with T2 as well as P3, but I found this worked for me, assuming it's the lowest ID:
select t1.transactionid, t1.id1, t1.id2, min(t2.transactionid) as partnerid
from Transaction t1, Partner t2 where t1.id1 = t2.id1(+) and t1.id2 = t2.id2(+)
group by t1.transactionid, t1.id1, t1.id2
order by t1.transactionid, t1.id1, t1.id2

Related

How to populate a table based on a value from a different table

I have two tables of data which I can join using a left join linked on the ID in both tables. Where the course and the person are the same, I need to populate the RegNumber as the same as the RegNumber which is already there for 1 row:
How it is currently: if I join table 1 and table 2 with a left join.
Table 1
ID | Course| Person
67705 | A | 1
68521 | A | 1
85742 | A | 1
89625 | A | 1
67857 | B | 2
86694 | B | 2
88075 | B | 2
88710 | C | 3
47924 | C | 3
66981 | C | 3
12311 | B | 1
12312 | B | 1
12313 | B | 1
Table 2
ID | RegNumber
67705 | N712316
NULL | NULL
NULL | NULL
NULL | NULL
67857 | N712338
NULL | NULL
NULL | NULL
NULL | NULL
47924 | M481035
NULL | NULL
12311 | N645525
NULL | NULL
NULL | NULL
I need table 2 to look like this:
ID | RegNumber
67705 | N712316
68521 | N712316
85742 | N712316
89625 | N712316
67857 | N712338
86694 | N712338
88075 | N712338
88710 | N712338
47924 | M481035
66981 | M481035
12311 | N645525
12312 | N645525
12313 | N645525
That is, I need to insert new rows into Table 2
Can anyone help me please? This is Totally beyond my capability!
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from table2 r2 join table1 r1
on r1.Id = r2.Id
and r1.Course = t1.Course
and r1.Person = t1.person
order by id) reg
where not exists (select 1 from table2 t2 where t1.ID = t2.ID)
you can improve performance a little bit by loading data into temp table first :
select t1.ID , Course,Person,regNumber
into #LoadedData
from table1 t1
join table2 t2 on t1.Id = t2.ID
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from #LoadedData l
where l.Course = t1.Course
and l.Person = t1.person
order by id) reg
where not exists (select 1 from #LoadedData l where t1.ID = l.ID)
in either case having an index on (ID, Course, Person) will help with performance
Assuming:
You are missing items in table 2 that inherit data from other records in table 1.
What makes two different IDs share the same Regnumber is to have BOTH course and person number in common.
You really need to join table 1 to itself to create the mapping that associates ID 67705 with ID 68521, then you can join in table 2 to pick up the Regnumber.
Try this:
Insert into table2 (ID,RegNumber)
Select right1.ID, left2.RegNumber
From (
(table2 left2 INNER JOIN
table1 left1 On (left1.ID=left2.ID)
INNER JOIN table1 right1 On (left1.Course=right1.Course AND left1.Person=right1.Person)
) LEFT OUTER JOIN table2 right2 On (right1.ID=right2.ID)
WHERE right2.ID Is Null
The 4th table join (alias right2) is purely defensive, to handle two records in table2 having identical Person & Course in table1.
I have solved this myself.
I concatenated the person and course columns and then joined them using that new concatenated field
insert into table 2 (ID,RegNumber)
select X1.ID,X2.Regnumber
from (select concat(course,person) as X,ID from table1) X1
join (select concat(t1.course,t1.person) as X, t2.RegNumber
from table1 t1
join table2 t2 on t1.ID = t2.ID) X2
on X1.X = X2.X
where X1.ID not in (select ID from table2)

How to do an outer join with full result between two tables

I have two tables:
TABLE1
id_attr
-------
1
2
3
TABLE2
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
As a result I want a table that show:
RESULT
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
So I want the row with id=10 and id_attr=3 also when id_Attr=3 is missing in TABLE2 (and I know that because I have a NULL value (or something else) in the val column of RESULT.
NB: I could have others ids in table2. For example, after insert this row on table2: {11,1,A}, as RESULT I want:
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
11 | 1 | A
11 | 2 | NULL
11 | 3 | NULL
So, for every id, I want always the match with all id_attr.
Your specific example only has one id, so you can use the following:
select t2.id, t2.id_attr, t2.val
from table2 t2
union all
select 10, t1.id_attr, NULL
from table1 t1
where not exists (select 1 from table2 t2 where t2.id_attr = t1.id_attr);
EDIT:
You can get all combinations of attributes and ids in the following way. Use a cross join to create all the rows you want and then a left join to bring in the data you want:
select i.id, t1.id_attr, t2.val
from (select distinct id from table2) i cross join
table1 t1 left join
table2 t2
on t2.id = i.id and t2.id_attr = t1.id_attr;
It sounds like you want to do just an outer join on id_attr instead of id.
select * from table2 t2
left outer join table1 t1 on t2.id_attr = t1.id_attr;

What is the correct way from performance perspective to match(replace) every value in every row in temp table using SQL Server 2016 or 2017?

I am wondering what should I use in SQL Server 2016 or 2017 (CTE, LOOP, JOINS, CURSOR, REPLACE, etc) to match (replace) every value in every row in temp table? What is the best solution from performance perspective?
Source Table
|id |id2|
| 1 | 2 |
| 2 | 1 |
| 1 | 1 |
| 2 | 2 |
Mapping Table
|id |newid|
| 1 | 3 |
| 2 | 4 |
Expected result
|id |id2|
| 3 | 4 |
| 4 | 3 |
| 3 | 3 |
| 4 | 4 |
You may join the second table to the first table twice:
WITH cte AS (
SELECT
t1.id AS id_old,
t1.id2 AS id2_old,
t2a.newid AS id_new,
t2b.newid AS id2_new
FROM table1 t1
LEFT JOIN table2 t2a
ON t1.id = t2a.id
LEFT JOIN table2 t2b
ON t1.id2 = t2b.id
)
UPDATE cte
SET
id_old = id_new,
id2_old = id2_new;
Demo
Not sure if you want just a select here, or maybe an update, or an insert into another table. In any case, the core logic I gave above should work for all these cases.
You'd need to apply joins on update query. Something like this:
Update tblA set column1 = 'something', column2 = 'something'
from actualName tblA
inner join MappingTable tblB
on tblA.ID = tblB.ID
this query will compare eachrow with ids and if matched then it will update/replace the value of the column as you desire. :)
Do the self join only
SELECT t1.id2 as id, t2.id2
FROM table1 t
INNER JOIN table2 t1 on t1.id = t.id
INNER JOIN table2 t2 on t2.id = t.id2
This may have best performance from solutions posted here if you have indexes set appropriately:
select (select [newid] from MappingTable where id = [ST].[id]) [id],
(select [newid] from MappingTable where id = [ST].[id2]) [id2]
from SourecTable [ST]

Oracle Efficiently joining tables with subquery in FROM

Table 1:
| account_no | **other columns**...
+------------+-----------------------
| 1 |
| 2 |
| 3 |
| 4 |
Table 2:
| account_no | TX_No | Balance | History |
+------------+-------+---------+------------+
| 1 | 123 | 123 | 12.01.2011 |
| 1 | 234 | 2312 | 01.03.2011 |
| 3 | 232 | 212 | 19.02.2011 |
| 4 | 117 | 234 | 24.01.2011 |
I have multiple join query, one of the tables(Table 2) inside a query is problematic as it is a view which computes many other things, that is why each query to that table is costly. From Table 2, for each account_no in Table 1 I need the whole row with the greatest TX_NO, this is how I do it:
SELECT * FROM TABLE1 A LEFT JOIN
( SELECT
X.ACCOUNT_NO,
HISTORY,
X.BALANCE
FROM TABLE2 X INNER JOIN
(SELECT
ACCOUNT_NO,
MAX(TX_NO) AS TX_NO
FROM TABLE2
GROUP BY ACCOUNT_NO) Y ON X.ACCOUNT_NO = Y.ACCOUNT_NO) B
ON B.ACCOUNT_NO = A.ACCOUNT_NO
As I understand at first it will make the inner join for all the rows in Table2 and after that left join needed account_no's with Table1 which is what I would like to avoid.
My question: Is there a way to find the max(TX_NO) for only those accounts that are in Table1 instead of going through all? I think it will help to increase the speed of the query.
I think you are on the right track, but I don't think that you need to, and would not myself, nest the subqueries the way you have done. Instead, if you want to get each record from table 1 and the matching max record from table 2, you can try the following:
SELECT * FROM TABLE1 t1
LEFT JOIN
(
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY account_no ORDER BY TX_No DESC) rn
FROM TABLE2 t
) t2
ON t1.account_no = t2.account_no AND
t2.rn = 1
If you want to continue with your original approach, this is how I would do it:
SELECT *
FROM TABLE1 t1
LEFT JOIN TABLE2 t2
ON t1.account_no = t2.account_no
INNER JOIN
(
SELECT account_no, MAX(TX_No) AS max_tx_no
FROM TABLE2
GROUP BY account_no
) t3
ON t2.account_no = t3.account_no AND
t2.TX_No = t3.max_tx_no
Instead of using a window function to find the greatest record per account in TABLE2, we use a second join to a subquery instead. I would expect the window function approach to perform better than this double join approach, and once you get used to it can even easier to read.
If table1 is comparatiely less expensive then you could think of doing a left outer join first which would considerable decrease the resultset and from that pick the latest transaction id records alone
select <required columns> from
(
select f.<required_columns),row_number() over (partition by account_no order by tx_id desc ) as rn
from
(
a.*,b.tx_id,b.balance,b.History
from table1 a left outer join table2 b
on a.account_no=b.account_no
)f
)g where g.rn=1

Left join with different queries from same table where identifier has incremented suffix

I have a database that has three columns which are intresting for me in this query; Id, Counter and Value. Id is naturally the identifier and is unique. But there are multiple entries in the database that have the initial sequence with an additional suffix added to it.
Each of these entries correspond to a different counter (there can be a maximum of three) which have different values.
An example of what the data might look like;
| id | counter | value |
|--------|-------------|----------|
| 1234_1 | counter_1 | 1.0 |
| 1234_2 | counter_2 | 7.0 |
| 1234_3 | counter_3 | 5.0 |
| 2341_1 | counter_1 | 2.0 |
| 2341_2 | counter_2 | 6.0 |
| 3412_1 | counter_1 | 8.0 |
What I am trying to achieve is to get the values for each of the counters related to the first 4 digits in the ID. So if I were to get the values for '1234' And '2341' I would get the following result;
| id | value_1 | value_2 | value_3 |
| 1234 | 1.0 | 7.0 | 5.0 |
| 2341 | 5.0 | 2.0 | NULL |
I have built a query that uses left joins for each of the counter values, this works fine when retrieving values from one ID, but it will not function when getting values from two different IDs. The result then is more than (in this case) 2 results.
Current query;
select LEFT(t1.id,LEN(t1.id)-1)
, t1.value
, t2.value
, t3.value
from (select * from table
where id LIKE '1234%'
AND Counter = 'counter_1') t1
left join (Select * from table Where Counter = 'counter_2') t2
on LEFT(t2.id,LEN(t2.id)-1) = LEFT(t1.id,LEN(t1.id)-1)
left join (Select * from table Where Counter = 'counter_3') t3
on LEFT(t3.id,LEN(t3.id)-1) = LEFT(t1.id,LEN(t1.id)-1)
This works fine and will return the first line of the wanted result. If I however change the first from to include another id;
from (select * from table where id LIKE '1234%' OR [Id] LIKE '2341%' ...
It will return four values, where the first three have the id '1234'.
I am unable to affect the structure of the database in any way and the reason for choosing left join is to support if the values do not exist for the counters, it should be NULL.
What am I doing wrong?
what you want is this:
from (select * from [table]
where (id LIKE '1234%' OR [Id] LIKE '2341%')
AND Counter = 'counter_1') t1
because AND is done before OR, you get Data where ID is like 1234%, not looking at counter.
I assumed there is always a line with t1.Counter = 'counter_1' :
select LEFT(t1.id,LEN(t1.id)-2), t1.value, t2.value, t3.value
from MyTable t1
left join MyTable t2 on t2.Counter = 'counter_2' and LEFT(t2.id,LEN(t2.id)-2) = LEFT(t1.id,LEN(t1.id)-2)
left join Mytable t3 on t3.Counter = 'counter_3' and LEFT(t3.id,LEN(t3.id)-2) = LEFT(t1.id,LEN(t1.id)-2)
where t1.Counter = 'counter_1'
and t1.[id] LIKE '1234%' OR t1.[Id] LIKE '2341%'
If not, you can trick the query like this :
select LEFT(t1.id,LEN(t1.id)-2), t1.value, t2.value, t3.value
from (select 1 as 'c') t
left join MyTable t1 on t1.Counter = 'counter_1' and t1.[id] LIKE '1234%' OR t1.[Id] LIKE '2341%'
left join MyTable t2 on t2.Counter = 'counter_2' and LEFT(t2.id,LEN(t2.id)-2) = LEFT(t1.id,LEN(t1.id)-2)
left join Mytable t3 on t3.Counter = 'counter_3' and LEFT(t3.id,LEN(t3.id)-2) = LEFT(t1.id,LEN(t1.id)-2)