Lately Bigquery decided to leave concept of “required” columns behind. They give nice description how to change type of columns. If you have a lot of tables maybe you will find useful this small golang program which will show you all required columns:

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"path/filepath"
	"time"

	"google.golang.org/api/iterator"

	"cloud.google.com/go/bigquery"
)

var (
	projectID     = "...projectid..."
	printDebugMsg = false
	debugLevel    = 0
)

func main() {

	paramDebug := flag.Bool("debug", false, "print debug messages")
	paramDebugLevel := flag.Int("debug_level", 0, "level of debug messages: 0 = default, common messages from run / 1 = show variables, queries etc. / 2 = deep debug, shows inserted data etc.")
	paramHelp := flag.Bool("help", false, "print help")
	paramBqDataset := flag.String("bqdataset", "", "name of dataset on bigquery")
	paramBqTable := flag.String("bqtable", "", "name of table on bigquery / mask of the name")
	flag.Parse()

	printDebugMsg = *paramDebug
	debugLevel = *paramDebugLevel
	printHelpMsg := *paramHelp
	bqDatasetID := *paramBqDataset
	bqTable := *paramBqTable
	debugMsg(1, fmt.Sprintf("printDebugMsg: %v", printDebugMsg))
	debugMsg(1, fmt.Sprintf("printHelpMsg: %v", printHelpMsg))
	debugMsg(1, fmt.Sprintf("bqDatasetID: %s", bqDatasetID))
	debugMsg(1, fmt.Sprintf("bqTable: %s", bqTable))

	if printHelpMsg == true {
		flag.PrintDefaults()
		log.Fatal()
	}

	ctx := context.Background()
	bqDB, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		log.Fatalln("Cannot create new BQ client: ", err)
	}

	var tableMatch bool
	bqdatasets := bqDB.Datasets(ctx)
	for {
		dataset, err := bqdatasets.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			log.Fatalf("Cannot not iterate over datasets %s: %s", dataset, err.Error())
		}
		datasetName := dataset.DatasetID
		debugMsg(1, "dataset: ", datasetName)

		if bqDatasetID == "" || ((bqDatasetID != "") && (bqDatasetID == datasetName)) {
			datasetMeta, err := dataset.Metadata(ctx)
			if err == nil {
				debugMsg(2, "dataset meta: ", datasetMeta.Name, " - ", datasetMeta.Location)
			} else {
				printMsg("error in checking dataset metadata: ", err)
			}

			bqtables := dataset.Tables(ctx)
			for {
				table, err := bqtables.Next()
				if err == iterator.Done {
					break
				}
				if err != nil {
					log.Fatalf("Cannot not iterate over table %s: %s", table, err.Error())
				}
				tableName := table.TableID
				debugMsg(1, "table: ", tableName)

				if bqTable != "" {
					tableMatch, err = filepath.Match(bqTable, tableName)
					if err != nil {
						log.Fatal("cannot check table name match: ", err)
					}
					debugMsg(2, "tableMatch: ", tableMatch)
				}

				if bqTable == "" || ((bqTable != "") && (tableMatch == true)) {
					tableMeta, err := table.Metadata(ctx)
					if err == nil {
						sch := tableMeta.Schema
						debugMsg(2, "columns: ", len(sch))
						for i := range sch {
							col := sch[i]
							debugMsg(2, "columns: ", i, ": ", col.Name, " - ", col.Required, " - ", col.Type)
							if col.Required == true {
								printMsg(dataset.DatasetID, ".", table.TableID, " - ", col.Name)
							}
						}
					} else {
						printMsg("error in checking table metadata: ", err)
					}
				}
			}
		}
	}
}

func curTime() string {
	return time.Now().UTC().Format(time.RFC3339) + ":"
}

func debugMsg(level int, t ...interface{}) {
	if printDebugMsg == true && level <= debugLevel {
		printMsg(t...)
	}
}

func printMsg(t ...interface{}) {
	fmt.Println(curTime(), fmt.Sprint(t...))
}