2009-06-04 43 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的問題,看看我能否在該級別找到解決方案。