diadia

興味があることをやってみる。自分のメモを残しておきます。

postgresqlにcopyでcsvデータを反映させる

copyを実行する方法

psqlコマンドでデータベースに接続した状態でcopyコマンドを実施する
psycopg2でpostgresqlに接続し、copy_fromメソッドを使ってcopyを実施する。
copy_expert(), execute()

できないこと、注意点1

csvファイルの指定カラムのみをテーブルにインポート
上記の記事を参考に色々試してみた。具体的には複数の列が存在するcsvファイルから特定の列のみコピーすることを試みた。上記の記事はテーブルにある複数のカラムから特定のカラムのみをcsvファイルのデータでコピーするということだと判断した。つまりcopyコマンドを実施する際にcsvファイルに含まれるデータはすべてコピー対象になってしまうのであって、csvファイルに含まれるデータから特定のデータのみを選んで反映させることではない、ということだ。

できないこと、注意点2

utf8には種類がありbomありとbomなしがある。bomありで保存することでintegerやdouble precisionなどのコピーが上手く出来ない事がわかった。一方charはコピーが上手く行った。windowsのメモ帳やエクセルでcsvを作成するときは注意すること。

psqlコマンドでデータベースに接続した状態でcopy

#データベースに接続する
psql -U ユーザー名 -d データベース名
#ex
psql -U postgres -d postgres

#copyコマンドの実行
\copy table名(userid, user_name) from 'csvのパス' with csv encoding 'utf8' ;
#ex
\copy my_test_table(userid, user_name) from 'C:\Users\USER\Desktop\sushi.csv' with csv encoding 'utf8' ; 

psycopg2でpostgresqlに接続し、copy_fromメソッドを使う

psycopg2で接続しcopy_fromメソッドでコピーする方法がある。
http://initd.org/psycopg/docs/cursor.html#cursor.copy_from
このメリットはプログラムの流れに沿ってcopyを実行することができることだ。しかしながらデメリットとしてcsvヘッダーを無視してコピーする事ができない事がある。コピーする前にヘッダーを削除してcopyを実行する事が必要だ。

#サンプル
import psycopg2

conn = psycopg2.connect(user="postgres", port=5432, dbname="postgres")
cur  = conn.cursor()

f = open('C:\\Users\\USER\\Desktop\\sushi.csv', mode='r', encoding='utf-8')
cur.copy_from(f, 'my_test_table', sep=',', columns=('serid', 'user_name'))
conn.commit()

f.close()

copy_expert

実際にpsqlに接続してcopyコマンドを実施するのと同じ機能を提供するらしい

http://initd.org/psycopg/docs/usage.html#using-copy-to-and-copy-fromhttp://initd.org/psycopg/docs/usage.html#using-copy-to-and-copy-from

DB設計について

テーブルの設計について学習している。

メモを残す。

 

テーブル設計には正規化という概念が重要らしい。正規化には5つの種類がある。通常は3つまでチェックして修正すれば良いらしい。

第1正規形の定義:1つのセルの中には1つの値しか含まないこと

第2正規形の定義:テーブル内で部分関数従属を解消し、完全関数従属のみ

テーブルを作ること

 

ある座標点からxkm以内に存在するデータオブジェクトをmapに表示したい

PostGISを使う場合はST_DWithin()関数を使えばよいだろう。
ではgeodjangoではどうやって特定の範囲内のデータオブジェクトを取得するか?
psycopg2を使ってデータオブジェクトのみ取得することももやり方としてはできるだろう。
しかしここではgeodjangoの備え付けの機能を使うとしたらどのように書くかを記しておく。

参考:https://docs.djangoproject.com/en/2.2/ref/contrib/gis/tutorial/#automatic-spatial-transformations
https://docs.djangoproject.com/en/2.2/ref/contrib/gis/db-api/#distance-lookups

特定の範囲内に存在するデータオブジェクトはdistance_ltを使う

djangoではデータオブジェクトはfilterを使って取得してきた。icontainsとか使うやり方でdistance_ltやdistance_lteを使えば特定の範囲内のデータオブジェクトは取得できる。
問題はどのように使うかだ。ここで引っかかってしまったのできちんと記録しておく。

ドキュメントの例文では以下のように書いてある。

qs = SouthTexasCity.objects.filter(point__distance_lte=(pnt, D(km=7)))

はじめにこの意味を確認するとpntから7km以内のSouthTexasCityのクエリ結果を示している。Dはfrom django.contrib.gis.measure import Dで別途インポートする必要がある。そして当初pntはデータオブジェクトを用いるのだろうと思っていた。しかしながら使うのはデータオブジェクトではなくgeometryのオブジェクトで、GEOSGeometryやPointクラスのインスタンスでなければならないことが分かった。GEOSGeometryやPointについては現状違いがわからない。少なくともpointのデータを作ることに関しては両者どちらでも作成できると思われる。

GEOSGeometryの使い方

