SQL Server: How to customize Merge When Matched area - tsql

See my code where I insert or update a table from XML using merge statement.
drop table if exists Student
Declare #Data xml
set #Data=
'<Root>
<Student>
<Name>Rakesh</Name>
<Marks>80</Marks>
</Student>
<Student>
<Name>Mahesh</Name>
<Marks>90</Marks>
</Student>
<Student>
<Name>Gowtham</Name>
<Marks>60</Marks>
</Student>
<Student>
<Name>Manoj</Name>
<Marks></Marks>
</Student>
</Root>'
create table Student (
Name varchar(10),
Marks int
)
insert into Student values
('Rakesh',90),
('Mahesh',80),
('Jack',80),
('Manoj',57)
DECLARE #archive TABLE
(
ActionType varchar(10),
Name varchar(10),
Marks int
);
Merge into Student as Trg
Using (select d.x.value('Name[1]','varchar(20)') as Name ,
d.x.value('Marks[1]','int') as Marks from
#data.nodes('/Root/Student')as d(x)) as Src
on Trg.Name=Src.Name
When Matched Then update set
Trg.Marks=Src.Marks
when not matched then
insert (Name,Marks) values (Src.Name,Src.Marks)
OUTPUT
$action ,
inserted.*
INTO #archive;
I want when Name match then UPDATE will perform but when Name match but Marks is empty then that records will be deleted based on name matched.
so tell me how could I customize this.
see my XML there is one student named Manoj whose marks are empty then that records will be removed from the table based on name matched.
How could I mention a condition in Merge that when Name matched and marks not empty then update and when Name matched but marks are empty then that records will be removed from the table.
Can i use Multiple matched condition ? if yes then it will be possible.
please guide me on how to achieve this. thanks

You should be able to use multiple WHEN MATCHED parts each with a clause search condition. One search checks that Src.Marks is not zero and the other checks that it was.
I've also put a clause search condition on the NOT MATCHED part as I would guess you don't want to be inserting people that have no marks either.
MERGE INTO Student as Trg
USING (
SELECT
d.x.value('Name[1]','varchar(20)') AS Name ,
d.x.value('Marks[1]','int') AS Marks
FROM
#data.nodes('/Root/Student') AS d(x)
) AS Src
ON Trg.Name=Src.Name
WHEN MATCHED AND Src.Marks <> 0 THEN
UPDATE
SET
Trg.Marks=Src.Marks
WHEN MATCHED AND Src.Marks = 0 THEN
DELETE
WHEN NOT MATCHED AND Src.Marks <> 0 THEN
INSERT (Name,Marks)
VALUES (Src.Name,Src.Marks)
OUTPUT
$action,
inserted.*
INTO #archive;

Related

Insert into when username matches

Suppose my table user_info has 2 columns, one is #username and another one is #info.
Now I already made a query "INSERT INTO user_info(username) value('')
How can I make another query to put data on column #info for the same username?? Because after the first query I'll have null for the column #info I believe.
Just to clarify, I don't get the #info when I have the username. Each user will get their info later. So I can't put then on the same query.
In that case, you need to update the row, simply using update on where the username exist
From documentation:
UPDATE table_name
SET column1 = value1,
column2 = value2,
...
WHERE condition;
Your case:
UPDATE user_info
SET info='new_information'
WHERE username='existing_username'

Teradata MERGE with DELETE and INSERT - syntax?

I have been trying to find the correct syntax for the following case (if it is possible?):
MERGE INTO TAB_A tgt
USING TAB_B src ON (src.F1 = tgt.F1 AND src.F2 = tgt.F2
WHEN MATCHED THEN DELETE
ELSE INSERT (tgt.*) VALUES (src.*)
Background: the temp table contains a fix for the target table, as in it contains two types of rows:
the incorrect rows that are to be removed (they match with rows in the target table), and the 'corrected' row that should be inserted (it replaces all the 'delete' rows).
So essentially: remove anything that matches;
insert anything that does not match.
the current error I am getting is:
"Syntax error: expected something between the 'DELETE' keyword and the 'ELSE' keyword"
Any help appreciated, thanks!
You can make use of MultiStatement DELETE and INSERT statement to correct data from temp table into target table
DELETE FROM TAB_A WHERE EXISTS (SELECT 1 FROM TAB_B WHERE TAB_A.F1 = TAB_B.F1 AND TAB_A.F2 = TAB_B.F2)
;INSERT INTO TAB_A SELECT * FROM TAB_B;

postgresql : search records based on array field vaule with multiple values

I have a table that has an array field.
CREATE TABLE notifications
(
id integer NOT NULL DEFAULT nextval('notifications_id_seq'::regclass),
title character(100) COLLATE pg_catalog."default" NOT NULL,
tags text[] COLLATE pg_catalog."default",
CONSTRAINT notifications_pkey PRIMARY KEY (id)
)
and tags field can have multiple values from
["a","b","c","d"]
now I want all the records for which tags have a or d ("a","d")array values.
I can use postgresl in but this can be used to search single value. How can I achieve this?
You could use ANY:
SELECT *
FROM notifications
WHERE 'a' = ANY(tags) OR 'b' = ANY(tags);
DBFiddle Demo
If the values 'a' and 'b' are static (you only need to check for those 2 values in every query), then you can go with the solution that Lukasz Szozda provided.
But if the values you want to check for are dynamic and are different in multiple queries(sometimes it is {'a','b'} but sometimes it is {'b', 'f','m'}) you can create an intersection of both of the arrays and check if the intersection is empty.
For example:
If we have the following table and data:
CREATE TABLE test_table_1(description TEXT, tags TEXT[]);
INSERT INTO test_table_1(description, tags) VALUES
('desc1', array['a','b','c']),
('desc2', array['c','d','e']);
If we want to get all of the rows from test_table_1 that have one of the following tags b, f, or m, we could do it with the following query:
SELECT * FROM test_table_1 tt1
WHERE array_length((SELECT array
(
SELECT UNNEST(tt1.tags)
INTERSECT
SELECT UNNEST(array['b','f','m'])
)), 1) > 0;
In the query above we use array_length to check if the intersection is empty.
Writing the query this way can also be useful if you want to add additional constraint to the number of matched tags.
For example if you want to get all of the rows that have at least 2 tags from the group {'a','b','c'} you just need to set array_length(...) > 1

sphinxsearch Delta index updates

I have a problem with Delta-index updates.
If the document id is less than the max_doc_id, is not included in the delta-index, so as long as main-index is not updated, the changes will not apply this data.
Suppose, we have 1000 data.
If fiftieth document is changed, there will be no changes in the delta-index.
How will delta-index include documents changes that their id is less than max_doc_id?
Is there a way that delta-index includes the data are updated so that we do not have to wait main-index run?
CREATE TABLE sph_counter
(
counter_id INTEGER PRIMARY KEY NOT NULL,
max_doc_id INTEGER NOT NULL
);
source main
{
# ...
sql_query_pre = SET NAMES utf8
sql_query_pre = REPLACE INTO sph_counter SELECT 1, MAX(id) FROM documents
sql_query = SELECT id, title, body FROM documents \
WHERE id<=( SELECT max_doc_id FROM sph_counter WHERE counter_id=1 )
}
source delta : main
{
sql_query_pre = SET NAMES utf8
sql_query = SELECT id, title, body FROM documents \
WHERE id>( SELECT max_doc_id FROM sph_counter WHERE counter_id=1 )
}
A really simply way I like for this is just to add a timestamp column to automatically track changed documents.
Add a column...
ALTER TABLE documents
ADD updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX(updated);
The default is also important, so it newly created documents are also included.
Then can just use that in queries, with a kill list. The main will include include all documents at time of indexing. But the delta will include new and changed documents. The kill list means the old version in main, is ignored.
CREATE TABLE sph_counter
(
counter_id INTEGER PRIMARY KEY NOT NULL,
max_doc_id INTEGER NOT NULL,
indexing_time DATETIME NOT NULL
);
source main
{
# ...
sql_query_pre = SET NAMES utf8
sql_query_pre = REPLACE INTO sph_counter SELECT 1, MAX(id), NOW() FROM documents
sql_query = SELECT id, title, body FROM documents
}
source delta : main
{
sql_query_pre = SET NAMES utf8
sql_query = SELECT id, title, body FROM documents \
WHERE updated > ( SELECT indexing_time FROM sph_counter WHERE counter_id=1 )
sql_query_killlist = SELECT id FROM documents \
WHERE updated > ( SELECT indexing_time FROM sph_counter WHERE counter_id=1 )
}
(as have the kill list, no point filtering the main, duplicates wont matter. Also dont neve need max_doc_id - so sph_counter could be simplified along with the sql_query_pre. In many way its a shame you have to repeat the query in the kill list. Can't just tell sphinx to use all the docs in the index as a kill list)
If you want to track document updates along with insertions, you should have a separate column for a document revision. Revision values should be unique across the document table, so it's a good idea to use global sequence to generate them.
When you update an existing document or insert a new one, you should take the next value from the revision sequence and save it in the document revision column. Sometimes it's a good idea to have DB triggers for automatic revision updates.
Then in sql_query_pre section you can save min and max revision values into sph_counter table and use them to create a proper delta index.

Add an attribute to the XML Column from another column in the same/another table

Here's my scenario:
--ORDER table
OrderID OrderCode DateShipped ShipmentXML
1 ABC 08/06/2013 <Order><Item CustomerName="BF" City="Philadelphia" State="PA"></Item></Order>
2 XYZ 08/05/2013 <Order><Item CustomerName="TJ" City="Richmond" State="VA"></Item></Order>
At some point in the process, I will know the respective TrackingNumber for these Orders. The tracking numbers are available in another table like this:
--TRACKING table
TrackingID OrderCode TrackingNumber
98 ABC 1Z1
99 XYZ 1Z2
The output I'm expecting is as below:
OrderID OrderCode ShipmentXML
1 ABC <Order><Item CustomerName="BF" City="Philadelphia" State="PA" DateShipped="08/06/2013" TrackingNumber="1Z1"></Item></Order>
2 XYZ <Order><Item CustomerName="TJ" City="Richmond" State="VA" DateShipped="08/05/2013" TrackingNumber="1Z2"></Item></Order>`
As you can see, I'm trying to get the TrackingNumber and the DateShipped for each OrderCode and have them as an attribute. The intent is a SELECT, not UPDATE.
All the examples I've seen demonstrate how to update the XML with a Constant value or a variable. I couldn't find one that demonstrates XML updates with a JOIN. Please help with how this can be accomplished.
UPDATE:
By 'Select not Update', I meant that no updates to the permanent table; UPDATE on temp tables are perfectly fine, as Mikael commented below the first answer.
A version using a temp table to add the attributes to the XML.
select OrderID,
OrderCode,
DateShipped,
ShipmentXML
into #Order
from [Order]
update #Order
set ShipmentXML.modify
('insert attribute DateShipped {sql:column("DateShipped")}
into (/Order/Item)[1]')
update O
set ShipmentXML.modify
('insert attribute TrackingNumber {sql:column("T.TrackingNumber")}
into (/Order/Item)[1]')
from #Order as O
inner join Tracking as T
on O.OrderCode = T.OrderCode
select OrderID,
OrderCode,
ShipmentXML
from #Order
drop table #Order
Prevous answer is good, but you have to explicitly specify columns and cast them into varchar, and that's not good for future support (if you add attributes to ShipmentXML you'll have to modify the query).
Instead, you could use XQuery:
select
O.OrderID, O.OrderCode,
(
select
(select O.DateShipped, T.TrackingNumber for xml raw('Item'), type),
O.ShipmentXML.query('Order/*')
for xml path(''), type
).query('<Order><Item>{for $i in Item/#* return $i}</Item></Order>')
from [ORDER] as O
left outer join [TRACKING] as T on T.OrderCode = O.OrderCode
or even like this:
select
O.OrderID, O.OrderCode,
O.ShipmentXML.query('
element Order {
element Item {
attribute DateShipped {sql:column("O.DateShipped")},
attribute TrackingNumber {sql:column("T.TrackingNumber")},
for $i in Order/Item/#* return $i
}
}')
from [ORDER] as O
left outer join [TRACKING] as T on T.OrderCode = O.OrderCode
see sqlfiddle with examples
The only way I know allowing partial modification of data in columns of xml type is using modify method, but as stated in documentation
The modify() method of the xml data type can only be used in the SET
clause of an UPDATE statement.
Since UPDATE is not desired, as a workaround I see shredding and reassembling it manually as:
select
o.OrderID,
o.OrderCode,
(
cast((select
t.c.value('#CustomerName', 'varchar(50)') as '#CustomerName',
t.c.value('#City', 'varchar(50)') as '#City',
t.c.value('#State', 'varchar(50)') as '#State',
o.DateShipped as '#DateShipped',
tr.TrackingNumber as '#TrackingNumber'
for xml path('Item'), root('Order')) as xml)
) as ShipmentXML
from
[ORDER] o
join [TRACKING] tr on tr.OrderCode = o.OrderCode
cross apply o.ShipmentXML.nodes('Order/Item') t(c)
You may have to apply formatting to o.DateShipped.