Merge, delete when not on Source - sql

I have an issue with a merge statement into a FACT TABLE
It was pretty simple until the users started to delete records from the source.
Current SQL:
Set Count = 1
WHEN NOT MATCHED
INSERT
WHEN MATCHED
UPDATED
New SQL:
So in this example, a record has been deleted from the source, it no longer matches but there is nothing to insert. I would like it the count to be set to 0.
WHEN DELETED FROM SOURCE
Set Count = 0
.
Source
Bob Jones | 1111
Mary Jones | 1112
James Jones | 1113
Helen Jones | 1114
TARGET
Bob Jones | 1111 | Count 1
Mary Jones | 1112| Count 1
James Jones | 1113| Count 1
Helen Jones | | 1114| Count 1
Peter Market | 1115| Count 0
I’m loading to a fact table using a merge and now they are just blanket deleting records, my facts are off. This must be accounted for somehow?
Thank you for any help at all.

You could do a merge using the target full outer joined with source, but id has to a unique key across both for this to work.
MERGE INTO target tgt
USING (SELECT CASE
WHEN s.id IS NULL THEN t.name --write this logic for all other non id columns.
ELSE s.name
END AS name,
coalesce(s.id, t.id) AS id, --use target's id when no source id is available
CASE
WHEN s.id IS NULL THEN 0 --zero for non matching ids
ELSE 1
END AS source_count
FROM target t
full outer join source s
ON s.id = t.id) src
ON ( src.id=tgt.id)
WHEN matched THEN
UPDATE SET tgt.name = src.name,
tgt.source_count = src.source_count
WHEN NOT matched THEN
INSERT (id,
name,
source_count)
VALUES(src.id,
src.name,
1) ; --insert 1 by default for new rows
Demo

Related

Return based on values selected

I really don't know how to phrase the title correctly, so please excuse me if the title is confusing.
Here's the scenario I am facing:
I have a table...assuming it contains the follow rows.
Name | Value
----- | ----
John | 1
Mary | 2
Jack | 3
Jim | 4
Here's the PL/SQL requirement:
If John exists, return John and his value.
If John does not exist, but Mary does, return Mary and her value.
If neither John nor Mary exist, return either Jack or Jim whichever
has the higher value.
I was able to use cursor to traverse the table and test each row, but am wondering if there are other more efficient way.
Thanks!
No need for a cursor and a loop. You can do this in a single query, using a conditional sort and a fetch clause:
select *
from mytable
order by
case name when 'John' then 1 when 'Mary' then 2 else 3 end,
value desc
fetch first row only
Or if you are a pre-12c version of Oracle, where the fetch clause is not available:
select name, value
from (
select t.*,
row_number() over(order by
case name when 'John' then 1 when 'Mary' then 2 else 3 end,
value desc
) rn
from mytable t
) t
where rn = 1

SQL update values from table

I'm seriously struggling with some easy stuff.
Currently I have 2 tables (tmp_jmo and persons)
table tmp_jmo
----------
ID ADRESS PERSON_ID
115 Street 1 (null)
120 Street 2 (null)
121 Street 3 (null)
Table persons
ID NAME PERSON_ID
----------
115 John 14
120 Ellen 27
121 Mark 114
Now I want to update the Peson_id from tmp_jmo with the values from person_id (persons table)
In this case I've getting the error that there are to many values
Update tmp_jmo t SET person_id = persons.person_id where tmp_jmo.id = persons.id;
I've also tried to with temporary data but also failing.
I'm sorry to interrupt you with this kind of questions but it's ruining my day!
Many thanks!
In Standard SQL, you can do:
update tmp_jmo t
set person_id = (select p.person_id from persons p where tmp_jmo.id = p.id);
Many databases also support join or from in updates, but that is database-specific syntax.

How to bring together multiple delta tables?

