oracle sql query to get data from two tables of similar type - sql

I have two tables ACTUAL AND ESTIMATE having unique column(sal_id, gal_id, amount, tax).
In ACTUAL table I have
actual_id, sal_id, gal_id, process_flag, amount, tax
1 111 222 N 100 1
2 110 223 N 200 2
In ESTIMATE table I have
estimate_id, sal_id, gal_id, process_flag, amount, tax
3 111 222 N 50 1
4 123 250 N 150 2
5 212 312 Y 10 1
Now I want a final table, which should have record from ACTUAL table and if no record exist for sal_id+gal_id mapping in ACTUAL but exist in ESTIMATE, then populate estimate record (along with addition of amount and tax).
In FINAL table
id sal_id, gal_id, actual_id, estimate_id, total
1 111 222 1 null 101 (since record exist in actual table for 111 222)
2 110 223 2 null 202 (since record exist in actual table for 110 223)
3 123 250 null 4 51 (since record not exist in actual table but estimate exist for 123 250)
(for 212 312 combination in estimate, since record already processed, no need to process again).
I am using Oracle 11g. Please help me on writing a logic in a single sql query?
Thanks.

There are several ways to write this query. One way is to use join and coalesce:
select coalesce(a.sal_id, e.sal_id) as sal_id,
coalesce(a.gal_id, e.gal_id) as gal_id,
coalesce(a.actual_value, e.estimate_value) as actual_value
from actual a full outer join
estimate e
on a.sal_id = e.sal_id and
a.gal_id = e.gal_id
This assumes that sal_id/gal_id provides a unique match between the tables.
Since you are using Oracle, here is perhaps a clearer way of doing it:
select sal_id, gal_id, actual_value
from (select *,
max(isactual) over (partition by sal_id, gal_id) as hasactual
from ((select 1 as isactual, *
from actual
) union all
(select 0 as isactual, *
from estimate
)
) t
) t
where isactual = 1 or hasactual = 0
This query uses a window function to determine whether there is an actual record with the matching sal_id/gal_id. The logic is to take all actuals and then all records that have no match in the actuals.

Related

SQL DB2 Toad - Sum from two tables by ID

I was hoping to find the sum from two tables with columns ID and Amount, grouping by ID.
My first attempt was to UNION the two tables first and then conduct a sum and group by, but I was hoping to know of a better way.
Inputs:
Table 1
ID Amount
123 100
123 100
145 500
167 600
Table 2
ID Amount
123 100
123 100
145 500
199 600
Output
ID Amount
123 400
145 1000
167 600
199 600
You can do:
select id, sum(amount) as amount
from (
select id, amount from table_1
union all
select id, amount from table_2
) x
group by id

Dividing a sum value into multiple rows due to field length constraint