from django.contrib.gis.geos import GEOSGeometry, Point
my_place    = UserPointModel.objects.get(id=1)
point = 'POINT({lon} {lat})'.format(lon=my_place.point.x,lat=my_place.point.y)
pnt = GEOSGeometry(point, srid=4326)

Pointの使い方

from django.contrib.gis.geos import GEOSGeometry, Point
my_place    = UserPointModel.objects.get(id=1)
pnt = Point(my_place.point.x, my_place.point.y, srid=4326)

本題のdistance_lteの使い方

サンプル
class MapsView(View):
    def get(self, request):
        my_place    = UserPointModel.objects.get(id=1)
        pnt = Point(my_place.point.x, my_place.point.y, srid=4326)

        all_objects = UserPointModel.objects.filter(point__distance_lte=(pnt, D(km=2)))
        context = {"all_objects":all_objects}
        return render(request, "maps/list.html",context)    

unittestのsys.exit()の抜け方

def tashizan(a,b):
    try:
        return int(a)+int(b)
    except ValueError:
        print("a,bは整数ではなく文字列の可能性があります")
        sys.exit(1)
import unittest , tashizan

class TestTashizan(unittest.TestCase):

    def test_tashizan_1(self):
        expected = 8
        actual = tashizan(2, 6)
        self.assertEqual(expected, actual)

    def test_tashizan_2(self):
        expected = 8
        actual = tashizan(6, "2")
        self.assertEqual(expected, actual)


    def test_tashizan_3(self):
        with self.assertRaises(SystemExit):
            tashizan("a", "6")
    


if __name__ == "__main__":
    unittest.main(exit=False)

どうやらエラーのときはwith を使ってassertRaises()を使い、エラーの出る使い方をくるむようだ。

デバッグについて

デバッグの方法論

今までプログラムを走らせてエラーが出たところでデバッグを行っていた。これではあまり具合が良くないと分かった。 それは設計(ロジック)が正しくて、コードが正しくて、でもインプットデータが問題であるかもしれない。インプットデータが正しくてもインプットデータを一種類しか使ってないと、エラー原因を正確に確定することができない。したがってエラーが出たならまずはインプットデータを複数使って正確なエラーの特定をすることが大事。

python csvファイルを結合

csvを結合する方法

  • ライブラリpandasを使う方法
  • open関数を使う方法

ライブラリpandasを使う方法

import pandas as pd

csv_list = ['csv1_path', 'csv2_path', 'csv3_path', 'csv4_path']
pd_list  = [pd.read_csv(file) for file in csv_list]
df       = pd.concat(pd_list)
df.to_csv("hoge.csv", index=False, encoding="utf-8")

この方法は時間がかかる恐れがある。以下の方法もあるので試してみる。どちらが速いか分かったら追記しようと思う。

open関数を使う方法

下記の方法はpandasを使った場合と同様、一番目のファイルのヘッダーは残し、追加するファイルのヘッダーは削除して追加するやり方になる。ヘッダーを排除するためにreadlines()で各行をリスト化し、ヘッダーを削除したリストとして[1:-1]でスライスしている。この方法を使うとpandasより速くできるか検証が必要である。

merge_csv_list = ['csv1_path', 'csv2_path', 'csv3_path', 'csv4_path']

for n in range(0,len(merge_csv_list)):
    if n == 0:  
        base_file = open(merge_csv_list[0], "a", encoding="utf-8")
        continue
    else:
        add_file = open(merge_csv_list[n], "r", encoding="utf-8")
        base_file.write(",".join(add_file.readlines()[1:-1]))
        add_file.close()
        continue
base_file.close()
    

geodjangoの環境構築

https://docs.djangoproject.com/ja/2.2/ref/contrib/gis/install/ geodjangoを使うには、python,django,空間データベース、地理空間ライブラリが必要。 PostgreSQLで空間データベースを使うには、ライブラリであるGEOS,GDAL,PROJ.4,PostGISが必要である。

macでのgeodjangoを構築する

https://docs.djangoproject.com/ja/2.2/ref/contrib/gis/install/#macos
https://homata.gitbook.io/geodjango/qian-zhun-bei/install#mac-osx-1

windows postgresqlのデータのディレクトリを変更したい

postgresqlwindowsにインストールする場合

windowsの場合はインストーラーを起動するとdataの置き場所をウィザードに従って自分で指定することができた。 しかしながらインストール後にデータディレクトリを変更(新たにデータクラスタも作成)する場合にはどのように実行するのかわからない。どうすればよいのか

memoを残しておく

まずservices smcでpostgresqlを見ると、実行ファイルパスは以下のようになっていた。

"C:\Program Files\PostgreSQL\11\bin\pg_ctl.exe" runservice -N "postgresql-x64-11" -D "C:\pgdata\11" -w

で、 -Dはデータの読込み先を表す。またpg_ctl.exeはpostgresqlのサービスの起動、停止、再起動を実行するためのコマンドである。だからdataの読込み先を新しく作ったdataの場所に変更してpostgresqlを起動しなおせば、新しいデータクラスタでdataを書くことができると思っていた。だが今のところできない・