I have a table with IDs and primary information. I also have two delta tables keyed on ID and date of change. I need to build a view that merges these three tables together indicating all changes over time.
Main Table:
ID Name
-- ------------------
1 Bob Jones
2 Dave Smith
First Attribute Table:
ID Date Attr1
-- ---------- -----
1 01/01/2013 25
1 02/15/2013 33
1 02/17/2013 47
1 03/02/2013 58
2 02/01/2013 1
...
Second Attribute Table
ID Date Attr2
-- ---------- -----
1 01/01/2013 ABC
1 01/05/2013 DEF
1 01/15/2013 RST
1 02/10/2013 XYZ
1 02/15/2013 Foo
1 03/05/2013 Blah
2 02/01/2013 Two
...
Based on that data, for Bob Jones, I need the view to return the following:
ID Name Date Attr1 Attr2
-- ----------- ---------- ----- -----
1 Bob Jones 01/01/2013 25 ABC
1 Bob Jones 01/05/2013 25 DEF
1 Bob Jones 01/15/2013 25 RST
1 Bob Jones 02/10/2013 25 XYZ
1 Bob Jones 02/15/2013 33 Foo
1 Bob Jones 02/17/2013 47 Foo
1 Bob Jones 03/02/2013 58 Foo
1 Bob Jones 03/05/2013 58 Blah
I tried outer joining the attribute tables to get all change values ordered by date and then used an outer join on the entire query with itself to get "prior" records:
with qry as (
select
rownum = ROW_NUMBER() OVER (ORDER BY m.ID, a.DATE),
m.ID,
m.Name,
a.DATE,
a.Attr1,
a.Attr2
from Main m
inner join (
select
COALESCE(a1.ID, a2.ID) as ID,
COALESCE(a1.LOAD_DATE, a2.LOAD_DATE) as LOAD_DATE,
a1.Attr1,
a2.Attr2
from Attributes1 a1
full outer join Attributes2 a2
on (a1.ID = a2.ID and a1.DATE = a2.DATE)
) a on (a.ID = m.ID)
)
select
COALESCE(qry.ID, prev.ID) as ID,
COALESCE(qry.Name, prev.Name) as Name,
COALESCE(qry.DATE, prev.DATE) as DATE,
COALESCE(qry.Attr1, prev.Attr1) as Attr1,
COALESCE(qry.Attr2, prev.Attr2) as Attr2,
from qry
left join qry prev
on (prev.rownum = qry.rownum - 1)
order by ID, DATE
However, that doesn't work when one attribute table changes quicker than the other because the attributes that didn't change are null in the results of the attribute table join and if two nulls show up back-to-back, the coalesce will return a null when I need the last non-null value that was in that column.
Can this even be done in a view in SQL Server 2012?

Updating multiple fields in a column simultaneously Microsoft Access

Say I have the following table:
App owner owner_id
______________________
1 John NULL
2 Jack 000123
3 John NULL
4 April 000124
5 John NULL
6 April 000124
7 John 000123
8 Ash NULL
9 Ash NULL
10 Ash NULL
If I need to update John and Ash's owner_ids, is there a way to update each owner's owner_id for every occurrence in the table by only entering the information once?
I have to do this for about 1000 owners, but with the amount of duplicates I am looking at around 10000 blank owner_ids.
I do not have the proper rights to restructure or split the table.
If I didn't misunderstand your question, it's just:
update owners_table set owner_id = '000001' where owner = 'john'
update owners_table set owner_id = '000002' where owner = 'ash'
This will set the owner_id to 000001 in every row where the owner is John, and to 000002 in every row where the owner is Ash.
It's just more copy & paste when you have a lot of owners, because you have to create one query for each owner.
Another way would be to create a temporary table with just the owner and his new id:
owner newid
--------------
John 000001
Ash 000002
You can enter hundreds or thousands of owners and their new owner_ids into the table.
Then, you can update them in your main table all at once with the following query:
UPDATE owners_table
INNER JOIN ownertmp ON owners_table .owner = ownertmp.owner
SET owners_table.owner_id = [ownertmp].[newid];
After that, you can delete the temporary table again - it's just needed to run the query.

