When working with Change Tracking in SQL Server, you're supposed to use CHANGE_TRACKING_IS_COLUMN_IN_MASK to determine which column was changed when dealing with updates. For example, like so:
DECLARE #last_synchronization_version bigint = ...;
DECLARE #column_id int = ...;
-- The statement below returns 1 if the specified column (#column_id) was changed, otherwise 0.
SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK(#column_id, SYS_CHANGE_COLUMNS)
FROM CHANGETABLE(CHANGES dbo.MyTable, #last_synchronization_version) AS CT
I wonder, is there a way to implement CHANGE_TRACKING_IS_COLUMN_IN_MASK myself, so I can work with the value of SYS_CHANGE_COLUMNS in my application without having to know beforehand which columns my application is interested in when executing the query?
For example, when I only change the value of the column with ID 11, the value of SYS_CHANGE_COLUMNS is 0x000000000B000000.
How can I programmatically determine that this mask contains the information that column 11 was changed?
It turns out SYS_CHANGE_COLUMNS consists of a byte array that can be grouped into groups of 4 bytes. The more columns were changed, the longer the byte array will be, thus the more groups you can make. The first byte of each group represents an ID of a column that was changed. In all of my tests, the other 3 bytes of each group were empty (0). I assume these bytes will be used when you have a column ID larger than 255. It seems the order in which columns were changed determines the order in which they appear in the byte array. Also, the first group of 4 bytes will always be empty (0), I'm not sure why.
To use this in application code, all you need to do is get a mapping for each column name and it's respective column ID. The previous paragraph should explain how to use SYS_CHANGE_COLUMNS to determine which column ID appears in the byte array.
C# example:
public static IEnumerable<int> GetColumnIdsInMask(byte[] columns)
{
// TODO: deal with column IDs larger than 255
for (var i = 4; i < columns.Length; i += 4)
{
yield return columns[i];
}
}
public static bool IsColumnInMask(int columnId, byte[] columns)
{
return GetColumnIdsInMask.Any(x => x == columnId);
}
Related
I need to anonymize a variable in SQL data (VAR NAME = "ArId").
The variable contains 10 numbers + 1 letter + 2 numbers. I need to randomize the 10 first numbers and then keep the letter + the last two numbers.
I have tried the rand() function, but this randomize the whole value.
SELECT TOP 1000 *
FROM [XXXXXXXXXXX].[XXXXXXXXXX].[XXXXX.TEST]
I have only loaded the data.
EDIT (from "answer"):
I have tried: UPDATE someTable
SET someColumn = CONCAT(CAST(RAND() * 10000000000 as BIGINT), RIGHT(someColumn, 3))
However as i am totally new to SQL i don't know how to make this work. I put 'someColumn = new column name for the variable i am crating. RIGHT(someColumn) = the column i am changing. When i do that i get the message that the right function requires 2 arguments??
Example for Zohar: I have a variable containing for example: 1724981628R01On all these values in this variable i would like to randomize the first 10 letters and keep the last three (R01). How can i do that?
A couple things. First, your conversion to a big int does not guarantee that the results has the right number of characters.
Second, rand() is constant for all rows of the query. Try this version:
UPDATE someTable
SET someColumn = CONCAT(FORMAT(RAND(CHECKSUM(NEWID())
), '0000000000'
),
RIGHT(someColumn, 3)
);
Hi I am loading data from flat file to my table through SSAS I want to derive a conditional column to append zeros to is numeric data in the zip code column when the length is <5
Derived Column Expression I am using : (CustomerZipCode!="[A-Za-z]")&&(LEN(CustomerZipCode) < 5) ? RIGHT(("00000" + CustomerZipCode),5) : CustomerZipCode
ForExample
the above expression is not working,request to guide me
Thank you.
Is this for SSIS? My last answer assumed that this was an SSAS column because the SSAS tag. If this is a date load via SSIS, you can use a Script Component to do this instead of a Derived Column. The example below uses C# and the CustomerZipCode column will need to be added in the Input Columns pane with the ReadWrite usage type. The TryParse method checks if the column is numeric and will return false if the column contains any text. The String constructor is used to create the zeroesToAppend string, which is set to the number of zeroes below 5 that the length of the column is, and afterwards this is added to the beginning of the column. This will go in the Input0_ProcessInputRow method, which is executed once for each row from the input.
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
int i;
if (int.TryParse(Row.CustomerZipCode, out i) && Row.CustomerZipCode.Length < 5)
{
string zeroesToAppend = new String('0', 5 - Row.CustomerZipCode.Length);
Row.name = String.Concat(zeroesToAppend, Row.CustomerZipCode);
}
}
I did a rather easy view to return only rows where there is number is CONTRACT_ID column. CONTRACT_ID has data type number(8).
CREATE OR REPLACE VIEW cid AS
SELECT *
FROM transactions
WHERE contract_id IS NOT NULL
AND LENGTH(contract_id) > 0;
View works just fine until I scroll down to row ~2950 where I get ORA-01722. Same thing happens if I want to export data to Excel, my file gets only ~2950 rows instead of expected ~20k.
Any idea what might be causing this and how to resolve this issue?
Many thanks!
You wrote too much SQL.. The following will provide all the results you require:
CREATE OR REPLACE VIEW cid AS
SELECT *
FROM transactions
WHERE contract_id IS NOT NULL
You can't LENGTH() a number - a number is either null or it's a value, so you don't need this kind of check.
Passing a number to LENGTH() will turn it into a string first, i.e. LENGTH(TO_CHAR(numbercolumn)). You don't even need a LENGTH() check for null strings, as to oracle NULL string and a zero length string are equivalent, and calling LENGTH() on an empty string or a null, will return null, not 0 (so LENGTH(myNullStr) = 0 doesnt work out; it's not comparing 0 = 0, it's comparing null = 0 and null compared with anything is always false).
The only time this seems to cause confusion is when the string columns in the table are CHAR types rather than VARCHAR types, and people forget that assigning an empty string to a CHAR causes it to become space padded out to the CHAR length hence, not a zero length string any more
First of all, you should remove redundant condition about length(), it's senseless. I'm not sure how it can produce such error, but check whether error disappered after it.
If no, replace star (*) to some field names, say, contract_id. If it will fix error - it would appoint that error source somewhere into removed fields (say, if generated column used).
I cannot imagine how error can be still alive after that, by if so, I'd tried to move it into other tablespace and add into fields list a call of logging function which stores rowid's of rows read - thus check which row produces error.
I have a specific column in a table, it shall contains only numbers in Nvarchar that have a length of 3. Unfortunately, some users wrote '12' but they should have written '012'. There were not enough validation at the time.
I need to fix that. Here is the logic I used :
UPDATE [Mandats_Approvisionnement].[dbo].[ARTICLE_ECOLE]
SET [UNIT_ADM] = STUFF(UNIT_ADM, 0, 0, '0')
WHERE LEN(UNIT_ADM) = 2;
The error goes like :
Cannot insert the value NULL into column 'UNIT_ADM', table
'Mandats_Approvisionnement.dbo.ARTICLE_ECOLE'; column does not allow
nulls. UPDATE fails.
I can't see where the problem is, I verified and all the records contain at least 2 characters, so the STUFF function cannot returns null as there are no NULL records in that table column [unit_adm]... How do I make it work ?
It should be stuff(UNIT_ADM,1,0,'0') as stuff returns null if the start position is 0.
Citing the documentation:
If the start position or the length is negative, or if the starting
position is larger than length of the first string, a null string is
returned. If the start position is 0, a null value is returned.
You could make this simpler by using
right('0' + UNIT_ADM, 3)
instead of stuff.
I have a TSQL table from which I need to copy a column in a second table. The values that I need to copy are stored in the format 4444.44434 as a string
But first I have to split the String into 4.444,4.4434. Then I store the value in the second table.
Now I do not know exactly how to split the string from the first table and then store them it in the desired format in the second table.
I am very grateful for any solution.
My Code so far:
SELECT String, LEN(Matchcode) as Length, 1 as Counter, 0 as Value1 From Test.dbo.tdValue1
WHILE Counter <> Length {
String.Index = Length - Counter if ISNUMERIC(String.Index) = 1
{ LEFT JOIN tdValue1 ON tdValue2.String1 = tdValue1.String;
Counter += 1
}
}
Sheesh what a mess. If I understand this correctly you have two values in each row and there is an implied decimal location. I would recommend not storing the "value" but instead storing both values. Each in their own row. Sticking more than 1 piece of data in a single table intersection violates 1NF. I would recommend getting away from a string for storing numbers too.
Here is one way to split this into the two pieces of information using PARSENAME.
declare #SomeString varchar(20) = '4444.44434'
select STUFF(PARSENAME(#SomeString, 2), 2, 0, '.')
, STUFF(PARSENAME(#SomeString, 1), 2, 0, '.')