# The QUERY() formula: Learn SQL code from the comfort of a Google spreadsheet

Have you heard of SQL, Structured Query Language, the code used to communicate with databases?

You know it’s a super important data skill to master but it’s intimidating because you’ve never seen it before.

You want to learn SQL, but don’t know where to start?

Well, here’s a first step you can take:

Try SQL out in a Google spreadsheet – a familiar and easy environment!

(Note 1: There are a few minor differences which I explain in this tutorial.)

(Note 2: You may find you need to “straighten” the quotes in the formulas in this tutorial if you copy and paste them directly into your Google sheet, to avoid curly quotes which won’t work. You need straight quotes.)

1. Go to this Google sheet containing the data we need.

2. Click into cell A1 and hit Ctrl + A (on PC) or Cmd + A (on Mac) to highlight the whole table.

3. Copy the data Ctrl + C (on PC) or Cmd + C (on Mac).

4. Open your own blank Google sheet and paste the table data with your cursor at cell A1, so you have your own copy of the data:

5. Ensure you have the whole table selected (repeat step 2 in your own sheet):

6. Go to the menu: Data > Named ranges… and click this menu. A new pane will show in the right side of your spreadsheet as follows:

7. In the first input box, enter the word “countries”, as shown in the image below. This names our table of data so we can refer to it easily.

8. In Google sheets we must use the QUERY() function and write our pseudo-SQL code inside this function. So, we’ll enter all of our SQL code inside a QUERY function in cell G1.

`=QUERY(countries,"our SQL code goes here between the quotes",1)`

Ok, now we’re set up, let’s start writing SQL code!

### SELECT all the data

The SQL code `SELECT *` retrieves all of the columns from our data table.

9. To the right side of the table, type the following formula into cell G1:

=QUERY(countries,” SELECT * “,1)

10. The output from this query is our full table again, because `SELECT *` retrieves all of the columns from the countries table:

Wow, there you go! You’ve written your first piece of SQL code! Pat yourself on the back.

The equivalent real world SQL code would be:

```SELECT * FROM countries```

Spot the difference: We don’t include a `FROM` clause with the QUERY function. This is different to SQL in the real world, where we must specify a `FROM` clause to select our table.

### SELECT specific columns only

11. What if we don’t want to select every column, but only certain ones? Modify your query formula to read:

=QUERY(countries,”SELECT B, D”,1)

12. This time we’ve selected only columns B and D, so our output will look like this:

Equivalent real world SQL code:

```SELECT country, population FROM countries```

### WHERE clause

The WHERE clause specifies a condition that must be satisfied. It filters our data. It comes after the SELECT clause.

13. Modify your QUERY formula in cell G2 to select only countries that have a population greater than 100 million:

=QUERY(countries,”SELECT B, D WHERE D>100000000″,1)

14. Our output table is:

Equivalent real world SQL code:

```SELECT country, population FROM countries WHERE population > 100000000```

15. Let’s see another WHERE clause example, this time selecting only European countries. Modify your formula to:

=QUERY(countries,”SELECT B, C, D WHERE C = ‘Europe’ ” ,1)

16. Now the output table is:

Equivalent real world SQL code:

```SELECT country, continent, population FROM countries WHERE continent = 'Europe'```

### ORDER BY clause

The ORDER BY clause sorts our data. We can specify column(s) and direction (ascending or descending). It comes after the SELECT and WHERE clauses.

17. Let’s sort our data by population from smallest to largest. Modify your formula to add the following ORDER BY clause, specifying an ascending direction with ASC:

=QUERY(countries,”SELECT B, C, D ORDER BY D ASC”,1)

18. The output table:

Equivalent real world SQL code:

```SELECT country, continent, population FROM countries ORDER BY population ASC```

19. Modify your formula in cell G1 to sort the data by country in descending order, Z – A:

=QUERY(countries,”SELECT B, C, D ORDER BY B DESC”,1)

20. Output table:

Equivalent real world SQL code:

```SELECT country, continent, population FROM countries ORDER BY country DESC```

### LIMIT clause

