query doesn't work with multiple ranges - google-sheets

I have 3 cells that I run a query on.
| name | val | name |
|------+-----+------|
| Test | 1 | Test |
I want to return True if the value is greater than 1. The problem occurs when I try to have the cells separated.. I did a demo to show what I mean (sorry for my bad explaination)
https://docs.google.com/spreadsheets/d/1Nh_YZPtswmTxbvNktTdJtSNchDMkWJt6nVfnV8sKhP8/edit?usp=sharing
This works fine:
=if(QUERY(A2:F2;"select B where A like F";-1) > 1; True; False)
These doesn't work:
=if(QUERY(A2:B2,F2;"select B where A like F";-1) > 1; True; False)
=if(QUERY({A2:B2;F2};"select B where A like F";-1) > 1; True; False)

This works:
=if(QUERY({A2:B2\F2};"select Col2 where Col1 like Col3";-1) > 1; True; False)
Your first error is {}: to place columns next use slash {Col\Col}
The second error is using ABC notation. When using {} data is converted into array, so use Col1, Col2... for columns.

Related

Copy value from a cell to another cell if it exists in another sheet's column

I have two sheets below. Links also added to each sheet for reference
Posts sheet:
id | title | tags
1 | title 1 | article, sports, football, england
2 | title 2 | news, sports, spain, france
3 | title 3 | opinion, political, france
4 | title 4 | news, political, russia
5 | title 5 | article, market, Germany
Tags sheet:
location | type | category
england | article | sports
spain | news | political
germany | opinion | market
russia | | football
france |
About each sheets:
Posts sheet consists of list of posts with title and tags associated with it.
Tags sheet consists of list of tags categorized to understandable heads.
What I am trying to do:
I need to extract the value from the tags column in Posts sheet and add the tag to individual columns based on what head its coming in tags sheet.
Desired Output:
id | title | type | category | location
1 | title 1 | article | sports, football | england
2 | title 2 | news | sports | spain, france
3 | title 3 | opinion | political | france
4 | title 4 | news | political | russia
5 | title 5 | article | market | Germany
I made this sample code for Google Apps Script that can help you sort the information. I added some comments in case you want to modify some of the columns or cells working on it. Here is the code:
function Split_by_tags() {
// Get the sheets you will work with by the name of the tab
const ss_posts = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Posts Sheet");
const ss_tags = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tags Sheet");
const ss_output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Expected Output");
// Get the range of the columns to of "Posts Sheet" we will work with
// If range_1 is the ID, range 2 is the title, range 3 is tags
// If you change the columns in the future, you only need to update this part
let range_1 = ss_posts.getRange("A2:A").getValues().flat();
let range_2 = ss_posts.getRange("B2:B").getValues().flat();
let range_3 = ss_posts.getRange("C2:C").getValues().flat();
// filter the arrays information to only the cells with values for "Posts Sheet"
// This way, you can add new information to the tags rows and they will be added
range_1 = range_1.filter((element) => {return (element !== '')});
range_2 = range_2.filter((element) => {return (element !== '')});
range_3 = range_3.filter((element) => {return (element !== '')});
// The values we will compare the tags with in arrays
let range_type = ss_tags.getRange("A2:A").getValues().flat();
let range_location = ss_tags.getRange("B2:B").getValues().flat();
let range_category = ss_tags.getRange("C2:C").getValues().flat();
// filter the arrays information to only the cells with values for "Tags Sheet"
// This way, you can add new information to the tags rows and they will be added
range_type = range_type.filter((element) => {return (element !== '')});
range_location = range_location.filter((element) => {return (element !== '')});
range_category = range_category.filter((element) => {return (element !== '')});
// new Arrays where the information will be sort, I added a new tag option called "Other"
// just in case the information in the column 2 has a value which is not under "Tags Sheet"
let type_tag = [];
let location_tag = [];
let category_tag = [];
let other_tag = [];
// for to copy the ID from "Posts Sheet" to "Expected Output"
for (let i=0; i< range_1.length ; i++){
ss_output.getRange(i+2,1).setValue(range_1[i]);
};
// for to copy the title from "Posts Sheet" to "Expected Output"
for (let j=0; j< range_2.length ; j++){
ss_output.getRange(j+2,2).setValue(range_2[j]);
};
// fuction to sort the tags from "Posts Sheet" base in "Tags Sheet"
function Separate_value(value_array){
for (let k=0; k < value_array.length; k++){
if(range_type.includes(value_array[k])){
type_tag.push(value_array[k]);
}
else if(range_location.includes(value_array[k])){
location_tag.push(value_array[k]);
}
else if(range_category.includes(value_array[k])){
category_tag.push(value_array[k]);
}
else{
other_tag.push(value_array[k]);
}
};
}
// Function to empty the arrays for the next loop
function Empty_value(){
type_tag = [];
location_tag = [];
category_tag = [];
other_tag = [];
}
// for to add the values we sorted to "Expected Output"
for (let e=0; e < range_3.length; e++ ){
let value_array = range_3[e].split(', ');
Separate_value(value_array)
ss_output.getRange(e+2,3).setValue(type_tag.join(", "));
ss_output.getRange(e+2,4).setValue(category_tag.join(", "));
ss_output.getRange(e+2,5).setValue(location_tag.join(", "));
ss_output.getRange(e+2,6).setValue(other_tag.join(", "));
Empty_value();
};
}
You can bound the script by accessing Extensions > Apps Script in your Google Sheet.
Copy and paste the sample code, and run it. The first time you run the Apps Script, it will ask you for permissions, accept those, and the information will get sorted.
You can also add a trigger to the Apps Script so it can sort the information automatically when new data is added.
Reference:
Create a bound Apps Script.
Create trigger.

