I'm dealing with an issue where the formatting on a CSV file that we're importing needs to be"pivoted" to match the formatting required for the program we are using to process the import.
Currently we are importing the file which comes with the following format:
Account
Department
Jan2022
Feb2022
Mar2022
12345
Sales
$456
$876
$345
98765
HR
$765
$345
$344
We need the format to hold the time periods in one column which would make each account be repeated per time period. For example:
Account
Department
Period
Amount
12345
Sales
Jan2022
$456
12345
Sales
Feb2022
$876
12345
Sales
Mar2022
$345
We are importing this CSV using JavaScript however its basic JS as the program does not support JQuery or any other JS library. Once we import the table into our staging area using JS, we can use SQL to modify the data as well, so this could be solved with either JS or SQL.
We are using a CSV to Array function to read the CSV file for importing into staging:
function CSVToArray(strData, strDelimiter) {
// Check to see if the delimiter is defined. If not, then default to comma.
strDelimiter = strDelimiter || ",";
// Create a regular expression to parse the CSV values.
var objPattern = new RegExp(
// Delimiters.
"(\\" +
strDelimiter +
"|\\r?\\n|\\r|^)" +
// Quoted fields.
'(?:"([^"]*(?:""[^"]*)*)"|' +
// Standard fields.
'([^"\\' +
strDelimiter +
"\\r\\n]*))",
"gi"
);
// Create an array to hold our data. Give the array a default empty first row.
var arrData = [[]];
// Create an array to hold our individual pattern matching groups.
var arrMatches = null;
// Keep looping over the regular expression matches until we can no longer find a match.
while ((arrMatches = objPattern.exec(strData))) {
// Get the delimiter that was found.
var strMatchedDelimiter = arrMatches[1];
// Check to see if the given delimiter has a length (is not the start of string) and if it matches
// field delimiter. If id does not, then we know that this delimiter is a row delimiter.
if (strMatchedDelimiter.length && strMatchedDelimiter !== strDelimiter) {
// Since we have reached a new row of data, add an empty row to our data array.
arrData.push([]);
}
//Now that we have our delimiter out of the way, let's check to see which kind of value we captured (quoted or unquoted).
var strMatchedValue;
if (arrMatches[2]) {
// We found a quoted value. When we capture this value, unescape any double quotes.
strMatchedValue = arrMatches[2]
.replace(new RegExp('""', "g"), '"')
.replace('"', "");
} else {
// We found a non-quoted value.
strMatchedValue = arrMatches[3];
}
// Now that we have our value string, let's add it to the data array.
arrData[arrData.length - 1].push(strMatchedValue);
}
// Return the parsed data.
return arrData;
}
UNPIVOT should work for you:
/* sample data */
with t as
(select '12345' account,
'Sales' department,
'$456' jan2022,
'$876' feb2022,
'$345' mar2022
from dual
union all
select '98765' account,
'HR' department,
'$765' jan2022,
'$345' feb2022,
'$344' mar2022
from dual)
select *
from t
unpivot include nulls(amount for period in(jan2022 as 'jan2022',
feb2022 as 'feb2022',
mar2022 as 'mar2022'));
If you have dynamic columns you gonna have bad time with this aproach - you have to generate "unpivot in clause" (that part with jan2022 as 'jan2022') on your own.
Related
I have a dynamic string that is generated like one of the following:
var q = "FROM Table SELECT avg(1), avg(2), avg(3) where x='y'
var q = "SELECT avg(1), avg(2), avg(3) FROM Table where z='x' since x days ago
The values after the select are also dynamic where there could be 1 select option, or 10. I'm trying to create some logic to always pluck whatever is selected into an array, but having trouble dealing with the dynamic nature (string being constructed dynamically AND the # of selects being dynamic).
Basically, end result something like this:
['avg(1)', 'avg(2)', 'avg(3)']
Currently I'm doing something like the following, but it always expects the string to be formatted in a certain order (always starting with SELECT and where after the fields to pluck):
let splitQ = q.match(".*SELECT(.*)where");
let selects = splitQ[1].trim().split(",");
Here is a working solution.
It makes these assumptions about the query (after lowercased).
the values come after the first instance of the word 'select '
if the query starts with 'from', values end before the first instance of ' where'
if the query starts with 'select', values end before the first instance of ' from'
const test1 = "FROM Table SELECT avg(1), avg(2), avg(3) where x='y'";
const test2 = "SELECT avg(1), avg(2), avg(3) FROM Table where z='x' since x days ago";
function extractValues(query) {
// in both scenarios, the values always come directly after 'select '
const valuesComeAfterMe = 'select ';
query = query.toLowerCase();
let valuesEndBeforeMe;
// conditionally handle both query syntaxes
if (query.startsWith('from')) {
valuesEndBeforeMe = ' where';
} else if (query.startsWith('select')) {
valuesEndBeforeMe = ' from';
} else {
throw Error('query not handled');
}
// remove start
query = query.slice(query.indexOf(valuesComeAfterMe) + valuesComeAfterMe.length);
// remove end
query = query.slice(0, query.indexOf(valuesEndBeforeMe));
// split values and trim whitespace
return query.split(',').map(item => item.trim());
}
console.log(extractValues(test1));
console.log(extractValues(test2));
Here's an example of the customer codes:
C000000123
C000000456
If I input C123 in the search box, "C000000123" will automatically display.
9 numbers are fixed.
Please help me, a short sample was shown to me but I don't get it.
function test(key, num, digit) {
let retStr;
xxxx (condition)
retun retStr;
}
here's an elaboration:
**
input:123
output:A00000123
input:1
output:A00000001
input:99999
output:A00099999
**
here's the detailed demand:
Since it takes time and effort to enter the management number “alphabet + numeric value 9 digits” on the search screen, when the alphabetic number and the number excluding the leading 0 are entered, it is automatically complemented so that it becomes 9 padded with zeros.
sorry i'm very very new to programming in javascript
Try this:
May be what you want...
Please test it and tell if its what you want.
function getOutput(input){
var str=input.substring(1,input.length);
var padd0=9-str.length;
var zr="000000000";
var zrsub=zr.substring(0,padd0);
var output=input[0]+zrsub+""+str;
return output;
}
//Example: Call it like (NB any letter can be used):
getOutput("C123"); //or
getOutput("D123");
You can use .endsWith in js which takes a string and a search string and returns true if the specified string ends with the search string.
This function takes an array of customer ids and a search string and returns the matching customer id
function searchCustomer(customers, searchString) {
return customers.find(customer => customer.endsWith(searchString));
}
searchCustomer(['C000000123', 'C000000456'], 123); // "C000000123"
searchCustomer(['C000000123', 'C000000456'], 456); // "C000000456"
searchCustomer(['C000000123', 'C000000456', 'A00000001'], 1); //"A00000001"
How do I ensure that my string have two words in it using typescript.
The reason I need this so that I would call server only if name is in "first last" name format.
The answer really isn't TypeScript dependent. It's basic JavaScript.
You can use a regular expression to perform a test on the string:
function testString(input){
// Return whether or not there are letters (any amount and any case)
// followed by one space and then more letters (any amount and any case).
// Of course, if you wanted to get more specific about case or character
// counts, adjusting the regular expression would be simple.
return /^[A-Za-z]+ [A-Za-z]+$/.test(input);
}
console.log(testString("Scott"));
console.log(testString("Scott Marcus"));
console.log(testString("Scott\nMarcus"));
Things like this can't be typed with TypeScript, but you can make use of type guards to explicitly indicate that a param value needs to pass a specific test first to be a valid input to a function.
Here's what a "strongly typed" version of Scott's answer could be:
my-custom-types.d.ts
// A string supertype that represents a string that passed our RegEx test
declare type TwoWords = Branded<string, 'two-words'>
// Used to declare an unique primitive type, "nominal types" aren't supported yet
// #see https://github.com/Microsoft/TypeScript/issues/202
declare type Branded<T, U> = T & { '__brand': U }
// FullName in a "first last" name format
declare type FullName = TwoWords
my-test.ts
// This a type guard that casts strings with two words to TwoWords
function isTwoWords(input: string): input is TwoWords {
return /^[A-Za-z]+ [A-Za-z]+$/.test(input)
}
// This is the function that should only receive strings with two words
function showName(name: FullName) {
let [first, last] = name.split(' ') // Can be splited with safety
console.log(`${last}, ${first}`)
}
let name1 = 'John'
let name2 = 'John Doe'
let name3 = 'John Doe Junior'
// Error, you can't assume this string has two words before testing it
showName(name2)
for (let name of [name1, name2, name3]) {
if (isTwoWords(name)) {
// No errors, TS knows that only a TwoWords type reach this line
showName(name)
}
}
You can ensure your string has two words in it possibly containing letters with accents, multiple hyphens within, multiple apostrophes within, and separated by a space with RegEx.
const allowedChars = "a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; // https://stackoverflow.com/a/1073545/188246
const isTwoWordNameRegEx = new RegExp(`^[${allowedChars}]+(['\-][${allowedChars}]+)* [${allowedChars}]+(['\-][${allowedChars}]+)*$`, "i");
isTwoWordNameRegEx.test("Sébastien Doe"); // true
isTwoWordNameRegEx.test("John Doe"); // true
isTwoWordNameRegEx.test("John Doe-Williams") // true
isTwoWordNameRegEx.test("Scarlett O'Hara") // true
isTwoWordNameRegEx.test("John Doe-Williams-Jane") // true
isTwoWordNameRegEx.test("John Doe-") // false
isTwoWordNameRegEx.test("John Doe'") // false
isTwoWordNameRegEx.test("John' Doe") // false
isTwoWordNameRegEx.test("John Doe Williams") // false
Now that I've mentioned this... don't do it! It's still making an assumption about how a name might be. Please read Falsehoods Programmers Believe About Names.
If you really want to restrict it to two words, then please consider a very relaxed version:
const isTwoWordNameRegEx = /^\S+ \S+$/;
I am trying to extract some data from user input that should follow this format: 1d 5h 30m, which means the user is entering an amount of time of 1 day, 5 hours and 30 minutes.
I am trying to extract the value of each part of the input. However, each group is optional, meaning that 2h 20m is a valid input.
I am trying to be flexible in the input (in the sense that not all parts need to be input) but at the same time I don't watch my regex to match some random imput like asdfasdf20m. This one should be rejected (no match).
So first I am getting rid of any separator the user might have used (their input can look like 4h, 10m and that's ok):
input = input.replace(/[\s.,;_|#-]+/g, '');
Then I am capturing each part, which I indicate as optional using ?:
var match = /^((\d+)d)?((\d+)h)?((\d+)m)?$/.exec(input);
It is kind of messy capturing an entire group including the letter when I only want the actual value, but I cannot say that cluster is optional without wrapping it with parentheses right?
Then, when an empty group is captured its value in match is undefined. Is there any function to default undefined values to a particular value? For example, 0 would be handy here.
An example where input is "4d, 20h, 55m", and the match result is:
["4d20h55m", "4d", "4", "20h", "20", "55m", "55", index: 0, input: "4d20h55m"]
My main issues are:
How can I indicate a group as optional but avoid capturing it?
How can I deal with input that can potentially match, like abcdefg6d8m?
How can I deal with an altered order? For example, the user could input 20m 10h.
When I'm asking "how to deal with x" I mean I'd like to be able to reject those matches.
As variant:
HTML:
<input type="text">
<button>Check</button>
<div id="res"></div>
JS:
var r = [];
document.querySelector('button').addEventListener('click', function(){
var v = document.querySelector('input').value;
v.replace(/(\d+d)|(\d+h)|(\d+m)/ig, replacer);
document.querySelector('#res').innerText = r;
}, false);
function trim(s, mask) {
while (~mask.indexOf(s[0])) {
s = s.slice(1);
}
while (~mask.indexOf(s[s.length - 1])) {
s = s.slice(0, -1);
}
return s;
}
function replacer(str){
if(/d$/gi.test(str)){
r[0] = str;
}
else if(/h$/gi.test(str)){
r[1] = str;
}
else if(/m$/gi.test(str)){
r[2] = str;
}
return trim(r.join(', '), ',');
}
See here.
I am new in programing and right now I am working on one program. Program need to find the substring in a string and return the index where the chain starts to be the same. I know that for that I can use "indexOf". Is not so easy. I want to find out substrings with at moste one different char.
I was thinking about regular expresion... but not really know how to use it because I need to use regular expresion for every element of the string. Here some code wich propably will clarify what I want to do:
var A= "abbab";
var B= "ba";
var tb=[];
console.log(A.indexOf(B));
for (var i=0;i<B.length; i++){
var D=B.replace(B[i],"[a-z]");
tb.push(A.indexOf(D));
}
console.log(tb);
I know that the substring B and string A are the lowercase letters. Will be nice to get any advice how to make it using regular expresions. Thx
Simple Input:
A B
1) abbab ba
2) hello world
3) banana nan
Expected Output:
1) 1 2
2) No Match!
3) 0 2
While probably theoretically possible, I think it would very complicated to try this kind of search while attempting to incorporate all possible search query options in one long complex regular expression. I think a better approach is to use JavaScript to dynamically create various simpler options and then search with each separately.
The following code sequentially replaces each character in the initial query string with a regular expression wild card (i.e. a period, '.') and then searches the target string with that. For example, if the initial query string is 'nan', it will search with '.an', 'n.n' and 'na.'. It will only add the position of the hit to the list of hits if that position has not already been hit on a previous search. i.e. It ensures that the list of hits contains only unique values, even if multiple query variations found a hit at the same location. (This could be implemented even better with ES6 sets, but I couldn't get the Stack Overflow code snippet tool to cooperate with me while trying to use a set, even with the Babel option checked.) Finally, it sorts the hits in ascending order.
Update: The search algorithm has been updated/corrected. Originally, some hits were missed because the exec search for any query variation would only iterate as per the JavaScript default, i.e. after finding a match, it would start the next search at the next character after the end of the previous match, e.g. it would find 'aa' in 'aaaa' at positions 0 and 2. Now it starts the next search at the next character after the start of the previous match, e.g. it now finds 'aa' in 'aaaa' at positions 0, 1 and 2.
const findAllowingOneMismatch = (target, query) => {
const numLetters = query.length;
const queryVariations = [];
for (let variationNum = 0; variationNum < numLetters; variationNum += 1) {
queryVariations.push(query.slice(0, variationNum) + "." + query.slice(variationNum + 1));
};
let hits = [];
queryVariations.forEach(queryVariation => {
const re = new RegExp(queryVariation, "g");
let myArray;
while ((searchResult = re.exec(target)) !== null) {
re.lastIndex = searchResult.index + 1;
const hit = searchResult.index;
// console.log('found a hit with ' + queryVariation + ' at position ' + hit);
if (hits.indexOf(hit) === -1) {
hits.push(searchResult.index);
}
}
});
hits = hits.sort((a,b)=>(a-b));
console.log('Found "' + query + '" in "' + target + '" at positions:', JSON.stringify(hits));
};
[
['abbab', 'ba'],
['hello', 'world'],
['banana', 'nan'],
['abcde abcxe abxxe xbcde', 'abcd'],
['--xx-xxx--x----x-x-xxx--x--x-x-xx-', '----']
].forEach(pair => {findAllowingOneMismatch(pair[0], pair[1])});