The LIMIT clause restricts the number of results returned. It comes after the SELECT, WHERE and ORDER BY clauses.

21. Let’s add a LIMIT clause to our formula in G1 and return only 10 results:

=QUERY(countries,”SELECT B, C, D ORDER BY D ASC LIMIT 10″,1)

22. This now returns only 10 results from our data:

Equivalent real world SQL code:

```SELECT country, continent, population FROM countries ORDER BY population ASC LIMIT 10```

### Arithmetic functions

We can perform standard math operations on numeric columns.

So let’s figure out what percentage of the total world population (7.16 billion) each country accounts for.

23. We’re going to divide the population column by the total (7,162,119,434) and multiply by 100 to calculate percentages. So, modify our formula to read:

=QUERY(countries,”SELECT B, C, (D / 7162119434) * 100″,1)

I’ve divided the values in column D by the total population (inside the parentheses), then multiplied by 100 to get a percentage.

24. The output table this time is:

Note – I’ve applied formatting to the output column in Google Sheets to only show 2 decimal places.

Equivalent real world SQL code:

```SELECT country, continent, (population / 7162119434) * 100 FROM countries```

Quick aside:

That heading for the arithmetic column is pretty ugly right? Well, we can rename it using the LABEL clause (however, careful as this is not part of SQL syntax). Try this out:

`=QUERY(countries,"SELECT B, C, (D / 7162119434) * 100 LABEL (D / 7162119434) * 100 'Percentage'",1)`

Equivalent real world SQL code:

```SELECT country, continent, (population / 7162119434) * 100 AS Percentage FROM countries```

### Aggregation functions

We can use other functions in our calculations, for example min, max and average.

25. To calculate the min, max and average populations in your country dataset, use aggregate functions in your query as follows:

=QUERY(countries,”SELECT max(D), min(D), avg(D)”,1)

26. The output returns three values – the max, min and average populations of the dataset, as follows:

Equivalent real world SQL code:

```SELECT max(population), min(population), avg(population) FROM countries```

### GROUP BY clause

Ok, take a deep breath. This is the most challenging concept to understand. However, if you’ve ever used pivot tables in Google Sheets (or Excel) then you should be fine with this.

The GROUP BY clause is used with aggregate functions to summarize data into groups, in the same way a pivot table does.

27. Let’s summarize by continent and count out how many countries per continent. Change your query formula to include a GROUP BY clause and use the COUNT aggregate function to count how many countries, as follows:

=QUERY(countries,”SELECT C, count(B) GROUP BY C”,1)

Note, every column in the SELECT clause (i.e. before the GROUP BY) must either be aggregated (e.g. counted, min, max) or appear after the GROUP BY clause (e.g. column C in this case).

28. The output for this query is:

Equivalent real world SQL code:

```SELECT continent, count(country) FROM countries GROUP BY continent```

29. Let’s see a more complex example, incorporating many different types of clause. Modify the formula in G1 to read:

=QUERY(countries,”SELECT C, count(B), min(D), max(D), avg(D) GROUP BY C ORDER BY avg(D) DESC LIMIT 3″,1)

This may be easier to read broken out onto multiple lines:

=QUERY(countries,”
SELECT C, count(B), min(D), max(D), avg(D)
GROUP BY C
ORDER BY avg(D) DESC
LIMIT 3
“,1)

This summarizes our data for each continent, sorts by highest to lowest average population and finally limits the results to just the top 3.

The code is very similar to real SQL and it’s looking pretty complex now! Good job!

30. The output of this query is:

Equivalent real world SQL code:

```SELECT continent, count(country), min(population), max(population), avg(population) FROM countries GROUP BY continent ORDER BY avg(population) DESC LIMIT 3```

Well, that’s all I got for this tutorial folks!

I hope this tutorial allowed you to understand and write some basic SQL code, and see that it needn’t be intimidating.

And I hope you feel inspired to keep learning!

### Resources

Official documentation for the QUERY() function.

Official documentation for Google’s Visualization API Query Language.

Want to keep learning SQL? Try this online tutorial from W3 Schools.

Prefer video? Try this free 1 hour introduction to the SQL language.