Google Sheets: Count number of rows in a column that do not match corresponding row in another column?

Say we have the following spreadsheet in google sheets:
a a
b b
c
d e
e d
How would I build a formula that counts the number of rows in column B that do not match the corresponding row in column A, and are not blank? In other words I want to get the number of rows that changed to a new letter in column B. So in this example the formula should return 2.
Thank you for your help.
UPDATE:
Now suppose I have this spreadsheet:
a a
b b b
c a
d e e
e d e
How would I build on the last formula for the third column, where the value returned is:
(the number of rows in column 3 that don't match the corresponding row in column 2) + (if column 2 is blank, the number of rows in column 3 that do not match the corresponding row in column 1)
and I also don't want to count blanks in the third column.
The value returned in this case should be 2 (rows 3 and 5).
To me it sounds like you could use:
=SUMPRODUCT((B:B<>"")*(B:B<>A:A))
=IFNA(ROWS(FILTER(A:B,
(A:A<>B:B)*
(B:B<>"")
)),0)
FILTER by matching conditions * for AND + for OR.
ROWS counts rows
IFNA returns 0 if nothing was found.
or with QUERY
=INDEX(QUERY(A:B,"select count(B) where B<>A"),2)
Try this:
=ARRAYFORMULA(COUNTA($B$1:$B)-SUM(COUNTIFS($A$1:$A, $B$1:$B,$B$1:$B,"<>")))
I see 2 ways to complete this.
First you could add a function to each row to return 1 or 0 if the value changed and was not blank and then sum results. This unfortunately adds a messy column in your spreadsheet.
=if(A1<>IF(ISBLANK(B1),A1,B1),1,0)
Second you could create a function where you would pass the range as a string.
The call from the worksheet would look like this:
=myFunction("A1:B5")
Then create a script by opening Tools -> Script editor and use something like this
function myFunction(r) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(r);
var numRows = range.getNumRows();
var areDifferent = 0;
for (let i=1; i<= numRows; i++) {
let currentValue = range.getCell(i,1).getValue();
let cmpValue = range.getCell(i,2).getValue();
if ((currentValue != cmpValue) && (cmpValue != "")) {
areDifferent++;
}
}
return areDifferent;
}

Associating row data with importrange data in a new workbook on Google Sheets

