v.db.join.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: v.db.join
  5. # AUTHOR(S): Markus Neteler
  6. # Converted to Python by Glynn Clements
  7. # PURPOSE: Join a table to a map table
  8. # COPYRIGHT: (C) 2007-2021 by Markus Neteler and the GRASS Development Team
  9. #
  10. # This program is free software under the GNU General Public
  11. # License (>=v2). Read the file COPYING that comes with GRASS
  12. # for details.
  13. #
  14. #############################################################################
  15. # %module
  16. # % description: Joins a database table to a vector map table.
  17. # % keyword: vector
  18. # % keyword: attribute table
  19. # % keyword: database
  20. # %end
  21. # %option G_OPT_V_MAP
  22. # % description: Vector map to which to join other table
  23. # % guidependency: layer,column
  24. # %end
  25. # %option G_OPT_V_FIELD
  26. # % description: Layer where to join
  27. # % guidependency: column
  28. # %end
  29. # %option G_OPT_DB_COLUMN
  30. # % description: Identifier column (e.g.: cat) in the vector table to be used for join
  31. # % required : yes
  32. # %end
  33. # %option G_OPT_DB_TABLE
  34. # % key: other_table
  35. # % description: Other table name
  36. # % required: yes
  37. # % guidependency: ocolumn,scolumns
  38. # %end
  39. # %option G_OPT_DB_COLUMN
  40. # % key: other_column
  41. # % description: Identifier column (e.g.: id) in the other table used for join
  42. # % required: yes
  43. # %end
  44. # %option G_OPT_DB_COLUMN
  45. # % key: subset_columns
  46. # % multiple: yes
  47. # % required: no
  48. # % description: Subset of columns from the other table
  49. # %end
  50. import sys
  51. import string
  52. import grass.script as grass
  53. from grass.exceptions import CalledModuleError
  54. def main():
  55. map = options["map"]
  56. layer = options["layer"]
  57. column = options["column"]
  58. otable = options["other_table"]
  59. ocolumn = options["other_column"]
  60. if options["subset_columns"]:
  61. scolumns = options["subset_columns"].split(",")
  62. else:
  63. scolumns = None
  64. try:
  65. f = grass.vector_layer_db(map, layer)
  66. except CalledModuleError:
  67. sys.exit(1)
  68. # Include mapset into the name, so we avoid multiple messages about
  69. # found in more mapsets. The following generates an error message, while the code
  70. # above does not. However, the above checks that the map exists, so we don't
  71. # check it here.
  72. map = grass.find_file(map, element="vector")["fullname"]
  73. maptable = f["table"]
  74. database = f["database"]
  75. driver = f["driver"]
  76. if driver == "dbf":
  77. grass.fatal(_("JOIN is not supported for tables stored in DBF format"))
  78. if not maptable:
  79. grass.fatal(
  80. _("There is no table connected to this map. Unable to join any column.")
  81. )
  82. # check if column is in map table
  83. if column not in grass.vector_columns(map, layer):
  84. grass.fatal(_("Column <%s> not found in table <%s>") % (column, maptable))
  85. # describe other table
  86. all_cols_ot = grass.db_describe(otable, driver=driver, database=database)["cols"]
  87. # check if ocolumn is on other table
  88. if ocolumn not in [ocol[0] for ocol in all_cols_ot]:
  89. grass.fatal(_("Column <%s> not found in table <%s>") % (ocolumn, otable))
  90. # determine columns subset from other table
  91. if not scolumns:
  92. # select all columns from other table
  93. cols_to_add = all_cols_ot
  94. else:
  95. cols_to_add = []
  96. # check if scolumns exists in the other table
  97. for scol in scolumns:
  98. found = False
  99. for col_ot in all_cols_ot:
  100. if scol == col_ot[0]:
  101. found = True
  102. cols_to_add.append(col_ot)
  103. break
  104. if not found:
  105. grass.warning(_("Column <%s> not found in table <%s>") % (scol, otable))
  106. all_cols_tt = grass.vector_columns(map, int(layer)).keys()
  107. # This is used for testing presence (and potential name conflict) with
  108. # the newly added columns, but the test needs to case-insensitive since it
  109. # is SQL, so we lowercase the names here and in the test.
  110. all_cols_tt = [name.lower() for name in all_cols_tt]
  111. select = "SELECT $colname FROM $otable WHERE $otable.$ocolumn=$table.$column"
  112. template = string.Template("UPDATE $table SET $colname=(%s);" % select)
  113. for col in cols_to_add:
  114. # skip the vector column which is used for join
  115. colname = col[0]
  116. if colname == column:
  117. continue
  118. use_len = False
  119. if len(col) > 2:
  120. use_len = True
  121. # Sqlite 3 does not support the precision number any more
  122. if driver == "sqlite":
  123. use_len = False
  124. # MySQL - expect format DOUBLE PRECISION(M,D), see #2792
  125. elif driver == "mysql" and col[1] == "DOUBLE PRECISION":
  126. use_len = False
  127. if use_len:
  128. coltype = "%s(%s)" % (col[1], col[2])
  129. else:
  130. coltype = "%s" % col[1]
  131. colspec = "%s %s" % (colname, coltype)
  132. # add only the new column to the table
  133. if colname.lower() not in all_cols_tt:
  134. try:
  135. grass.run_command(
  136. "v.db.addcolumn", map=map, columns=colspec, layer=layer
  137. )
  138. except CalledModuleError:
  139. grass.fatal(_("Error creating column <%s>") % colname)
  140. stmt = template.substitute(
  141. table=maptable,
  142. column=column,
  143. otable=otable,
  144. ocolumn=ocolumn,
  145. colname=colname,
  146. )
  147. grass.debug(stmt, 1)
  148. grass.verbose(_("Updating column <%s> of vector map <%s>...") % (colname, map))
  149. try:
  150. grass.write_command(
  151. "db.execute", stdin=stmt, input="-", database=database, driver=driver
  152. )
  153. except CalledModuleError:
  154. grass.fatal(_("Error filling column <%s>") % colname)
  155. # write cmd history
  156. grass.vector_history(map)
  157. return 0
  158. if __name__ == "__main__":
  159. options, flags = grass.parser()
  160. sys.exit(main())