## 46 thoughts on “The QUERY() formula: Learn SQL code from the comfort of a Google spreadsheet”

1. Ben says:

Thanks!

1. Alex says:

Is it possible to use column titles instead of the letters in the query? As in, SELECT MAX(Population)

1. Ben says:

Hi Alex – no, unfortunately you can’t use the column titles inside the QUERY function in Google sheets (see this Stack Overflow thread for some discussion on this subject). The only other variation I’ve seen is the ability to use “Col1”, “Col2”, “Col3” etc. when you combine the QUERY function with one of the IMPORT functions, e.g. try this:

`=query(importhtml("https://en.wikipedia.org/wiki/The_Simpsons","table",3),"select Col1, Col4, Col8 where Col8 > 25",1)`

For details on this, check out this introduction on Stack Overflow.

1. Alex says:

Thank you, Ben!

`importhtml` is another useful function I didn’t know about

2. Susmeet Jain says:

Thank you very much for taking the time to create such quality content!!

3. Jonathan says:

This was really useful, thank you.
I’m struggling to use arithmetic operators in a query. I want to minus the sum of one column from the sum of another. My instinct says
query(data, “select D, (Sum(E) – Sum(G)) group by D where E>G”,1) but that doesn’t work :/
Any guidance would be gratefully received!

1. Ben says:

Hey Jonathan,

You were really close but the WHERE clause needs to come before the GROUP BY, as the WHERE is applied to each row. In SQL there is a HAVING clause that comes after the GROUP BY but it’s not available in this Google Visualization API Query Language. So, the formula would be e.g.:

`=query(data,"select C, sum(D), sum(E), sum(D) - sum(E) where D>E group by C")`

Feel free to make your own copy (File > Make a copy…) so you can edit it.

Cheers,
Ben

1. Jonathan says:

Great!
I’m getting all fancy now, then I do something that breaks it and I have to decipher the error codes (I’m actually beginning to understand those now too!)
Self-taught, no experience, so thanks again. Your article and reply have helped me improve.
The latest iteration after your input, look at me doing all SQL stuff!
=query(data,”select D, Sum(E) – SUM(G) where E>G group by D order by sum(E) – Sum(G) desc label Sum(E) – Sum(G) ‘Difference'”,1)

1. Ben says:

Awesome! Great work Jonathan 🙂

2. Juwan says:

I think the order of your syntax might be off. Try putting group by after the where clause 🙂

4. David Holcomb says:

Great summary. Thanks much. I am trying to do a query to return duplicate results, and I seem to be doing something wrong with the syntax. It looks like this:

Select I, count(F)
WHERE count(F) > 1
GROUP BY I
Order by count(F)

The error I get looks like this:

Error
Unable to parse query string for Function QUERY parameter 2: CANNOT_BE_IN_WHERE: COUNT(`F`)

Any idea what I am doing wrong here?

1. Ben says:

Hi David,

The problem here is that you’re not allowed aggregate functions (in this case COUNT) in the WHERE clause. The WHERE clause filters data at the row-level and won’t work with aggregated data. In real-world SQL, there’s a filter for aggregate groups called HAVING that comes after the GROUP BY, but sadly it’s not available in Google Sheets Query language.

So the solution here is to remove the WHERE line altogether and then wrap your query inside of another one, which will filter on the groups greater than 1. It’ll look something like this:

`=query(query(,"select I, count(F) group by I order by count(F)"),"select * where Col2 > 1")`

You may need to tweak this to get it to work in your particular scenario.

Here’s a screenshot of a quick example:

Thanks,
Ben

5. David Holcomb says:

Awesome. I was wondering what to do without the HAVING clause. Query a sub-query. Good thinking. And thanks for the Col2 syntax. Don’t know where I’d have found that. After a little tweaking to reach my goals, I achieved this, which works like a charm:

=query(query(‘Form Responses 1’!F:I,”
select I, count(F)
where F = ‘Yes’
group by I
order by count(F)
Label count(F) ‘Instances’
“),”
select *
where Col2> 1
“)

1. Ben says:

Great stuff! 🙂

6. Robert says:

Is there anyway to use OR:

iferror(query(importrange(AM3,”my_movies!C6:DF”),”select Col1 where ‘Col107’ OR ‘Col108’ = ‘george'”)))

Any help would be awesome

1. Ben says:

Hi Robert,

If you change:

`”select Col1 where ‘Col107’ OR ‘Col108’ = ‘george'”`

to:

`”select Col1 where Col107 = 'george' OR Col108 = 'george'”`

(i.e. remove the quotes around Col107 and Col108 and add the = ‘george’ for both) then this should work for you.

Thanks,
Ben

7. Hi Ben,

I’m a TC (top contributor) on the Google Sheet forum and this post is probably the best intro to Query that I’ve seen.

I’ve developed fairly extensive sheets and sheet systems for clients and friends, but have remained shy about Queries – the syntax has always seemed too delicate and I just spend too much time in trial and error… “maybe THIS clause goes first, maybe I need a comma here or there” etc.

I’ll be coming back here to review your tutorial often, as I think it contains the answers I need.

Thanks for the post!
Best,
Lance

1. Ben says:

Thanks Lance!

8. Jonathan Leonardo Martinez says:

may I use join?

1. Ben says:

No, SQL-style joins are not possible in the QUERY function. I’d suggest running two separate QUERY functions to get the data you want and then “joining” in the spreadsheet sense with VLOOKUP or INDEX/MATCH.

Cheers,
Ben

9. Murray says:

So, if we can’t use Labels in the =QUERY function, does that mean we are limited to only the first 26 columns? After Z are we stuck?

1. Ben says:

No, you can keep using letters in the same way as columns are labeled in the spreadsheet. So AA, AB, AC etc. becomes your next set of headings…

10. John says:

Excellent tutorial! Many thanks.

I have a question.
I have 2 tabs in a spreadsheet.
As a simplified example of my problem:
Tab 1 has columns: country, population, inflation
Tab 2 has columns: country, area
I cannot combine the information all onto one tab.

How do I create a query which will give me the equivalent of:
select area, population
from Tab1, Tab2
where Tab1.country = Tab2.country

1. Ben says:

Hi John,

Unfortunately you can’t perform “joins” (bringing data from two different tables together) with the QUERY function, so you won’t be able to do what you’re trying to do with the QUERY function.

So it sounds like a scenario for using a lookup formula (e.g. VLOOKUP or INDEX/MATCH) against the country name and retrieving the area and population into a single tab that way.

Thanks,
Ben

11. Rajnish Chandra says:

I have the following data-set. I just want to have data from it for the financial year 2015-2016 only. How to do it? Please suggest. Thanks.
Col A Col B
2015 1
2015 2
2015 3
2015 4
2015 5
2015 6
2015 7
2015 8
2015 9
2015 10
2015 11
2015 12
2016 1
2016 2
2016 3
2016 4
2016 5
2016 6
2016 10
2016 11
2016 12
2017 1
2017 2
2017 3
2017 4
2017 5
2017 6
2017 7

1. Ben says:

Hi Rajnish,

You should be able to do with this QUERY function:

`=QUERY(A1:B31,"select * where A = 2015 or A = 2016")`

Thanks,
Ben

12. Kenny says:

Thank you for the tutorial! It has helped me a lot these past couple of days.

I was wondering if it was possible to format the results of the query as they are returned. For example, using your sample spreadsheet:

=QUERY(countries,”SELECT B, C”,1)

And instead of Asia, Europe, Africa, North America, South America, we would get AS, EU, NA, SA.

Thank you.

1. Ben says:

Hi Kenny,

The short answer is not really. The easiest solution is to create a small lookup table of the continents and their 2-letter codes and then just add a new lookup column next to the QUERY output columns.

However, it is possible to create a QUERY formula version for this specific example. It works by taking the first two letters of the continent as shorthand, or for the two word continents (e.g. North America) taking the first letter of each word (i.e. NA).

Here’s the full formula (warning, it’s a beast!):

```=ArrayFormula({query(countries,"select B",1),if(if(isnumber(SEARCH(" ",QUERY(countries,"SELECT UPPER(C)",1) )),2,1)=2,left(QUERY(countries,"SELECT UPPER(C)",1),1)&mid(QUERY(countries,"SELECT UPPER(C)",1),search(" ",QUERY(countries,"SELECT UPPER(C)",1))+1,1),left(QUERY(countries,"SELECT UPPER(C)",1),2))})```

which gives an output like this:

Whew, that was a sporting challenge! Here’s a working version in a Google Sheet.

Cheers,
Ben

1. Kenny says:

Wow, that’s exactly what I was looking for! Thank you so much! Looking at the query, I don’t feel so bad about not being able to figure this out myself.

Now to sit here for the next few hours/days trying to understand how this thing works.

Again, thank you and Happy Holidays!

1. Ben says:

Great, and thanks for the challenge 🙂 Happy Holidays to you too!

2. Ben says:

P.S. I’ve added my workings to the Google Sheet, which should help explain the array formula…

1. Kenny says:

I’ve got to tell you that the extras you put into the sheet helped tremendously in helping me understand what you did. Thank you again!

2. Ben says:

Great! That’s the key to all of these larger formulas/problems: break them down into manageable chunks…

13. Jonathan says:

hi, how in the sql statement do a reference for a cell using where
example,=query(libro;”SELECT C WHERE B=’A1′”;1). a1 doesn’t work. a1 has a id

1. Ben says:

Hey Jonathan,

You’ll need to move the A1 cell reference outside of the quotes to get this to work. Something like this:

`=query(libro,"select C where B = " & A1,1)`

Hope that helps!

Cheers,
Ben

1. Jonathan says:

It works when it’s a number but doesn’t work when it’s a text. May you help me and tell also how could I use wildcards. Thank’s

1. David Holcomb says:

Try this:
=query(libro,”select C where B = ‘” & A1 & “‘”,1)

14. Great article. But how do you perform subqueries? Like:

=query(libro,”select A, (select Count(B) where B < 4)

?

1. Ben says:

Great question. You can nest the QUERY functions to get a subquery type of behaviour. So use the first subquery to create a subset of your data, then insert this as your data table in the second query:

`=QUERY(QUERY(yourData,"your select"),"your 2nd select")`

Hope that helps!

Ben

15. Ben,
As always, excellent work! Thank you for putting together such well documented tutorials. I will often add your name to my google searches to get results from you, specifically.

I was hoping QUERY could be the solution for a particular challenge I have now. The problem is best illustrated here: http://imgur.com/a/Kbkm1

I’m trying to write a formula that will count the items in one column that also appear in a separate column. For other, similar situations, I’ve added a helper column as seen in column D and the formula bar in the screenshot. I would then sum that entire column. I’m trying to find a way to do this without the helper column—putting all of this into one cell, such as cells E3 and E6, in the example (whose desired results would be 3 and 2, respectively).

Is there some sort of query (or other function) that would provide this? Perhaps SELECT…COUNT…WHERE…(IN?) Or does QUERY only work with data structured as a database, where each row is a record and linked all the way across?

An alternative would be to build a custom formula with GAS. That would be a fun project, but if there’s something easier, that would be nice.

A secondary goal for this project (in a separate formula) is to return those values that were repeated in other columns, e.g., E3 (and overflow) would be:
monkey
pen
magic

(if you want the sample data to experiment with, you can find it here: https://goo.gl/e1acwu. Hopefully will save typing out some dummy data.)

If you have a chance to take a crack at this, I’d be interested to see what you find. Thanks again for your work!

1. Ben says:

Hey Andy,

This is a perfect problem to solve with array formulas.

So these ones give you the counts:

`=ArrayFormula(count(iferror(match(C2:C1000,A2:A1000,0),"")))`

`=ArrayFormula(count(iferror(match(C2:C1000,B2:B1000,0),"")))`

and this one will output the matching values:

`=ArrayFormula(query(sort(vlookup(C2:C1000,A2:A1000,1,FALSE),1,TRUE),"select * where Col1 <> '#N/A'"))`

Hope this helps!

Cheers,
Ben

16. Augustiine Agbi says:

Great piece