For our distribution company, we have a sheet that has all of the invoices listed (in reverse chronological order to keep most recent invoices at the top), along with relevant data that our invoicing/accounting person would add. Order date, company name, location, invoice subtotal, taxes, total, outstanding balance, delivery status, etc.
I then have another sheet that pulls this data for our fulfillment and dispatch team. It filters to only the relevant columns (stuff like invoice subtotal/taxes, order date, etc are removed). I do not have it filter by row, so that way it includes ALL of the invoices from the original sheet.
I want to include a "delivery date" column and "assigned driver" column in this spreadsheet, which I have done, but like most people trying to do something similar on here, when a new invoice is added, the manually-entered data on this second sheet doesn't dynamically shift with the imported data, thus causing the rows to misalign.
The formula for the second sheet is =query(IMPORTRANGE("sheet_id","'Order Tracker'!A:T"),"select Col1, Col3, Col5, Col6, Col9, Col10, Col11, Col12, Col19 where Col10 = 'New' OR Col10 = 'Packed' OR Col10 = 'Pending'",1) I then have columns 10/11 as manual entry columns for driver assigning and delivery date. Unfortunately, as I mentioned, the rows don't stick together so as the dynamic order of the imported columns changes, the static order of the manual columns causes a mismatch.
Is there a way to make this work? Let's say I have the following invoices, with delivery dates and driver manually entered on this second sheet:
INV-005 | 10/26 | Frank
INV-004 | 10/27 | Brandon
INV-003 | 10/27 | Frank
INV-002 | 10/26 | Frank
INV-001 | 10/28 | Brandon
And then I add a new invoice, INV-006 to the top of the original invoicing spreadsheet. Now the fulfillment spreadsheet will show:
INV-006 | 10/26 | Frank
INV-005 | 10/27 | Brandon
INV-004 | 10/27 | Frank
INV-003 | 10/26 | Frank
INV-002 | 10/28 | Brandon
INV-001
Instead, I want it to show:
INV-006
INV-005 | 10/26 | Frank
INV-004 | 10/27 | Brandon
INV-003 | 10/27 | Frank
INV-002 | 10/26 | Frank
INV-001 | 10/28 | Brandon
The script below will do what you need.
When you make a change in your order sheet, the delivery sheet will be updated correctly.
It will update:
When you open the delivery sheet
If you press a REFRESH button on the delivery sheet.
(So not quite as automatically as when using the "query(IMPORTRANGE..)" formula).
Here is the code that you will need to install.
function onOpen() {
updateTracker();
}
function updateTracker(){
// This function is executed when the sheet is opened
// and also intended to be linked to a REFRESH button to be installed in the sheet
// The function populates ColA to ColK with data from another sheet
// Existing data in ColL and ColM has to be preserved, and realigned
// with the same invoice numbers in ColA of the new data.
// Step 1 - read ColA, ColL and ColM of the old data, before repopulating ColA and ColsK
//---------------------------------------------------------------------------------------
//var openSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Open Order Tracker");
var openSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = openSheet.getLastRow(); // locate last row of data
var invNoArray = openSheet.getRange(2,1,lastRow-1).getValues(); // 1D array of invoices numbers
var invNoList = {};
for(var row=0; row < invNoArray.length; row++){ // make an "associative array" of invoice numbers
invNoList[invNoArray[row]] = row;
}
// read the delivery dates and driver from this sheet ('Open Order Tracker')
var driverArray = openSheet.getRange(2,12,lastRow-1,3).getValues(); // 2D array of Delivery Dates and Drivers
// clear out the old content (not formats or vaidation)
var currentRange = openSheet.getRange(2,1,lastRow,14);
currentRange.clearContent();
// Step 2 - Read and the data for ColA to ColK from the source sheet
//------------------------------------------------------------------
// Simulating this formula
//=query(IMPORTRANGE("1rm31Zza8fMS2pASIuFvQ0WBBqWb-174lD5VrtAixDjg","'Order Tracker'!A:T"),"select Col1, Col2, Col3, Col5, Col9, Col10, Col11, Col12, Col13, Col14,
// Col19 where Col10 = 'New' OR Col10 = 'Packed' OR Col10 = 'Pending' OR(Col10 = 'Delivered' AND Col14 > 0.01)",1)
var sourceSheet = SpreadsheetApp.openById('1LU-dSlGqyiKj6xjo5AVvNNdf1pBR26NTuaXZBdLK2Og').getSheetByName("Order Tracker");
var dataRange = sourceSheet.getDataRange();
var dataValues = dataRange.getValues().filter(function (x) {return x[9]=='New' || x[9] =='Packed' || x[9] == 'Pending' || (x[9] == 'Delivered' && x[13] >=0.01);});
// Remove columns we dont need.
var reqValues = [];
var reqCols=[0,1,2,4,8,9,10,11,12,13,18]; // corresponding to Col1, Col2 etc
for(var row=0; row<dataValues.length; row++){
var thisRow = [];
for (var col=0; col<reqCols.length; col++){
thisRow.push(dataValues[row][reqCols[col]]);
}
// Add placeholders cols for ColL and ColM
thisRow.push("None");
thisRow.push("None");
thisRow.push("None"); // to be removed later
reqValues.push(thisRow);
}
// Step 3 - Populate ColL and ColM - re-aligning the Invoice Numbers
//------------------------------------------------------------------
for (var row=0; row < reqValues.length; row++){
if (invNoList.hasOwnProperty(reqValues[row][0])){
var invNoIndex= invNoList[reqValues[row][0]]; // locate correct data based on invoice number
reqValues[row][11] = driverArray[invNoIndex][0]; // fill in Delivery Date
reqValues[row][12] = driverArray[invNoIndex][1]; // fill in the Driver
// below line to be removed later
reqValues[row][13] = driverArray[invNoIndex][2]; // fill in the CrossCheck data
}
}
//Step 4 - Copy the reqValues
//-----------------------------------------------
var finalRange = openSheet.getRange(2,1,reqValues.length,14); // openSheet and lastRow should be still valid
finalRange.setValues(reqValues);
//Done
}
I have tested this in copies of your test sheets and all seems to work OK.
This link is a version of your fulfilment sheet, and has the script and REFRESH button install in Col M: https://docs.google.com/spreadsheets/d/15ecr9CmXn2YyhMpGTg8VCVf8tTi5GaGrjgmQus9FxWA/edit?usp=sharing
NOTE to any Google script experts: I had to make a script version of the original "query(IMPORTRANGE..)" formula. This is in step 2. If anyone sees a better way to do this I would be interested to hear. The reason I did this was due to Google script restrictions (as I understand):
There is no event following execution of "query(IMPORTRANGE..)"
If I install the query in script, there is no way to execute it in the script.

