2009-06-04 44 views
1

我从Python字典中构建了一些Postgres表,其中{'key':'value'}对与列'key'和字段'value'对应。这些是从的.dbf文件生成的 - 我现在管dbf文件的内容到返回类似类型的字典列表的脚本:从Python中的字符串推断适当的数据库类型声明

{'Warngentyp': '', 'Lon': '-81.67170', 'Zwatch_war': '0', 'State':... 

目前,我把这些成SQLite数据库,没有类型声明,然后将其转储到.sql文件,手动编辑架构并导入到Postgres。

我希望能够推断出正确的类型声明,基本上遍历一系列字符串,如['0','3','5']或['ga','ca','tn ']或['-81.009','135.444','-80.000']并生成类似'int','varchar(2)','float'的东西。 (我会对Python,Postgres或SQLite工具同样感到满意。)

是否有一个包装可以执行此操作,或者直接执行它?

回答

2

你不需要提供类型的声明!

您可以直接从.dbf文件中派生出您想要的内容。每一列都有一个名称,一个类型代码(C =字符,N =数字,D =日期(yyyymmdd),L =逻辑(T/F),如果文件来自Foxpro则加上更多类型),长度)和一些小数位(对于类型N)。

无论您用什么软件从.dbf文件中挖掘数据,都需要使用该信息将每段数据转换为适当的Python数据类型。

字典?为什么?只需少量工作,就可以修改该软件,以根据这些列定义生成CREATE TABLE语句,并为每行数据添加INSERT语句。

我推测你正在使用几个已发布的Python DBF阅读模块之一。他们中的任何一个都应该具备您需要的功能:打开.dbf文件,获取列名,获取列类型等信息,获取每行数据。如果您对使用的模块不满意,请与我交谈;我有一个未发表的文章,就阅读DBF而言,结合其他人的更好功能,避免最糟糕的特性,与纯Python实现一样快,处理所有Visual Foxpro数据类型和_NullFlags伪-column,处理memoes,等等等等

HTH

========= 附录: 当我说你也没必要来推断类型的,你没有已清楚你有一堆包含数字的C类字段。

FIPS字段:某些字符与一些没有前导零。如果你打算使用它们,你就会面临'012'!='12'!= 12的问题。我建议剥离前导零并将它们保留在整数列中,恢复报告中的前导零或任何需要的地方。为什么每个州都有两个县和两个县?

人口:在样本文件中,几乎所有的都是整数。四个就像40552.0000,合理的数字是空白/空的。你似乎认为人口是重要的,并问道:“有可能一小部分人口领域包含......”吗?任何事情都可能在数据中。不要怀疑和猜测,调查!我强烈建议您按照人口顺序对数据进行排序并进行眼球检查;你会发现在同一州的多个地方共享相同的人口数量。例如。纽约州有35个地方的pop'n被称为8,008,278;他们遍布6个县。其中29人的PL_FIPS值为51000; 5有5100 - 看起来像一个尾随零问题:-(

提示浮法和int之间决定:尝试ANUM =浮子(字符)第一;如果成功,则检查INT(ANUM)== ANUM

ID:精彩的“唯一身份证”; 59个案例中,它不是一个整数 - 在加拿大有几个(网站上称“美国城市”;这是一些未解决的边界争议的产物? '数字',还有一些是空的。

低悬的果实:我会认为推断人口实际上是整数在地面以上0.1英寸:-)

