Oracle Efficiently joining tables with subquery in FROM - sql

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

Related

How to create a table with multiple calculations?

If I do one calculation with one join:
SELECT
SUM(friends_made) as calc1, table2.group_id
FROM
friends_made_table as table1
INNER JOIN
grouped_users as table2 ON table1.user_id = table2.user_id
GROUP BY
table2.group_id
The result I get is:
calc1 | group_id
-----------------
400 | 1
320 | 2
330 | 3
But I also need another calculation (calc2) with the same inner join on table1 but with a different table (table3)
SELECT
SUM(request_accept) AS calc2, table1.group_id
FROM
friends_accept_table AS table3
INNER JOIN
grouped_users as table1 ON table1.user_id = table3.user_id
GROUP BY
table1.group_id
Result is:
calc2 | group_id
-----------------
100 | 1
150 | 2
120 | 3
How can I join these two queries and create a new table showing both of the calculations (calc1, calc2)?
calc1 |calc2 | group_id
-----------------------
400 | 100 | 1
320 | 150. | 2
330 | 120. | 3
EDITED to show tables/results and take out rounding
A join will suffice as long as there is a common set of group_ids across the two results. You may otherwise need a left/right join or full join.
with data1 as (
SELECT SUM(friends_made) as calc1, table2.group_id
FROM friends_made_table as table1 INNER JOIN grouped_users as table2
ON table1.user_id = table2.user_id
GROUP BY table2.group_id
), data2 as (
SELECT SUM(request_accept) as calc2, table1.group_id
FROM friends_accept_table as table3 INNER JOIN grouped_users as table1
ON table1.user_id = table3.user_id
GROUP BY table1.group_id
)
select calc1, calc2, d1.group_id
from data1 d1 inner join data2 d2 on d2.group_id = d1.group_id;
This does assume that your platform supports CTE syntax. If it doesn't there are probably similar rewrites.

Compare columns from 2 different tables with only last inserted values in table_2 in SQL Server

If I have two different tables in a SQL Server 2019 database as follows:
Table1
|id | name |
+-----+--------+
| 1 | rose |
| 2 | peter |
| 3 | ann |
| 4 | rose |
| 5 | ann |
Table2
| name2 |
+--------+
|rose |
|ann |
I would like to retrieve only the last tow ids from table1 (which in this case 4 and 5) that match name2 in table2. In other words, match happens only once on the last added names in table1, furthermore, the ids (4, 5) to be inserted in table2.
How to do that using SQL?
Thank you
You can use row_number()
select name,id from
(
select *, row_number() over(partition by t.name order by id desc) as rn
from table1 t join table2 t1 on t.name=t1.name2
)A where rn=1
Your question is vague, so there could be many answers here. My first thought is that you simply want an inner join. This will fetch ONLY the data that both tables share.
SELECT Table1.*
FROM Table1
INNER JOIN Table2 on Table1.name = Table2.name2
You seem to be describing:
select . . . -- whatever columns you want
from (select top (2) t1.*
from table1 t1
order by t1.id desc
) t1 join
table2 t2
on t2.name2 = t1.name;
This doesn't seem particularly useful for the data you have provided, but it does what you describe.
EDIT:
If you want only the most recent rows that match, use row_number():
select . . . -- whatever columns you want
from (select t1.*,
row_number() over (partition by name order by id desc) as seqnum
from table1 t1
) t1 join
table2 t2
on t2.name2 = t1.name and t1.seqnum = 1;

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]

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

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

SQL Query - Get count of two columns from two tables

Table 1:
TicketNumber | Rules
---------------------------
PR123 | rule_123
PR123 | rule_234
PR123 | rule_456
PR999 | rule_abc
PR999 | rule_xyz
Table2:
TicketNumber | Rules
---------------------------
PR123 | rule_123
PR123 | rule_234
PR999 | rule_abc
NOTE: Both tables have the same structure: same column names but different count.
NOTE: Both tables have same set of TicketNumber values
CASE 1:
If I need ticket and rules count of each ticket from table1, the query is:
Select [TicketNo], COUNT([TicketNo]) AS Rules_Count from [Table1] group by TicketNo
This will give me output in format :
ticketNumber | Rules_Count
---------------------------
PR123 | 3
PR999 | 9
CASE 2: (NEED HELP WITH THIS)
Now, the previous query gets the ticket and the count of the ticket of only 1 table. I need the count of the same ticket (since both have same set of tkt nos) in table2 also.
I need result in this way:
ticketNumber | Count(ticketNumber) of table1 | Count(ticketNumber) of table2
---------------------------------------------------------------------------------
PR123 | 3 | 2
PR999 | 2 | 1
Both Table1 and table2 have the same set of ticket nos but different counts
How do i get the result as shown above?
A simpler solution from a "statement point of view" (without COALESCE that maybe it's not so easy to understand).
Pay attention to the performances:
Select T1.TicketNumber,T1.Rules_Count_1,T2.Rules_Count_2
FROM
(
Select [TicketNumber], COUNT([TicketNumber]) AS Rules_Count_1
from [Table1] T1
group by TicketNumber) T1
INNER JOIN
(
Select [TicketNumber], COUNT([TicketNumber]) AS Rules_Count_2
from [Table2] T2
group by TicketNumber
) T2
on T1.TicketNumber = T2.TicketNumber
SQL Fiddle Demo
You can do this with a full outer join after aggregation (or an inner join if you really know that both tables have the same tickets:
select coalesce(t1.TicketNo, t2.TicketNo) as TicketNo,
coalesce(t1.Rules_Count, 0) as t1_Rules_Count,
coalesce(t2.Rules_Count, 0) as t2_Rules_Count
from (Select [TicketNo], COUNT([TicketNo]) AS Rules_Count
from [Table1]
group by TicketNo
) t1 full outer join
(Select [TicketNo], COUNT([TicketNo]) AS Rules_Count
from [Table2]
group by TicketNo
) t2
on t1.TicketNo = t2.TicketNo;
SELECT A.center,
A.total_1st,
B.total_2nd
FROM (SELECT a.center,
Count (a.dose1) AS Total_1st
FROM table_1 a
GROUP BY a.center) A
INNER JOIN (SELECT b.center,
Count (b.dose2) AS Total_2nd
FROM table_2 b
GROUP BY b.center) B
ON a.center = b.center
ORDER BY A.center