I am migrating financial data from a very large table (100 million+ of rows) by summarizing the amount and insert them into summary table. I ran into problem when the summary amount (3 billions) is larger than what the field in the summary table can hold (can only hold up to 999 millions.) Changing the field size is not an option as it requires a change process.
The only option I have is to divide the amount (the one that breach the size limit) into smaller ones so it can be inserted into the table.
I came across this SQL - I need to divide a total value into multiple rows in another table which is similar except the number of rows I need to insert is dynamic.
For simplicity, this is how the source table might look like
account_table
acct_num | amt
-------------------------------
101 125.00
101 550.00
101 650.00
101 375.00
101 475.00
102 15.00
103 325.00
103 875.00
104 200.00
104 275.00
The summary records are as follows
select acct_num, sum(amt)
from account_table
group by acct_num
Account Summary
acct_num | amt
-------------------------------
101 2175.00
102 15.00
103 1200.00
104 475.00
Assuming the maximum value in the destination table is 1000.00, the expected output will be
summary_table
acct_num | amt
-------------------------------
101 1000.00
101 1000.00
101 175.00
102 15.00
103 1000.00
103 200.00
104 475.00
How do I create a query to get the expected result? Thanks in advance.
You need a numbers table. If you have a handful of values, you can define it manually. Otherwise, you might have one on hand or use a similar logic:
with n as (
select (rownum - 1) as n
from account_table
where rownum <= 10
),
a as (
select acct_num, sum(amt) as amt
from account_table
group by acct_num
)
select acct_num,
(case when (n.n + 1) * 1000 < amt then 1000
else amt - n.n * 1000
end) as amt
from a join
n
on n.n * 1000 < amt ;
A variation along these lines might give some ideas (using the 1,000 of your sample data):
WITH summary AS (
SELECT acct_num
,TRUNC(SUM(amt) / 1000) AS times
,MOD(SUM(amt), 1000) AS remainder
FROM account_table
GROUP BY acct_num
), x(acct_num, times, remainder) AS (
SELECT acct_num, times, remainder
FROM summary
UNION ALL
SELECT s.acct_num, x.times - 1, s.remainder
FROM summary s
,x
WHERE s.acct_num = x.acct_num
AND x.times > 0
)
SELECT acct_num
,CASE WHEN times = 0 THEN remainder ELSE 1000 END AS amt
FROM x
ORDER BY acct_num, amt DESC
The idea is to first build a summary table with div and modulo:
ACCT_NUM TIMES REMAINDER
101 2 175
102 0 15
103 1 200
104 0 475
Then perform a hierarchical query on the summary table based on the number of "times" (i.e. rows) you want, with an extra for the remainder.
ACCT_NUM AMT
101 1000
101 1000
101 175
102 15
103 1000
103 200
104 475

Distributing Records Evenly From One Table to Another

I have 3 tables:
Users
-----
UserID (varchar)
Active (bit)
Refunds_Upload
--------------
BorrowerNumber (varchar)
Refunds
-------
BorrowerNumber
UserID
I first select all of the UserID values where Active = 1.
I need to insert the records from Refunds_Upload to Refunds but I need to insert the same (or as close as possible) number of records for each Active UserID.
For example, if Refunds_Upload has 20 records and the Users table has 5 people where Active = 1, then I would need to insert 4 records per UserID into table Refunds.
End Result would be:
BorrowerNumber UserID
105 Fred
110 Fred
111 Fred
115 Fred
120 Billy
122 Billy
123 Billy
125 Billy
130 Lucius
131 Lucius
133 Lucius
135 Lucius
138 Lucy
139 Lucy
140 Lucy
141 Lucy
142 Grady
143 Grady
144 Grady
145 Grady
Of course, it won't always come to an even number of records per User so I need to account for that as well.
First run this and check it returns something like what you want to insert, before you uncomment the insert and actually carry it out..
--INSERT INTO Refunds
SELECT
numbered_u.UserID,
numbered_ru.BorrowerNumber
FROM
(SELECT u.*, ROW_NUMBER() OVER(ORDER BY UserID) - 1 as rown, SUM(CAST(Active as INT)) OVER() as count_users FROM Users u WHERE active=1) numbered_u
INNER JOIN
(SELECT ru.*, ROW_NUMBER() OVER(ORDER BY BorrowerNumber) - 1 as rown, COUNT(*) OVER() as count_ru FROM Refund_Uploads ru) numbered_ru
ON
ROUND(CAST(numbered_ru.rown AS FLOAT) / (count_ru / count_users)) = numbered_u.rown
The logic:
We number every interesting (active=1) row in users and we also count them all. This should return us all 5 users, numbered 0 to 4 and with a ctr that is 5 on each row.
Then we join them to a similarly numbered list of Refund_Uploads (say 20). Similarly, those rows will be numbered 0 to 19 for mathematical reasons that become apparent later. We also count all these rows too
And we then join these two datasets together but the condition is a range of values rather than exact values. The logic is "refund_upload row number, divided by the_count_of_rows_there_should_be_per_user" (i.e. 0..19 / (20/5) ) = user_row_number. Hopefully thus refund rows 0 to 3, associate with user 0, refund rows 4 thru 7 associate with user 1.. etc
It's a little hard to debug without full data - I feel it might need a few +1 / -1 tweaks here and there.
I originally used FLOOR but switched to using ROUND, as I think this might work for distributing sets of numbers where there isn't a whole number of divisions in Refund/User e.g. your 240/13 example.. Hopefully some users will have 18 rows and some 19

