sqlalchemy many to many problem

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Ruffy
User
Beiträge: 34
Registriert: Dienstag 2. Oktober 2012, 11:26

Hi,

Ich hab mal angefangen meine DB mit sqlalchemy zu laden, allerdings bin ich nun auf ein Problem gestossen bei dem ich einfach nicht weiter komme...

der Code:

Code: Alles auswählen

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *


engine = create_engine('sqlite:///metadata.db', echo=True)

Base = declarative_base()


books_authors_link = Table('books_authors_link', Base.metadata,
	Column('book', Integer, ForeignKey('books.id'), primary_key=True),
	Column('author', Integer, ForeignKey('authors.id'), primary_key=True)
	)


class Authors(Base):
	__tablename__ = 'authors'

	id = Column(Integer, primary_key=True)
	name = Column(String)
	sort = Column(String)
	link = Column(String)

	def __init__(self, name, sort, link):
		self.name = name
		self.sort = link
		self.sort = link

	def __repr__(self):
		return u"<Authors('{0},{1}{2}')>".format(self.name, self.sort, self.link)

class Books(Base):
	__tablename__ = 'books'

	id = Column(Integer,primary_key=True)
	title = Column(String)
	sort = Column(String)
	path = Column(String)
	has_cover = Column(Integer)

	authors = relationship('Authors', secondary=books_authors_link, backref='books')

	def __init__(self, title, sort, path, has_cover, authors, tags):
		self.title = title
		self.sort = sort
		self.path = path
		self.has_cover = has_cover
		self.tags = tags


	def __repr__(self):
		return u"<Books('{0},{1}{2}{3}')>".format(self.title, self.sort, self.path, self.has_cover)

Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()

result = session.query(Books).filter(Books.authors.like('Charlotte Link')).first() 
print result.title
das problem hab ich mit der vorletzten zeile:

Code: Alles auswählen

result = session.query(Books).filter(Books.authors.like('%Link%')).first() 
wenn ich das genau so ausführe bekomme ich die meldung:

Code: Alles auswählen

  File "build/bdist.macosx-10.6-intel/egg/sqlalchemy/sql/operators.py", line 267, in like
  File "build/bdist.macosx-10.6-intel/egg/sqlalchemy/orm/attributes.py", line 123, in operate
  File "build/bdist.macosx-10.6-intel/egg/sqlalchemy/sql/operators.py", line 499, in like_op
  File "build/bdist.macosx-10.6-intel/egg/sqlalchemy/sql/operators.py", line 267, in like
  File "build/bdist.macosx-10.6-intel/egg/sqlalchemy/sql/operators.py", line 148, in operate
NotImplementedError: <function like_op at 0x1007bb140>

Code: Alles auswählen

result = session.query(Books).filter(Books.authors.any(name='Charlotte Link')).first() 
funktioniert ist aber etwas doof da so nur der genaue name geht...

das ganze als join umgebaut funktioniert auch nicht:

Code: Alles auswählen

result = session.query(Books).join(Authors).filter(Authors.name.like('%Link%')).first() 
gibt folgenden fehler aus:

Code: Alles auswählen

sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from.  Tried joining to <class '__main__.Books'>, but got: Can't find any foreign key relationships between 'authors' and 'books'.
Die Foreign Keys sind doch eigentlich definiert und auch die relationships...
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Code: Alles auswählen

result = session.query(Books).filter(Books.authors.like('Charlotte Link')).first() 

Code: Alles auswählen

result = session.query(Books).filter(Books.authors.any(name='Charlotte Link')).first() 
Ohne das jetzt lange angesehen zu haben:
Bei dem Like: wo genau sagst du ihm, dass du dich auf das Feld "name" in "Authors" beziehst?
Ruffy
User
Beiträge: 34
Registriert: Dienstag 2. Oktober 2012, 11:26

gute frage...

Code: Alles auswählen

result = session.query(Books).filter(Books.authors.name.like('Charlotte Link')).first()
funktioniert nicht da (wieso auch immer) das attribut name nicht verfügbar ist...
BlackJack

@Ruffy: Du hattest es mit dem `any()` ja schon fast, nur geht halt kein einfaches Schlüsselwort-Argument:

Code: Alles auswählen

result = (
    session.query(Books)
        .filter(Books.authors.any(Authors.name.like('%Link%')))
        .first()
)
wird zu:

Code: Alles auswählen

SELECT books.id AS books_id, books.title AS books_title,
    books.sort AS books_sort, books.path AS books_path,
    books.has_cover AS books_has_cover 
 FROM books 
WHERE EXISTS (
    SELECT 1 
      FROM books_authors_link, authors 
     WHERE books.id = books_authors_link.book
       AND authors.id = books_authors_link.author
       AND authors.name LIKE '%Link%'
)
LIMIT 1
OFFSET 0
`Books.authors` hat kein `name`-Attribut weil das eben für alle Autoren steht und nicht für einen einzelnen.

An der Stelle auch eine Bemerkung zur Namensgebung: `Books` und `Authors` sollten eigentlich `Book` und `Author` heissen. Denn *ein* Exemplar von diesen Typen repräsentiert genau *ein* Buch oder *einen* Autor. Das selbe gilt für die Tabellennamen. In ER-Diagrammen werden die Entitäten auch in der Einzahl benannt. Zitat aus „Learn SQL The Hard Way”:
http://sql.learncodethehardway.org/book/learn-sql-the-hard-waych16.html#x21-6500016.2.2 hat geschrieben:Once you've identified the concepts, give them simple names that are singular. A huge misconception programmers have is thinking of a database table as a "bag of objects" and therefore naming the tables with plural names. This is like naming all of your classes with plural names. Instead, think of the tables as being like classes, and the rows as being like instances of that class or objects.
Ein `join()` zwischen `Books` und `Authors` funktioniert nicht, weil es eben keine Fremdschlüsselbeziehung zwischen *diesen beiden* gibt. Wenn sich SQLAlchemy automatisch die Beziehung über weitere Tabellen suchen würde, könnte man in den meisten DB-Schemata einen `join()` zwischen beliebigen Tabellen machen und würde für einige oder vielleicht sogar viele Fälle Monsterabfragen generiert bekommen, die man sicher nicht ungefragt verwenden möchte.
Ruffy
User
Beiträge: 34
Registriert: Dienstag 2. Oktober 2012, 11:26

Code: Alles auswählen

result = (
    session.query(Books)
        .filter(Books.authors.any(Authors.name.like('%Link%')))
        .first()
)
funktioniert 1a vielen dank... das heißt ich muss den filter zwar auf any aufrufen allerdings mit der anweisung die ich aufrufen möchte verschachtelt?

Die namen der Mapper/klassen kann ich ändern, die der DB leider nicht, da eine bereits vorhandene DB eines drittprogramms benutzt wird :(

Hatte das mit dem join – durch die doku - so verstanden, dass es eben über die relationship() anweißung funktioniert da dort ja die verbindung zwischen beiden angegeben wird ...
Antworten