代码之家  ›  专栏  ›  技术社区  ›  unmounted

从python中的字符串推断适当的数据库类型声明

  •  1
  • unmounted  · 技术社区  · 16 年前

    我正在用python字典构建一些postgres表,其中'key':'value'对对应于列'key'和字段'value'。这些是从.dbf文件生成的--我现在将.dbf文件的内容通过管道传输到一个脚本中,该脚本返回一系列dict,如:

    {'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工具也同样满意。)

    有没有一个包可以做到这一点,或者有一个简单的方法来实现它?

    4 回复  |  直到 16 年前
        1
  •  2
  •   John Machin Santi    16 年前

    您不需要推断类型声明!!!!

    您可以直接从.dbf文件派生所需的内容。每列都有一个名称、一个类型代码(C=字符,N=数字,D=日期(YYYYMMDD),L=逻辑(T/F),如果文件来自FoxPro,则还有更多类型)、长度(如果相关)和小数位数(对于N类型)。

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

    字典?为什么?只需做少量的工作,就可以修改该软件,以根据这些列定义生成一个create table语句,并为每一行数据添加一个insert语句。

    我假设您使用的是几种已发布的python dbf读取模块之一。它们中的任何一个都应该具有您需要的功能:打开一个.dbf文件,获取列名,获取列类型等信息,获取每一行数据。如果您对所使用的模块不满意,请与我联系;我有一个未发布的模块,就阅读dbfs而言,它结合了其他模块的更好功能,避免了最糟糕的功能,与纯python实现一样快,处理所有Visual FoxPro数据类型和NullFlags伪列,处理memoes,e等。

    高温高压

    不受欢迎的= 附录: 当我说你不需要推断类型时,你没有说清楚你有一堆C类型的包含数字的字段。

    FIPS字段:有些带有前导零,有些没有前导零。如果你要使用它们,你就要面对“012”!='12'!=12问题。我建议去掉前导零,并将它们保存在整型列中,在报表中恢复前导零,如果您真的需要的话,也可以这样做。为什么每个州和县都有两个FIP?

    填充:在示例文件中,几乎所有都是整数。四个是40552.0000,一个合理的数字是空的。你似乎认为人口是重要的,并问“有没有可能有一小部分人口字段包含…”?“数据中有任何可能。不要怀疑和猜测,调查!我强烈建议您按照人口顺序对数据进行排序并进行观察;您会发现同一州的多个地方具有相同的人口计数。例如,纽约州有35个地方的人口是8008278,分布在6个县。其中29个的pl_fips值为51000;5个的pl_fips值为5100——看起来像是一个尾随的零问题:-(

    决定float和int的提示:try anum=float(chars) 第一 ;如果成功,检查int(anum)=anum。

    身份证:很棒的“唯一身份证”;59个案件中,它不是一个int——在加拿大有几个(网站上说“美国城市”;这是一些未解决的边界争端的产物吗?)其中一些包含单词“number”,还有一些是空的。

    低垂的果实:我本以为推断人口实际上是整数,比地面高0.1英寸。

    这里面有一个严重的缺陷,如果全部([int(value)……逻辑:

    >>> 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,但是您添加了附加条件“并且都是非零的”。稍后再浮出几行。

    如果只有一个零值,则声明该列不是整数。 即使在修复了这个问题之后,如果只有一个空值,也可以将其称为varchar。 我的建议是:计算有多少是空的(在规范化空白之后(应该包括nbsp))、多少是限定为整数的、多少个非整数非空的是限定为浮点的,以及多少“其他”。检查“其他”项;决定是拒绝还是修复;重复直到满意为止:—)

    我希望这些能有所帮助。

        2
  •  5
  •   Unknown    16 年前

    不要使用eval。如果有人插入了错误的代码,它会破坏数据库或服务器。

    而是用这些

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

    其他的都可以是varchar

        3
  •  1
  •   jacob    16 年前

    您可以通过以下方式确定整数和不安全的浮点数: 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"
    
        4
  •  1
  •   unmounted    16 年前

    感谢您的帮助,这是一个有点长的更新,这里是我如何组合的答案。我从这样的dict列表开始,它是从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是一个很好的默认值,float和int基本上足以满足所有其他条件, 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_
    

    然后将返回的dict格式化为 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是联邦信息处理标准,它应该是一个介于0和100000之间的整数。人口、海拔等。也许我有更多关于Postgres的特定问题,我不介意丢失少量数据,或将其推到一个表中查找错误或其他内容,同时尝试更改say the population字段的类型。DBF类型检查有多严格?例如,我看到每个shp2pgsql的填充是varchar(12)。有没有可能,人口中的一小部分字段包含“2445 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                |
    

    另一方面,如果我检查all中的每个值(['list'、'of'、'everything'…]),我会得到一个更像第一个的模式。我可以容忍这里的一些数据丢失——如果某个城镇的输入错误,并且它不会显著影响人口数量,等等。

    我只使用一个叫 dbview 要将DBF文件传输到这些脚本中,我不会尝试映射任何格式的本机功能。我认为在这方面,SHP2PGSQL会选择低挂水果。对于dbview或其他包的任何建议都是受欢迎的——尽管在其他情况下,我可能不使用dbf文件,而且无论如何都需要找到最好的类型。我还要问一个关于PostgreSQL的问题,看看我是否能在那个级别找到解决方案。