How Do I Select All Parents and the Top Previous Child Record Based on Dates in SQL Server 2008

I'm using a vendor provided database running on SQL Server 2008. There are two tables that track tests. For every record in Table A there may be zero, one or multiple records in Table B. There can also be multiple tests in Table A for the same user. The relationship is TableA.UserID = TableB.UserID. Tests taken in Table B can occur before or after Table A.
I need to select all of the records in Table A and, if test(s) from Table B have been taken by the same user before the test in Table A, data from Table B but only from the last previous child record. Both tables are structured similarly:
**TABLE A**
TestID INTEGER PRIMARY KEY,
UserID INTEGER,
TestDate DATE,
Score INTEGER
TABLE B
TestID INTEGER PRIMARY KEY,
UserID INTEGER,
TestDate Date,
Score INTEGER
Sample Data
TABLE A
TestID UserID TestDate Score
1 100 2014-02-15 80
2 101 2014-02-20 100
3 102 2014-02-22 90
4 102 2014-03-10 70
TABLE B
TestID UserID TestDate Score
1000 100 2014-02-01 55
1007 100 2014-02-05 85
1012 100 2014-02-20 95
1034 102 2014-02-12 65
1205 102 2014-03-05 75
1986 101 2014-03-10 45
What I'd like returned would be:
UserID TestA_ID TestADate TestAScore TestB_ID TestBDate TestBScore
100 1 2014-02-15 80 1007 2014-02-05 85
101 2 2014-02-20 100 NULL NULL NULL
102 3 2014-02-22 90 1034 2014-02-12 65
102 4 2014-03-10 70 1205 2014-03-05 75
I've know how to get all of the previous Table B rows joined to the Table A rows by using a LEFT OUTER JOIN and filtering by date in the WHERE clause, and I know how to get the Top row from Table B, but I haven't been able to work out how to get the top child record that occurs before the date of the record in Table A. Any help would be appreciated. Thanks.
You can do this using OUTER APPLY in T-SQL.
For each record in TableA, we're looking for a record in TableB for the same user but with a test date prior to the test date in TableA and we're also ordering the test in TableB to ensure we're getting the most recent test from TableB (but still prior to the test date from TableA).
SELECT
A.[UserID],
A.[TestID] [TestA_ID],
A.[TestDate] [TestADate],
A.[Score] [TestAScore],
B.[TestB_ID],
B.[TestBDate],
B.[TestBScore]
FROM [TableA] A
OUTER APPLY
(
SELECT TOP 1
B1.[TestID] [TestB_ID],
B1.[TestDate] [TestBDate],
B1.[Score] [TestBScore]
FROM [TableB] B1
WHERE A.[UserID] = B1.[UserID]
AND A.[TestDate] > B1.[TestDate]
ORDER BY
B1.[TestDate] DESC
) B
Or another option might be to use the ROW_NUMBER() window function to find the record from TableB. I have a hunch this one wouldn't perform as well because it needs to hit TableA twice, but can't be sure without running tests.
SELECT
A.[UserID],
A.[TestID] [TestA_ID],
A.[TestDate] [TestADate],
A.[Score] [TestAScore],
B.[TestB_ID],
B.[TestBDate],
B.[TestBScore]
FROM [TableA] A
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY A.[UserID], A.[TestID] ORDER BY B.[TestDate] DESC) [rn],
A.[UserID],
A.[TestID] [TestA_ID],
B.[TestID] [TestB_ID],
B.[TestDate] [TestBDate],
B.[Score] [TestBScore]
FROM [TableA] A
INNER JOIN [TableB] B
ON A.[UserID] = B.[UserID]
AND A.[TestDate] > B.[TestDate]
) B
ON A.[UserID] = B.[UserID]
AND A.[TestID] = B.[TestA_ID]
AND B.[rn] = 1