How to join a one-to-many relationship to ensure that only 1 row from the left comes back?

Sorry about a vague title, but here's what i am trying to do.
I have the following two tables
TABLE_PERSON | TABLE_PHONE
|
col_Name | col_Name col_phoneID col_phoneNbr
-------- | -------- ----------- ------------
Clark Kent | Clark Kent 1 111-111-1111
Bruce Wayne | Clark Kent 2 222-222-2222
Peter Parker | Peter Parker 2 333-333-3333
| Peter Parker 3 444-444-4444
| Bruce Wayne 3 555-555-5555
| Bruce Wayne 4 666-666-6666
The col_Name is actually an ID field, but i am using a name to demostrate. Here's what i want to get by using one SQL statement
col_Name col_phoneNbr
-------- ------------
Clark Kent 111-111-1111
Peter Parker 333-333-3333
Bruce Wayne 555-555-5555
The phone_ID is actually a type of phone number such as main, fax, toll-free, etc. So in my output i would like to have a phone number for each individual, but be selected in a such a way that if type "1" is not available, select type "2", if type "2" is not there, select type "3", etc. In my case there are 6 types, and their priority is something like 4 > 2 > 1 > 3 > 5 > 6, so i would like to be able to select only the first available number, and NULL if neither is there.
I did this in a previous project by using a very convoluted method where i would select all type "1" and then all type "2" while filtering type "1" using a "NOT IN" clause and then UNIONing the two sets together. Actually, it ended up being like 4 sets, and for the 1000+ records i was pulling out, it ran really slow.
I should mention that this is MS SQL Server 2000, so i can't use any 2005 features.
Thanks in advance!
SELECT
pe.col_Name,
(SELECT TOP (1)
ph.col_PhoneNbr
FROM TABLE_PHONE ph
WHERE pe.col_Name = ph.col_Name
ORDER BY
CASE col_phoneID
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 1 THEN 3
WHEN 3 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 6
END
) as col_phoneNbr
FROM TABLE_PERSON pe
The simple case where the priority of the phone ID is its numeric value:
select pers.col_Name, ph2.col_phoneNbr
from TABLE_PERSON pers
inner join (select col_Name, min(col_phoneID) from TABLE_PHONE group by col_Name) as ph1 on ph1.col_Name=pers.col_Name
inner join TABLE_PHONE ph2 on ph2.col_Name=pers.col_Name and ph2.col_PhoneID=ph1.col_PhoneID
Now, let's create a mapping to handle the priority <-> ID:
Try this:
create table phone_prio (phone_ID int, prio int);
insert into phone_prio (phone_ID, prio) values (4, 1);
insert into phone_prio (phone_ID, prio) values (2, 2);
insert into phone_prio (phone_ID, prio) values (1, 3);
insert into phone_prio (phone_ID, prio) values (3, 4);
insert into phone_prio (phone_ID, prio) values (5, 5);
insert into phone_prio (phone_ID, prio) values (6, 6);
and update the naive case:
select pers.col_Name, ph2.col_phoneNbr
from TABLE_PERSON pers
inner join (
select col_Name, min(pr.prio) as prio
from TABLE_PHONE ph
inner join phone_prio pr on pr.phone_ID=ph.col_phoneID
group by col_Name
) as ph1 on ph1.col_Name=pers.col_Name
inner join phone_prio pr1 on pr.prio=ph1.prio
inner join TABLE_PHONE ph2 on ph2.col_Name=pers.col_Name and ph2.col_PhoneID=pr1.phoneID
You want to use the HAVING clause
GROUP by col_phoneID
HAVING col_phoneID = MAX(col_phoneID)
Obviously the MAX isn't what you want so you may have to add some things to your query
But I hope it points you in the right direction.