有在一个严重缺陷,如果所有([INT(值)...逻辑:

>>> all([int(value) for value in "0 1 2 3 4 5 6 7 8 9".split()]) 
False 
>>> all([int(value) for value in "1 2 3 4 5 6 7 8 9".split()]) 
True 
>>> 

你显然认为你正在测试的所有字符串可以被转换成int,但你增加骑手“并且都是非零”。同样在稍后浮动几行。

IOW如果只有一个零值,则声明该列不是整数。 即使修复后,如果只有一个空值,你可以称它为varchar。 我的建议是:统计有多少人是空的(在规范化空白(应包括NBSP)后),多少人有资格作为整数,多少非整数非空人有资格担任浮动,以及多少“其他”。检查“其他”;决定是拒绝还是修复;重复,直到高兴:-)

我希望这有助于这一点。

1

您可以通过type(eval(elem))不安全地确定整数和浮点数,其中elem是列表的一个元素。 (但你需要检查ELEM可能的恶意代码)

一个更安全的方式可能是做以下

a = ['24.2', '.2', '2'] 
try: 
    if all(elem.isdigit() for elem in a): 
     print("int") 
    elif all(float(elem) for elem in a): 
     print("float") 
except: 
    i = len(a[0]) 
    if all(len(elem)==i for elem in a): 
     print("varchar(%s)"%i) 
    else: 
     print "n/a" 
+0

我也不认为eval是不安全的(在我的情况下),这是一个很好的答案。我可能会有管理员上传数据,因此在某些假设中可能存在风险,但我没有规定不可靠的数据。 – unmounted 2009-06-06 06:16:23

5

不要使用eval。如果有人插入错误的代码,它可以用软管连接你的数据库或服务器。

而是使用这些

def isFloat(s): 
try: 
    float(s) 
    return True 
except (ValueError, TypeError), e: 
    return False 


str.isdigit() 

和其他一切可以为VARCHAR

+3

“如果有人插入错误的代码,它可以使数据库或服务器软管”什么?这将如何发生?谁是可以插入错误代码的“人”?什么“坏代码”将软管服务器? '进口系统; sys.crash_server(True)'作为列值? – 2009-06-04 19:46:17

+0

+1:避免显式类型比较。 – 2009-06-04 19:46:55

1

感谢您的帮助,这是一个更新有点长,这里是我如何组合的答案。我开始像这样类型的字典列表,从DBF文件生成:

dbf_list = [{'Warngentyp': '', 'Lon': '-81.67170', 'Zwatch_war': '0', 'State':... 

然后返回每列1000点的值来测试最佳DB类型声明函数:{'column_name':['list', 'of', 'sample', 'values'], 'col2':['1','2','3','4'...这样的:

def sample_fields(dicts_, number=1000): #dicts_ would be dbf_list from above 
    sample = dict([[item, []] for item in dicts_[1]]) 
    for dict_ in dicts_[:number]: 
     for col_ in dict_: 
      sample[col_].append(dict_[col_]) 
    return sample 

然后你结合的未知和雅各的做法:VARCHAR是一个很好的默认和花车和整数基本上够一切,all是明确的和快速的:

def find_typedefs(sample_dict): #arg is output of previous function 
    defs_ = {} 
    for key in sample_dict: 
     defs_[key] = 'varchar(255)' 
     try: 
      if all([int(value) for value in sample_dict[key]]): 
       defs_[key] = 'int' 
     except: 
      try: 
       if all([float(value) for value in sample_dict[key]]): 
        defs_[key] = 'float' 
      except: 
       continue 
    return defs_ 

然后将返回的字典格式化为create table语句,迭代原始大列表中的值并将它们输入到数据库中。它工作得很好,我现在跳过了中级的sqlite步骤,再次感谢。

John Machin更新:我正在使用随PostGIS分发的shp2pgsql库。它创建的模式就像下面像this one来源:

Column |   Type   | 
------------+-----------------------+- 
gid  | integer    | 
st_fips | character varying(7) | 
sfips  | character varying(5) | 
county_fip | character varying(12) | 
cfips  | character varying(6) | 
pl_fips | character varying(7) | 
id   | character varying(7) | 
elevation | character varying(11) | 
pop_1990 | integer    | 
population | character varying(12) | 
name  | character varying(32) | 
st   | character varying(12) | 
state  | character varying(16) | 
warngenlev | character varying(13) | 
warngentyp | character varying(13) | 
watch_warn | character varying(14) | 
zwatch_war | bigint    | 
prog_disc | bigint    | 
zprog_disc | bigint    | 
comboflag | bigint    | 
land_water | character varying(13) | 
recnum  | integer    | 
lon  | numeric    | 
lat  | numeric    | 
the_geom | geometry    | 

有东西,那里有是错误的 - FIPS是联邦信息处理标准,它应该像100000 0和东西之间的整数。人口,海拔等也许我有更多的postgres特定问题,我不介意丢失少量数据,或将它推入表格以查找错误或其他内容,同时尝试更改人口字段中的类型。 dbf类型检查有多严格?例如,我看到每个shp2pgsql的人口是varchar(12)。是否可能有一小部分人口领域包含“2,445 Est。”之类的东西?如果我把我阐述了在这个问题上,与第一千个记录的方法,我得到一个架构是这样的:

Column |   Type   | 
------------+------------------------+- 
warngentyp | character varying(255) | 
lon  | double precision  | 
zwatch_war | character varying(255) | 
state  | character varying(255) | 
recnum  | character varying(255) | 
pop_1990 | integer    | 
land_water | character varying(255) | 
elevation | integer    | 
prog_disc | integer    | 
comboflag | character varying(255) | 
sfips  | integer    | 
zprog_disc | integer    | 
pl_fips | integer    | 
county_fip | integer    | 
population | integer    | 
watch_warn | integer    | 
name  | character varying(255) | 
st   | character varying(255) | 
lat  | double precision  | 
st_fips | integer    | 
cfips  | integer    | 
id   | integer    | 
warngenlev | integer    | 

在另一方面,如果我在所有([“名单”检查每一个值, '','所有'...]),我得到的模式更像第一个。我可以容忍这里的一些数据丢失 - 如果某个城镇的入口是错误的,并且不会显着影响人口数字等。

我只使用一个名为dbview的旧包来管道dbf文件到这些脚本 - 我不想映射任何格式的本地功能。我认为shp2pgsql在这方面会取得低下的成果。对于dbview或其他软件包的任何建议都是值得欢迎的 - 尽管在其他情况下我可能没有使用dbf文件,并且无论如何都需要找到最好的类型。我也会问一个关于postgresql的问题,看看我能否在该级别找到解决方案。