2016-08-31 12 views
0

にSQLクエリの結果を回します。これは、permissionsテーブルとmany-to-manyリレーションシップとaddressesテーブルとの多対多リレーションシップを持つユーザーテーブルです。この不自然な例ではそうは、私は(擬似コードで)このようになりますのPostgresのデータベーススキーマを持つコンパクトなPythonのフォーム

results = [ 
    {'pk': 1, 'name': 'Joe', 'permission': 'user', 'address': 'home'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'user', 'address': 'work'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'admin', 'address': 'home'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'admin', 'address': 'work'}, 
    {'pk': 2, 'name': 'John', 'permission': 'user', 'address': 'home'}, 
] 

Pythonで

私は正しいSQLAlchemyのクエリの呪文を実行すると、私は(Pythonで辞書のリストに変換した後に)このような何かを見て、結果を取り戻します、Joeはユーザーと管理者の両方です。ジョンはユーザーだけです。ジョーの自宅住所と仕事住所の両方がデータベースに存在します。ジョンの自宅の住所だけが存在します。

質問は、これらのSQLクエリの 'results'から下のよりコンパクトな 'desired_results'に行く最良の方法を知っている人はいますか?

desired_results = [ 
    { 
     'pk': 1, 
     'name': 'Joe', 
     'permissions': ['user', 'admin'], 
     'addresses': ['home', 'work'] 
    }, 
    { 
     'pk': 2, 
     'name': 'John', 
     'permissions': ['user'], 
     'addresses': ['home'] 
    }, 
] 

必要な追加情報:私は多対多の関係を持っている各フィールドのdesired_resultsで使用したいのラベル」を記述した辞書の小さなリスト。

relationships = [ 
    {'label': 'permissions', 'back_populates': 'permission'}, 
    {'label': 'addresses', 'back_populates': 'address'}, 
] 

最終的な考慮事項は、私はこの質問の目的のために一緒に具体例を入れているが、一般的に、私は関係の任意の量を想定し、一般的にSQLデータベースを照会の問題を解決しようとしています。 SQLAlchemy ORMはこの問題をうまく解決しますが、私はSQLAlchemy Coreを使用することに限られています。私自身のソリューションを構築しようとしています。

アップデートはここで答えですが、私はそれが最高の/最も効率的なソリューションだか分かりません。誰かが何か良いものを考え出すことができますか?

# step 1: generate set of keys that will be replaced by new keys in desired_result 
back_populates = set(rel['back_populates'] for rel in relationships) 

# step 2: delete from results keys generated in step 1 
intermediate_results = [ 
    {k: v for k, v in res.items() if k not in back_populates} 
    for res in results] 

# step 3: eliminate duplicates 
intermediate_results = [ 
    dict(t) 
    for t in set([tuple(ires.items()) 
    for ires in intermediate_results])] 

# step 4: add back information from deleted fields but in desired form 
for ires in intermediate_results: 
    for rel in relationships: 
     ires[rel['label']] = set([ 
      res[rel['back_populates']] 
      for res in results 
      if res['pk'] == ires['pk']]) 

# done 
desired_results = intermediate_results 

+2

「リレーションシップ」リストを使用して希望の結果を得ることは何ですか? – AlokThakur

+0

あなたにコードを教えてください。 'relationships'と' results'リストについては何を仮定できますか?そして、それらは完全に正規であると仮定し、すべてのフィールドが表示されていると仮定できますか? –

+0

彼らは完全に規則的です。すべてのフィールドが表示されます。 – bcmyers

答えて

1

部分エントリのグループを反復することは、itertools.groupbyのジョブのように見えます。

しかし、最初にrelationshipsを使いやすいフォーマットに入れてください。back_populates:label辞書?

conversions = {d["back_populates"]:d['label'] for d in relationships} 

次の我々はそれがエントリの異なるグループを区別するkeyfuncが必要になりますitertools.groupbyを使用することになりますので。

: だから初期 resultsから一つのエントリを指定して、この機能は、今、私たちはグループでこのような何かを resultsを通過することができます凝縮されることはありません/

def grouper(entry): 
    #each group is identified by all key:values that are not identified in conversions 
    return {k:v for k,v in entry.items() if k not in conversions} 

を変換するだけでペアと辞書を返します。

for base_info, group in itertools.groupby(old_results, grouper): 
    #base_info is dict with info unique to all entries in group 
    for partial in group: 
     #partial is one entry from results that will contribute to the final result 
     #but wait, what do we add it too? 

唯一の問題は、我々が構築した場合、当社のentrybase_infoから、それはgroupbyは、私たちはで動作するようにentryを作成する必要が混乱するということです私はここにset Sを使用しています

entry = {new_field:set() for new_field in conversions.values()} 
entry.update(base_info) 

注我々が最後にリストにそれらを変更する必要がありますJSON-互換性がないため、すべてのcontenceしかし、 一意であるとき、彼らは自然のコンテナであるため。今

は、我々はちょうど最後のエントリは、それが残されているすべての構成され、その後、いったんoriginal

for partial in group: 
    for original, new in conversions.items(): 
     entry[new].add(partial[original]) 

から各newフィールドにaddgroupを反復処理することができます構築するためのエントリを持っていることを変換することですsetは今、我々はそれをどちらかができappendと呼ばれるリストに、list

for new in conversions.values(): 
    entry[new] = list(entry[new]) 

に戻っSとそのエントリが行われますしかし、このプロセスは、基本的にそれは、このような最終的なコードを見て何かを作るgenerator にそれを置くために、より理にかなって結果生成されているので:次にnew_resultsはこのように得でき

import itertools 

results = [ 
    {'pk': 1, 'name': 'Joe', 'permission': 'user', 'address': 'home'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'user', 'address': 'work'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'admin', 'address': 'home'}, 
    {'pk': 1, 'name': 'Joe', 'permission': 'admin', 'address': 'work'}, 
    {'pk': 2, 'name': 'John', 'permission': 'user', 'address': 'home'}, 
] 

relationships = [ 
    {'label': 'permissions', 'back_populates': 'permission'}, 
    {'label': 'addresses', 'back_populates': 'address'}, 
] 
#first we put the "relationships" in a format that is much easier to use. 
conversions = {d["back_populates"]:d['label'] for d in relationships} 

def grouper(entry): 
    #each group is identified by all key:values that are not identified in conversions 
    return {k:v for k,v in entry.items() if k not in conversions} 

def parse_results(old_results, conversions=conversions): 
    for base_info, group in itertools.groupby(old_results, grouper): 
     entry = {new_field:set() for new_field in conversions.values()} 
     entry.update(base_info) 
     for partial in group: #for each entry in the original results set 
      for original, new in conversions.items(): #for each field that will be condensed 
       entry[new].add(partial[original]) 


     #convert sets back to lists so it can be put back into json 
     for new in conversions.values(): 
      entry[new] = list(entry[new]) 

     yield entry 

を:

>>> new_results = list(parse_results(results)) 
>>> from pprint import pprint #for demo purpose 
>>> pprint(new_results,width=50) 
[{'addresses': ['home', 'work'], 
    'name': 'Joe', 
    'permissions': ['admin', 'user'], 
    'pk': 1}, 
{'addresses': ['home'], 
    'name': 'John', 
    'permissions': ['user'], 
    'pk': 2}] 
関連する問題