成功した方法

 


現在稼働しているpostgresqlのサービスを停止する。これはservices mscを使ってpostgresqlの停止を行った。その他の方法としてGUIではなくCUI

pg_ctl stop -U postgres

でやることができると思う。

ドロップボックス上にpostgres_dataディレクトリを作成した。
ここにテーブルやテーブルデータを入れることになる。 ディレクトリを作成しただけなので、このディレクトリをデータベースクラスタ化する。これはinitdbコマンドを使う。

initdb --encoding=UTF8 --no-locale --username=postgres -W -D "C:\Users\USER\Dropbox\postgres_data"
#コマンドの結果
データベースシステム内のファイルの所有者は"USER"となります。
このユーザがサーバプロセスも所有する必要があります。

データベースクラスタロケール"C"で初期化されます。
デフォルトのテキスト検索設定はenglishに設定されました。

データベージのチェックサムは無効です。

新しいスーパユーザのパスワードを入力してください:
再入力してください:

既存のディレクトリC:/Users/USER/Dropbox/postgrestestの権限を修正します ... 完了
サブディレクトリを作成します ... 完了
max_connectionsのデフォルト値を選択します ... 100
shared_buffersのデフォルト値を選択します ... 128MB
selecting default timezone ... Asia/Tokyo
動的共有メモリの実装を選択します ... windows
設定ファイルを作成します ... 完了
ブートストラップスクリプトを実行します ... 完了
ブートストラップ後の初期化を行っています ... 完了
データをディスクに同期します...完了

警告: ローカル接続で"trust"認証を有効にします。
この設定はpg_hba.confを編集するか、次回のinitdbの実行の際であれば-Aオプ
ション、または、--auth-localおよび--auth-hostを使用することで変更するこ
とができます。

成功しました。以下のようにしてデータベースサーバを起動できます。

    pg_ctl -D ^"C^:^\Users^\USER^\Dropbox^\postgrestest^" -l <ログファイル> start

次にpostgresqlを稼働させる。これはpg_ctl start コマンドで動かす事ができる。

pg_ctl start -U postgres -D "C:\Users\USER\Dropbox\postgres_data" -w

これで動かすことができた。 参考http://koshian2.hatenablog.jp/entry/2018/02/17/213446

https://kenpg.bitbucket.io/blog/201506/04.html

人にコードを見せるときには

まずコードだけ見せない。設計書、コード、テストをセットにする。

どの順番にモジュールを使うのかを図とか絵で説明するのは設計書のレベル。

設計書でもっと詳しく確認したいときにコードを確認する流れとなる。

感覚としてはコード無しで構造が分かるものを準備すること。

またコードに関しては、パラメータやDB情報などを記載した設定ファイルを準備すること、そしてコード全体としてメンテナンス性に優れた構造にする。

また設計書、コードの信頼性を高めるテストを添付する。

 

まずフローチャートを書く。これは人にどのような論理でコードを書いているか説明できるとともに、自分が書こうとするコードの論理構造を明確に理解できる。フローチャートを用いると、同じフローを視覚を通して発見できるので関数化しやすくなる。また関数化しなくとも冗長な部分を視覚的に発見するメリットもある。

納品物のドキュメントはエクセルなどのファイルにしておく。

納品物の構成もPROGRAM、設計書、INPUT_DIRにしておくと良い。 

postgres create

    
import pandas as pd
import zipfile,os,time

ZIP_DIR     = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\ZIP_DIR\\"
UNZIP_DIR   = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\UNZIP_DIR\\"
MERGED_CSVS = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\DIR_DIR\\MERGED_CSVS\\"
zip_files = os.listdir(ZIP_DIR)


#zipファイルを解凍してrenameを実行。すべて解凍する。
userid_list = []
for zf in zipfiles:
	with zipfile.ZipFile(ZIP_DIR+zf) as zip:
		time.sleep(0.0000001)
		timestamp = str(time.time()).replace(".", "_")
		ziplst = zip.namelist()
		for f in ziplst:
			#チェック
			print(f)
			sys.exit()
			userid = f.split("_","_")[X]
			zip.extract(f, UNZIP_DIR)
			os.rename("".join([UNZIP_DIR, f]), "".join([UNZIP_DIR, userid, span, timestamp, ".csv"]))
			userid_list.append(userid)



#同一useridのcsvファイルをUNZIPディレクトリから取得
unzip_list = os.listdir(UNZIP_DIR)

for userid in set(userid_list):
	
	csv_list = [ file for file in unzip_list if userid in file ]
	csv_list.sort()
	print(csv_list)
	for f in csv_list:
		unzip_list.pop(f)

	sys.exit()

	#マージファイルを作成
	pd_list = [ pd.read_csv(csv_file) for csv_file in csv_list ]
	df = pd.concat(pd_list)
	#マージファイルタイトルの作成
	merge_file = "".join([userid, span, ".csv" ]) 
	df.to_csv("".join([MERGED_CSVS, merge_file]), index=False, encoding="utf-8")