I want to use function split to a column in a table, so that each element can be a new column.
For example, there is a column in the format of “aa, bb, cc“. How can I obtain new columns “aa“ “bb“ ”cc“?
Since the object of split must be a scalar, you can use each\loop for a table.
Use user-defined function for expected columns.
def splitV(sp,k){
return string(split(sp, " ")[k])
}
Apply it to a table:
sp = array(STRING, 0).append!("aa bb cc").append!("aa bb dd")
t = table(sp as sp)
select *, each(splitV,sp,0) as `a, each(splitV,sp,1) as `b, each(splitV,sp,2) as `c from t
The output is as follows:
If the format of this column is unified, you can use built-in function substr for vectorization.
Performance can be doubled:
select *, substr(sp,0,2) as `a, substr(sp,3,2) as `b, substr(sp,6,2) as `c from t
Related
I need to build a table based on the following data:
Ref
Product
R1
ProdA
R2
ProdC
R1
ProdB
R3
ProdA
R4
ProdC
And here the result I need:
My Product
All Ref
ProdA
R1#R3
ProdC
R2#R4
The particularity is that the 'My Product' column is computed elsewhere. So I need an arrayformula based on 'My Product' column to look in the first table to build the 'All Ref' column. You follow me?
I know that Arrayformula is not compatible with filter and join ... I expect a solution like this one Google sheet array formula + Join + Filter but not sure to understand all steps and if really adapted to my case study.
Hope you can help.
You could try something like this:
CREDIT: player0 for the method shared to similar questions
=ARRAYFORMULA(substitute(REGEXREPLACE(TRIM(SPLIT(TRANSPOSE(
QUERY(QUERY({B2:B&"😊", A2:A&"#"},
"select max(Col2)
where Col1 !=''
group by Col2
pivot Col1"),,999^99)), "😊")), "#$", )," ",""))
Step by step:
Instead of the workaround hacks I implemented a simple joinMatching(matches, values, texts, [sep]) function in Google Apps Script.
In your case it would be just =joinMatching(MyProductColumn, ProductColumn, RefColumn, "#").
Source:
// Google Apps Script to join texts in a range where values in second range equal to the provided match value
// Solves the need for `arrayformula(join(',', filter()))`, which does not work in Google Sheets
// Instead you can pass a range of match values and get a range of joined texts back
const identity = data => data
const onRange = (data, fn, args, combine = identity) =>
Array.isArray(data)
? combine(data.map(value => onRange(value, fn, args)))
: fn(data, ...(args || []))
const _joinMatching = (match, values, texts, sep = '\n') => {
const columns = texts[0]?.length
if (!columns) return ''
const row = i => Math.floor(i / columns)
const col = i => i % columns
const value = i => values[row(i)][col(i)]
return (
// JSON.stringify(match) +
texts
.flat()
// .map((t, i) => `[${row(i)}:${col(i)}] ${t} (${JSON.stringify(value(i))})`)
.filter((_, i) => value(i) === match)
.join(sep)
)
}
const joinMatching = (matches, values, texts, sep) =>
onRange(matches, _joinMatching, [values, texts, sep])```
I have a spreadsheet with criteria, a start and end date, and a value. The goal is to find the lowest value for each unique criteria and start date without overlapping dates (exclusive of end date). I made a pivot table to make it easier for myself but I know there is probably a way to highlight all valid rows that meet the above requirements with some formula or conditional formatting.
I have attached a google drive link where the spreadsheet can be found here and I have some images of the sheet as well. I know that it might be possible with conditional formatting but I just don't know how to combine everything I want it to do in a single formula.
Example below:
Row 2 is a valid entry because it has the lowest value for Item 1 starting on 03-15-2021, same with row 9.
Row 5 is valid because the start date does not fall within the date range of row 2 (exclusive of end date)
Row 7 is not valid because the start date is between the start and end date of row 6
You may add a bounded script to your project. Then you can call it either with a picture/drawing that has the function assigned (button-like), or adding a menu to Google Sheets.
From what you said in the question and the comments, this seems to do what you are trying. Notice that this requires the V8 runtime (which should be the default).
function validate() {
// Get the correct sheet
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const sheet = spreadsheet.getSheetByName('Sheet1')
// Get the data
const length = sheet.getLastRow() - 1
const range = sheet.getRange(2, 1, length, 4)
const rows = range.getValues()
const data = Array.from(rows.entries(), ([index, [item, start, end, value]]) => {
/*
* Row Index
* 1 Criteria 1
* 2 Item 1 0
* 3 Item 1 1
* 4 Item 1 2
*
* row = index + 2
*/
return {
row: index + 2,
criteria: item,
start: start.getTime(),
end: end.getTime(),
value: value
}
})
// Sort the data by criteria (asc), start date (asc), value (asc) and end date (asc)
data.sort((a, b) => {
let order = a.criteria.localeCompare(b.criteria)
if (order !== 0) return order
order = a.start - b.start
if (order !== 0) return order
order = a.value - b.value
if (order !== 0) return order
order = a.end - b.end
return order
})
// Iterate elements and extract the valid ones
// Notice that because we sorted them, the first one of each criteria will always be valid
const valid = []
let currentCriteria
let currentValid = []
for (let row of data) {
if (row.criteria !== currentCriteria) {
// First of the criteria
valid.push(...currentValid) // Move the valids from the old criteria to the valid list
currentValid = [row] // The new list of valid rows is only the current one (for now)
currentCriteria = row.criteria // Set the criteria
} else {
const startDateCollision = currentValid.some(valid => {
row.start >= valid.start && row.start < valid.end
})
if (!startDateCollision) {
currentValid.push(row)
}
}
}
valid.push(...currentValid)
// Remove any old marks
sheet.getRange(2, 5, length).setValue('')
// Mark the valid rows
for (let row of valid) {
sheet.getRange(row.row, 5).setValue('Valid')
}
}
Algorithm rundown
We get the sheet that we have the data in. In this case we do it by name (remember to change it if it's not the default Sheet1)
We read the data and transform it in a more an array of objects, which for this case makes it easier to manage
We sort the data. This is similar to the transpose you made but in the code. It also forces a priority order and groups it by criteria
Iterate the rows, keeping only the valid:
We keep a list of all the valid ones (valid) and one for the current criteria only (currentValid) because we only have to check data collisions with the ones in the same criteria.
The first iteration will always enter the if block (because currentCriteria is undefined).
When changing criteria, we dump all the rows in currentValid into valid. We do the same after the loop with the last criteria
When changing criteria, the CurrentValid is an array with the current row as an element because the first row will always be valid (because of sorting)
For the other rows, we check if the starting date is between the starting and ending date of any of the valid rows for that criteria. If it's not, add it to this criteria's valid rows
We remove all the current "Valid" in the validity row and fill it out with the valids
The cornerstone of the algorithm is actually sorting the data. It allows us to not have to search for the best row, as it's always the next one. It also ensures things like that the first row of a criteria is always valid.
Learning resources
Javascript tutorial (W3Schools)
Google App Scripts
Overview of Google Apps Script
Extending Google Sheets
Custom Menus in Google Workspace
Code references
Class SpreadsheetApp
Class Sheet
Sheet.getRange (notice the 3 overloads)
let ... of (MDN)
Spread syntax (...) (MDN)
Arrow function expressions (MDN)
Array.from() (MDN)
Array.prototype.push() (MDN)
Array.prototype.sort() (MDN)
Date.prototype.getTime() (MDN)
String.prototype.localeCompare() (MDN)
To create a trigger before insert using Informix database.
When we try to insert a record into the table it should insert random alphanumeric string into one of the field. Are there any built in functions?
The table consists of the following fields:
empid serial NOT NULL
age int
empcode varchar(10)
and I am running
insert into employee(age) values(10);
The expected output should be something as below:
id age empcode
1, 10, asf123*
Any help is appreciated.
As already commented there is no existing function to create a random string however it is possible to generate random numbers and then convert these to characters. To create the random numbers you can either create a UDR wrapper to a C function such as random() or register the excompat datablade and use the dbms_random_random() function.
Here is an example of a user-defined function that uses the dbs_random_random() function to generate a string of ASCII alphanumeric characters:
create function random_string()
returning varchar(10)
define s varchar(10);
define i, n int;
let s = "";
for i = 1 to 10
let n = mod(abs(dbms_random_random()), 62);
if (n < 10)
then
let n = n + 48;
elif (n < 36)
then
let n = n + 55;
else
let n = n + 61;
end if
let s = s || chr(n);
end for
return s;
end function;
This function can then be called from an insert trigger to populate the empcode column of your table.
I have the Cypher query:
match(p:Product {StyleNumber : "Z94882A", Color: "Black"})--(stock:Stock {Retailer: "11"})
with sum(stock.Stockcount) as onstock, p
optional match(p)-->(s:Sale {Retailer : "11"})
where s.Date = 20170801
return p.Color,p.Size, onstock as stock, sum(s.Quantity) as sold
This gives correctly:
Color,Size,Stock,Sold
Black,M,3,0
Black,S,3,1
Black,L,1,1
Black,XL,5,2
But if I leave out the Size property in the return statement,and just return:
return p.Color, onstock as stock, sum(s.Quantity) as sold
This only returns 3 rows (Size "M" is missing):
Black,3,1
Black,1,1
Black,5,2
I can't figure out why there is a difference in the result?
Because you are using the sum() aggregation function.
Cypher doesn't have a GROUP BY clause (like traditional SQL databases), but when you use an aggregation function all non-aggregated fields are implicitly used as grouping fields.
So when you remove p.Size from return the first row is grouped with the second row because all values implicitly grouped are equals (p.Color = 'Black' and onstock = 3). Also, the values of the Sold column are used in the sum() function (0 + 1 = 1), producing the row:
Black,3,1
I need your help in excluding the suppressed amounts from the SUM in the group footer. In the details section, I am having duplicate deal no and amounts.So, in the Section Expert for the details sections, I performed a condition on the formula "Suppress" to suppress the duplicated fields which is:
{deal_no} = (next{deal_no})
The above condition is applied in the report and I was able to suppress the duplicates. Now in the group footer, I am performing a running total using the formulas to calculate the SUM for the amounts. However, this SUM is calculating the suppressed amounts and it is giving the results. So how to remove them from the calculation.
This is the answer
WhilePrintingRecords;
If {deal_no} <> (next{deal_no}) OR OnLastRecord then //this excludes the duplicates
(
numbervar gsum := gsum + {#Cost};
numbervar grand := grand + gsum;
);
what you can do is instead of using running total you can just summarize by right clicking on the field and using Insert Summary which will give correct result.
I have made function funsumColumn(dtfunSum, "intProductId", "decCBMCMS")
and pass datattable, table id name, and column name that i want to sum to.
This function returns sum of unique id row.
Private Function funsumColumn(ByVal dt As DataTable, ByVal idColumnName As String, ByVal ColumnForSum As String) As Decimal
Dim dtnew As New DataTable
dtnew.Columns.Add(idColumnName, GetType(Integer))
dtnew.Columns.Add(ColumnForSum, GetType(Double))
Dim dtMainTable As DataTable
Dim ComputeColumnSum As Decimal
Try
Dim ExistIdInLocalTable As Integer
Dim foundRow() As DataRow
Dim PIid As Integer
For i As Integer = 0 To dt.Rows.Count - 1
PIid = dt.Rows(i).Item(idColumnName)
foundRow = dtnew.Select("" & idColumnName & "='" & PIid & "'")
ExistIdInLocalTable = foundRow.Count
If foundRow.Count = 0 Then
dtnew.Rows.Add(dt.Rows(i).Item(idColumnName), dt.Rows(i).Item(ColumnForSum))
Else
End If
Next
dtMainTable = dtnew
ComputeColumnSum = dtMainTable.Compute("sum(" & ColumnForSum & ")", "")
Return ComputeColumnSum
Catch ex As Exception
End Try
Return ComputeColumnSum