SQL multiple 1-to-many joins

I'm almost certain I've run into this before and am just having an extended senior moment, but I am trying to pull work order data from three different tables across 2 db's on a SQL instance and combine it all into a report, I'm looking for the end result to contain the following columns:
WO | Production Recorded Qty | Inventory Qty | Variance
The Variance part is easy I can just nest the select statement, and subtract the two quantities in the outer statement, but the problem I'm running in to is when I join the production and Inventory tables in their corresponding databases I end up getting sums of the columns that I'm targeting that are way larger than what they should be
Sample Data:
Work Order, Short P/N, and Long P/N in Work Order Table:
dba.1
WO ShortPN LongPN
152 1234 Part1
Short P/N, Quantity on hand, location, and lot # in inventory table:
dba.2
ShortPN Qty Loc Lot
1234 31 Loc1 456
1234 0 Loc2 456
1234 0 Loc4 456
1234 19 Loc1 789
1234 25 Loc4 789
Work Order, Long P/N, and production count of the last 5min in Production table:
dbb.3
WO LongPN Count
152 Part3 6
152 Part3 8
152 Part3 9
152 Part3 4
152 Part3 6
152 Part3 7
With this example I've tried:
SELECT 1.WO AS WO
,SUM(2.Qty) AS "Qty On Hand"
,SUM(3.Count) AS "Produced Qty"
FROM dba.1
INNER JOIN dbb.2 ON 1.ShortPN=2.ShortPN
INNER JOIN dbb.3 ON 1.WO = 3.WO
GROUP BY 1.WO
I've also tried selecting from 3, joining 1 to 3 on the WO, then 2 to 1 on shortPN, and both yield SUM() numbers that are exponentially higher than they should be(ie what should be 15,xxx turns into over 2,000,000), however if I remove one of the data points from the report and select just the inventory or production qty I get the correct end results. I swear that I've run into this before but for the life of me can't remember how it was solved, sorry if it's a duplicate question as well, couldn't find anything by searching.
Thanks in advance for the help, it's greatly appreciated.
You can do something like this
select
WO.WO, isnull(i.Qty, 0) as Qty, isnull(p.[Count], 0) as [Count]
from WorkOrder as WO
left outer join (select t.ShortPN, sum(t.Qty) as Qty from inventory as t group by t.ShortPN) as i on
i.ShortPN = WO.ShortPN
left outer join (select t.WO, sum(t.[Count]) as [Count] from Production as t group by t.WO) as p on
p.WO = WO.WO
SQL FIDDLE example
if you have SQL Server 2005 or higher, you can write it like this
select
WO.WO, isnull(i.Qty, 0) as Qty, isnull(p.[Count], 0) as [Count]
from WorkOrder as WO
outer apply (select sum(t.Qty) as Qty from inventory as t where t.ShortPN = WO.ShortPN) as i
outer apply (select sum(t.[Count]) as [Count] from Production as t where t.WO = WO.WO) as p
SQL FIDDLE example
this happens because when you make a join of WO and inventory tables you got
WO SHORTPN QTY
-------------------
152 1234 31
152 1234 0
152 1234 0
152 1234 19
152 1234 25
and you see that now you have 5 rows with WO = 152. When you add join with Production table, for each row with WO = 152 from this join there will be 6 rows with WO = 152 from Production table, so you will have 30 rows total and QTY from inventory will be listed 6 times each. When you sum this up, instead of 75 you will have 75 * 6 = 450. And for Count you'll have each Count * 5, so instead of 40 you'll have 200.