123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- #!/usr/bin/env python3
- ############################################################################
- #
- # MODULE: v.db.join
- # AUTHOR(S): Markus Neteler
- # Converted to Python by Glynn Clements
- # PURPOSE: Join a table to a map table
- # COPYRIGHT: (C) 2007-2021 by Markus Neteler and the GRASS Development Team
- #
- # This program is free software under the GNU General Public
- # License (>=v2). Read the file COPYING that comes with GRASS
- # for details.
- #
- #############################################################################
- # %module
- # % description: Joins a database table to a vector map table.
- # % keyword: vector
- # % keyword: attribute table
- # % keyword: database
- # %end
- # %option G_OPT_V_MAP
- # % description: Vector map to which to join other table
- # % guidependency: layer,column
- # %end
- # %option G_OPT_V_FIELD
- # % description: Layer where to join
- # % guidependency: column
- # %end
- # %option G_OPT_DB_COLUMN
- # % description: Identifier column (e.g.: cat) in the vector table to be used for join
- # % required : yes
- # %end
- # %option G_OPT_DB_TABLE
- # % key: other_table
- # % description: Other table name
- # % required: yes
- # % guidependency: ocolumn,scolumns
- # %end
- # %option G_OPT_DB_COLUMN
- # % key: other_column
- # % description: Identifier column (e.g.: id) in the other table used for join
- # % required: yes
- # %end
- # %option G_OPT_DB_COLUMN
- # % key: subset_columns
- # % multiple: yes
- # % required: no
- # % description: Subset of columns from the other table
- # %end
- import sys
- import string
- import grass.script as grass
- from grass.exceptions import CalledModuleError
- def main():
- map = options["map"]
- layer = options["layer"]
- column = options["column"]
- otable = options["other_table"]
- ocolumn = options["other_column"]
- if options["subset_columns"]:
- scolumns = options["subset_columns"].split(",")
- else:
- scolumns = None
- try:
- f = grass.vector_layer_db(map, layer)
- except CalledModuleError:
- sys.exit(1)
- # Include mapset into the name, so we avoid multiple messages about
- # found in more mapsets. The following generates an error message, while the code
- # above does not. However, the above checks that the map exists, so we don't
- # check it here.
- map = grass.find_file(map, element="vector")["fullname"]
- maptable = f["table"]
- database = f["database"]
- driver = f["driver"]
- if driver == "dbf":
- grass.fatal(_("JOIN is not supported for tables stored in DBF format"))
- if not maptable:
- grass.fatal(
- _("There is no table connected to this map. Unable to join any column.")
- )
- # check if column is in map table
- if column not in grass.vector_columns(map, layer):
- grass.fatal(_("Column <%s> not found in table <%s>") % (column, maptable))
- # describe other table
- all_cols_ot = grass.db_describe(otable, driver=driver, database=database)["cols"]
- # check if ocolumn is on other table
- if ocolumn not in [ocol[0] for ocol in all_cols_ot]:
- grass.fatal(_("Column <%s> not found in table <%s>") % (ocolumn, otable))
- # determine columns subset from other table
- if not scolumns:
- # select all columns from other table
- cols_to_add = all_cols_ot
- else:
- cols_to_add = []
- # check if scolumns exists in the other table
- for scol in scolumns:
- found = False
- for col_ot in all_cols_ot:
- if scol == col_ot[0]:
- found = True
- cols_to_add.append(col_ot)
- break
- if not found:
- grass.warning(_("Column <%s> not found in table <%s>") % (scol, otable))
- all_cols_tt = grass.vector_columns(map, int(layer)).keys()
- # This is used for testing presence (and potential name conflict) with
- # the newly added columns, but the test needs to case-insensitive since it
- # is SQL, so we lowercase the names here and in the test.
- all_cols_tt = [name.lower() for name in all_cols_tt]
- select = "SELECT $colname FROM $otable WHERE $otable.$ocolumn=$table.$column"
- template = string.Template("UPDATE $table SET $colname=(%s);" % select)
- for col in cols_to_add:
- # skip the vector column which is used for join
- colname = col[0]
- if colname == column:
- continue
- use_len = False
- if len(col) > 2:
- use_len = True
- # Sqlite 3 does not support the precision number any more
- if driver == "sqlite":
- use_len = False
- # MySQL - expect format DOUBLE PRECISION(M,D), see #2792
- elif driver == "mysql" and col[1] == "DOUBLE PRECISION":
- use_len = False
- if use_len:
- coltype = "%s(%s)" % (col[1], col[2])
- else:
- coltype = "%s" % col[1]
- colspec = "%s %s" % (colname, coltype)
- # add only the new column to the table
- if colname.lower() not in all_cols_tt:
- try:
- grass.run_command(
- "v.db.addcolumn", map=map, columns=colspec, layer=layer
- )
- except CalledModuleError:
- grass.fatal(_("Error creating column <%s>") % colname)
- stmt = template.substitute(
- table=maptable,
- column=column,
- otable=otable,
- ocolumn=ocolumn,
- colname=colname,
- )
- grass.debug(stmt, 1)
- grass.verbose(_("Updating column <%s> of vector map <%s>...") % (colname, map))
- try:
- grass.write_command(
- "db.execute", stdin=stmt, input="-", database=database, driver=driver
- )
- except CalledModuleError:
- grass.fatal(_("Error filling column <%s>") % colname)
- # write cmd history
- grass.vector_history(map)
- return 0
- if __name__ == "__main__":
- options, flags = grass.parser()
- sys.exit(main())
|