How can I include empty strings in HTML text() extracted with XPath?

I have a page which consists of a table with two columns.
header | value
----------------
field1 | 1
field2 |
field3 | 1
field4 |
field5 | 1
When I select the values I need to get the same number as there are fields. I get the right number with:
>s = scrapy.Selector(response)
>values = s.xpath('//tr/td[#class="tdMainBottom"][2]').extract() # get the second column
>len(values)
5
But:
>s = scrapy.Selector(response)
>values = s.xpath('//tr/td[#class="tdMainBottom"][2]/text()').extract() # get the values
>len(values)
3
I can clean the first list up afterwards, but is there a one-shot way of doing this in XPath/Scrapy?
This works but is kind of ugly:
values = [v.xpath('text()').extract()
for v in s.xpath('//tr/td[#class="tdMainBottom"][2]')]

Query builder with select

I am trying to select some columns from a join result. The query is:
$queryBuilder = new \Phalcon\Mvc\Model\Query\Builder();
$queryBuilder->columns(array('L.*', 'S.build_id'));
$queryBuilder->addFrom('Gadwin\Models\Licenses', 'L');
$queryBuilder->join('Gadwin\Models\Serials', 'S.id = L.serial_id', 'S');
$resultset = $queryBuilder->getQuery()->execute();
If i remove the columns method, the query works and return a Licenses collection. But, if i set the columns method, an empty array is returned.
Any ideas ?
Instead of query builder, try raw sql in phalcon.
create one method in your model.
public function myRawSql()
{
$query = "SELECT * FROM your_table WHERE some_condition";
$modelObj = new ModelName(); // Create object of your model
return $modelObj->getReadConnection()->query($query)->fetchAll();
}
I see that you ask for results from 2 Tables. In this case, Phalcon will return a ComplexResultSet, instead of a ResultSet. Try a get_class on your $resultset, and if it is a ComplexResultSet, you can iterate it to access its rows. It should be something like:
----------------------------------
| Licence (object) | s.id (int ) |
----------------------------------
| Licence (object) | s.id (int ) |
----------------------------------
Generally, try also using a debugging tool such as Kint to examine your variables. It can be very helpful.

Resources