قليلا من SQL Alchemy

تمت كتابة الكثير من المقالات حول مكتبة SQLAlchemy الشهيرة للعمل مع أنظمة DBMS مختلفة من Python. أوجه انتباهكم إلى نظرة عامة ومقارنة الاستعلامات باستخدام نهج ORM و SQL. سيكون هذا البرنامج التعليمي ذا أهمية للمطورين المبتدئين في المقام الأول ، حيث يتيح لك الانغماس بسرعة في الإنشاء والعمل باستخدام SQLAlchemy ، نظرًا لأنه من الصعب قراءة الوثائق من مطور SQLAlchemy ، في رأيي المتواضع.



صورة



قليلا عن نفسي: أنا أيضا مطور مبتدئ ، وأنا آخذ الدورة التدريبية "مطور Python". لم يتم تجميع هذه المواد نتيجة للاستشعار عن بعد ، ولكن بترتيب التطوير الذاتي. قد يكون الكود الخاص بي ساذجًا تمامًا ، لذا لا تتردد في ترك تعليقاتك في التعليقات. إذا لم أخافك بعد ، من فضلك ، تحت القطع :)



سنقوم بتحليل مثال عملي لتطبيع جدول مسطح يحتوي على بيانات مكررة إلى حالة 3NF ( النموذج العادي الثالث ).



من هذا الجدول:



جدول البيانات
صورة



دعونا نجعل قاعدة البيانات هذه:



مخطط اتصال قاعدة البيانات
صورة



للصبر: الكود الجاهز للتشغيل موجود في هذا المستودع . مخطط قاعدة البيانات التفاعلي هنا . توجد ورقة غش لكتابة استعلامات ORM في نهاية المقالة.



دعنا نتفق على أنه في نص المقال سنستخدم كلمة "جدول" بدلاً من "علاقة" ، وكلمة "حقل" بدلاً من "سمة". عند التعيين ، نحتاج إلى وضع جدول بملفات الموسيقى في قاعدة البيانات ، مع التخلص من تكرار البيانات. يحتوي الجدول الأصلي (تنسيق CSV) على الحقول التالية (المسار ، النوع ، الموسيقي ، الألبوم ، الطول ، Album_year ، Collection ، collection_year). الروابط بينهما كالتالي:



  • يمكن لكل موسيقي أن يغني في عدة أنواع ، كما يمكن للعديد من الموسيقيين أن يؤدوا في نوع واحد (العديد من العلاقات)
  • يمكن لموسيقي واحد أو أكثر المشاركة في إنشاء ألبوم (علاقة أطراف بأطراف)
  • المسار ينتمي إلى ألبوم واحد فقط (علاقة واحد بأطراف)
  • يمكن تضمين المسارات في عدة مجموعات (علاقة أطراف بأطراف)
  • قد لا يتم تضمين المسار في أي مجموعة.


للتبسيط ، لنفترض أن أسماء الأنواع وأسماء الفنانين وأسماء الألبومات وأسماء المجموعات لم تتكرر. يمكن تكرار أسماء المسار. لقد قمنا بتصميم 8 جداول في قاعدة البيانات:



  • الأنواع (الأنواع)
  • genres_musicians (جدول مرحلي)
  • الموسيقيين (الموسيقيين)
  • albums_musicians (جدول متوسط)
  • ألبومات (ألبومات)
  • المسارات
  • collections_tracks (جدول مرحلي)
  • مجموعات (مجموعات)


* هذا المخطط اختبار ، مأخوذ من أحد DZ ، له بعض العيوب - على سبيل المثال ، لا يوجد اتصال بين المقطوعات والموسيقي ، وكذلك المسار مع النوع لكن هذا ليس ضروريًا للتعلم ، وسوف نتجاهل هذا العيب.



بالنسبة للاختبار ، قمت بإنشاء قاعدتي بيانات على Postgres المحلية: "TestSQL" و "TestORM" ، والوصول إليها: اختبار تسجيل الدخول وكلمة المرور. دعنا نكتب أخيرا بعض التعليمات البرمجية!



إنشاء اتصالات وجداول



إنشاء اتصالات بقاعدة البيانات
* read_data clear_db .

DSN_SQL = 'postgresql://test:test@localhost:5432/TestSQL'
    DSN_ORM = 'postgresql://test:test@localhost:5432/TestORM'
    #    CSV     .
    DATA = read_data('data/demo-data.csv')

    print('Connecting to DB\'s...')
    #     ,       .
    engine_orm = sa.create_engine(DSN_ORM)
    Session_ORM = sessionmaker(bind=engine_orm)
    session_orm = Session_ORM()

    engine_sql = sa.create_engine(DSN_SQL)
    Session_SQL = sessionmaker(bind=engine_sql)
    session_sql = Session_SQL()

    print('Clearing the bases...')
    #        .    .
    clear_db(sa, engine_sql)
    clear_db(sa, engine_orm)
      
      







نقوم بإنشاء الجداول بالطريقة الكلاسيكية باستخدام SQL
* read_query . .



print('\nPreparing data for SQL job...')
    print('Creating empty tables...')
    session_sql.execute(read_query('queries/create-tables.sql'))
    session_sql.commit()

    print('\nAdding musicians...')
    query = read_query('queries/insert-musicians.sql')
    res = session_sql.execute(query.format(','.join({f"('{x['musician']}')" for x in DATA})))
    print(f'Inserted {res.rowcount} musicians.')

    print('\nAdding genres...')
    query = read_query('queries/insert-genres.sql')
    res = session_sql.execute(query.format(','.join({f"('{x['genre']}')" for x in DATA})))
    print(f'Inserted {res.rowcount} genres.')

    print('\nLinking musicians with genres...')
    # assume that musician + genre has to be unique
    genres_musicians = {x['musician'] + x['genre']: [x['musician'], x['genre']] for x in DATA}
    query = read_query('queries/insert-genre-musician.sql')
    # this query can't be run in batch, so execute one by one
    res = 0
    for key, value in genres_musicians.items():
        res += session_sql.execute(query.format(value[1], value[0])).rowcount
    print(f'Inserted {res} connections.')

    print('\nAdding albums...')
    # assume that albums has to be unique
    albums = {x['album']: x['album_year'] for x in DATA}
    query = read_query('queries/insert-albums.sql')
    res = session_sql.execute(query.format(','.join({f"('{x}', '{y}')" for x, y in albums.items()})))
    print(f'Inserted {res.rowcount} albums.')

    print('\nLinking musicians with albums...')
    # assume that musicians + album has to be unique
    albums_musicians = {x['musician'] + x['album']: [x['musician'], x['album']] for x in DATA}
    query = read_query('queries/insert-album-musician.sql')
    # this query can't be run in batch, so execute one by one
    res = 0
    for key, values in albums_musicians.items():
        res += session_sql.execute(query.format(values[1], values[0])).rowcount
    print(f'Inserted {res} connections.')

    print('\nAdding tracks...')
    query = read_query('queries/insert-track.sql')
    # this query can't be run in batch, so execute one by one
    res = 0
    for item in DATA:
        res += session_sql.execute(query.format(item['track'], item['length'], item['album'])).rowcount
    print(f'Inserted {res} tracks.')

    print('\nAdding collections...')
    query = read_query('queries/insert-collections.sql')
    res = session_sql.execute(query.format(','.join({f"('{x['collection']}', {x['collection_year']})" for x in DATA if x['collection'] and x['collection_year']})))
    print(f'Inserted {res.rowcount} collections.')

    print('\nLinking collections with tracks...')
    query = read_query('queries/insert-collection-track.sql')
    # this query can't be run in batch, so execute one by one
    res = 0
    for item in DATA:
        res += session_sql.execute(query.format(item['collection'], item['track'])).rowcount
    print(f'Inserted {res} connections.')
    session_sql.commit()
      
      







في الواقع ، نقوم بإنشاء أدلة في حزم (أنواع ، موسيقيين ، ألبومات ، مجموعات) ، ثم في حلقة نربط بقية البيانات وننشئ جداول وسيطة يدويًا. قم بتشغيل التعليمات البرمجية وتأكد من إنشاء قاعدة البيانات. الشيء الرئيسي هو عدم نسيان استدعاء الالتزام () في الجلسة.



نحاول الآن أن نفعل الشيء نفسه ، ولكن باستخدام نهج ORM. من أجل العمل مع ORM ، نحتاج إلى وصف فئات البيانات. لهذا ، سنقوم بإنشاء 8 فصول (واحد لكل جدول).



قائمة فئات DB
.



Base = declarative_base()

class Genre(Base):
    __tablename__ = 'genres'
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(20), unique=True)
    #       Musician    genres_musicians
    musicians = relationship("Musician", secondary='genres_musicians')


class Musician(Base):
    __tablename__ = 'musicians'
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(50), unique=True)
    #       Genre    genres_musicians
    genres = relationship("Genre", secondary='genres_musicians')
    #       Album    albums_musicians
    albums = relationship("Album", secondary='albums_musicians')


class GenreMusician(Base):
    __tablename__ = 'genres_musicians'
    #     ,    
    __table_args__ = (PrimaryKeyConstraint('genre_id', 'musician_id'),)
    #           
    genre_id = sa.Column(sa.Integer, sa.ForeignKey('genres.id'))
    musician_id = sa.Column(sa.Integer, sa.ForeignKey('musicians.id'))


class Album(Base):
    __tablename__ = 'albums'
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(50), unique=True)
    year = sa.Column(sa.Integer)
    #       Musician    albums_musicians
    musicians = relationship("Musician", secondary='albums_musicians')


class AlbumMusician(Base):
    __tablename__ = 'albums_musicians'
    #     ,    
    __table_args__ = (PrimaryKeyConstraint('album_id', 'musician_id'),)
    #           
    album_id = sa.Column(sa.Integer, sa.ForeignKey('albums.id'))
    musician_id = sa.Column(sa.Integer, sa.ForeignKey('musicians.id'))


class Track(Base):
    __tablename__ = 'tracks'
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(100))
    length = sa.Column(sa.Integer)
    #    album_id     ,      
    album_id = sa.Column(sa.Integer, ForeignKey('albums.id'))
    #       Collection    collections_tracks
    collections = relationship("Collection", secondary='collections_tracks')


class Collection(Base):
    __tablename__ = 'collections'
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.String(50))
    year = sa.Column(sa.Integer)
    #       Track    collections_tracks
    tracks = relationship("Track", secondary='collections_tracks')


class CollectionTrack(Base):
    __tablename__ = 'collections_tracks'
    #     ,    
    __table_args__ = (PrimaryKeyConstraint('collection_id', 'track_id'),)
    #           
    collection_id = sa.Column(sa.Integer, sa.ForeignKey('collections.id'))
    track_id = sa.Column(sa.Integer, sa.ForeignKey('tracks.id'))
      
      







نحتاج فقط إلى إنشاء قاعدة فئة أساسية للأسلوب التعريفي لوصف الجداول والوراثة منها. يكمن سحر علاقات الجدول في الاستخدام الصحيح للعلاقة و ForeignKey. يشير الرمز في هذه الحالة إلى إنشاء أي علاقة. الشيء الرئيسي هو عدم نسيان تسجيل العلاقة على كلا جانبي علاقة أطراف بأطراف.



يتم إنشاء الجداول مباشرة باستخدام نهج ORM عن طريق استدعاء:



Base.metadata.create_all(engine_orm)
      
      





وهنا يأتي دور السحر ، فكل الفئات المُعلنة في الكود من خلال الوراثة من Base تصبح جداول. على الفور لم أجد كيف يمكنني تحديد حالات أي الفئات يجب إنشاؤها الآن وأيها يجب تأجيل إنشائها لاحقًا (على سبيل المثال ، في قاعدة بيانات أخرى). بالتأكيد توجد مثل هذه الطريقة ، ولكن في الكود الخاص بنا ، يتم إنشاء مثيل لجميع الفئات الموروثة من Base دفعة واحدة ، ضع ذلك في الاعتبار.




تبدو جداول الملء باستخدام نهج ORM كما يلي:



تعبئة الجداول بالبيانات عبر ORM
    print('\nPreparing data for ORM job...')
    for item in DATA:
        #  
        genre = session_orm.query(Genre).filter_by(name=item['genre']).scalar()
        if not genre:
            genre = Genre(name=item['genre'])
        session_orm.add(genre)

        #  
        musician = session_orm.query(Musician).filter_by(name=item['musician']).scalar()
        if not musician:
            musician = Musician(name=item['musician'])
        musician.genres.append(genre)
        session_orm.add(musician)

        #  
        album = session_orm.query(Album).filter_by(name=item['album']).scalar()
        if not album:
            album = Album(name=item['album'], year=item['album_year'])
        album.musicians.append(musician)
        session_orm.add(album)

        #  
        #            ,       
        #   
        track = session_orm.query(Track).join(Album).filter(and_(Track.name == item['track'],
                                                                 Album.name == item['album'])).scalar()
        if not track:
            track = Track(name=item['track'], length=item['length'])
        track.album_id = album.id
        session_orm.add(track)

        #  ,           
        if item['collection']:
            collection = session_orm.query(Collection).filter_by(name=item['collection']).scalar()
            if not collection:
                collection = Collection(name=item['collection'], year=item['collection_year'])
            collection.tracks.append(track)
            session_orm.add(collection)
        session_orm.commit()
      
      







يجب عليك ملء كل كتاب مرجعي (الأنواع والموسيقيين والألبومات والمجموعات) حسب القطعة. في حالة استعلامات SQL ، كان من الممكن إنشاء إضافات البيانات الدفعية. لكن الجداول الوسيطة لا تحتاج إلى إنشاء صريح ؛ الآليات الداخلية لـ SQLAlchemy مسؤولة عن ذلك.



استعلامات قاعدة البيانات



عند التعيين ، نحتاج إلى كتابة 15 استعلامًا باستخدام تقنيات SQL و ORM. فيما يلي قائمة بالأسئلة المطروحة بترتيب الصعوبة المتزايدة:



  1. عنوان وسنة إصدار الألبومات التي تم إصدارها في 2018 ؛
  2. عنوان ومدة أطول مسار ؛
  3. اسم المسارات التي لا تقل مدتها عن 3.5 دقيقة ؛
  4. عناوين المجموعات المنشورة في الفترة من 2018 إلى 2020 ضمناً ؛
  5. فناني الأداء الذين يتكون اسمهم من كلمة واحدة ؛
  6. اسم المسارات التي تحتوي على كلمة "أنا".
  7. عدد فناني الأداء في كل نوع ؛
  8. عدد المسارات المضمنة في ألبومات 2019-2020 ؛
  9. متوسط ​​طول المسار لكل ألبوم ؛
  10. جميع الفنانين الذين لم يصدروا ألبومات في 2020 ؛
  11. عناوين المجموعات التي يوجد فيها فنان معين ؛
  12. اسم الألبومات التي يوجد بها فنانون من أكثر من نوع واحد ؛
  13. اسم المسارات التي لم يتم تضمينها في المجموعات ؛
  14. الفنان (الفنانون) الذي كتب أقصر مسار (نظريًا يمكن أن يكون هناك العديد من هذه المسارات) ؛
  15. اسم الألبومات التي تحتوي على أصغر عدد من المسارات.


كما ترى ، فإن الأسئلة أعلاه تتضمن اختيارًا بسيطًا وتسلسلًا للجداول ، بالإضافة إلى استخدام وظائف مجمعة.



فيما يلي حلول لكل من الاستفسارات الـ 15 في خيارين (باستخدام SQL و ORM). في الكود ، تأتي الطلبات في أزواج لإظهار أن النتائج متطابقة في إخراج وحدة التحكم.



الطلبات ووصفها المختصر
    print('\n1. All albums from 2018:')
    query = read_query('queries/select-album-by-year.sql').format(2018)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Album).filter_by(year=2018):
        print(item.name)

    print('\n2. Longest track:')
    query = read_query('queries/select-longest-track.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Track).order_by(Track.length.desc()).slice(0, 1):
        print(f'{item.name}, {item.length}')

    print('\n3. Tracks with length not less 3.5min:')
    query = read_query('queries/select-tracks-over-length.sql').format(310)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Track).filter(310 <= Track.length).order_by(Track.length.desc()):
        print(f'{item.name}, {item.length}')

    print('\n4. Collections between 2018 and 2020 years (inclusive):')
    query = read_query('queries/select-collections-by-year.sql').format(2018, 2020)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Collection).filter(2018 <= Collection.year,
                                                     Collection.year <= 2020):
        print(item.name)

    print('\n5. Musicians with name that contains not more 1 word:')
    query = read_query('queries/select-musicians-by-name.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Musician).filter(Musician.name.notlike('%% %%')):
        print(item.name)

    print('\n6. Tracks that contains word "me" in name:')
    query = read_query('queries/select-tracks-by-name.sql').format('me')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Track).filter(Track.name.like('%%me%%')):
        print(item.name)

    print('Ok, let\'s start serious work')

    print('\n7. How many musicians plays in each genres:')
    query = read_query('queries/count-musicians-by-genres.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Genre).join(Genre.musicians).order_by(func.count(Musician.id).desc()).group_by(
            Genre.id):
        print(f'{item.name}, {len(item.musicians)}')

    print('\n8. How many tracks in all albums 2019-2020:')
    query = read_query('queries/count-tracks-in-albums-by-year.sql').format(2019, 2020)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Track, Album).join(Album).filter(2019 <= Album.year, Album.year <= 2020):
        print(f'{item[0].name}, {item[1].year}')

    print('\n9. Average track length in each album:')
    query = read_query('queries/count-average-tracks-by-album.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Album, func.avg(Track.length)).join(Track).order_by(func.avg(Track.length)).group_by(
            Album.id):
        print(f'{item[0].name}, {item[1]}')

    print('\n10. All musicians that have no albums in 2020:')
    query = read_query('queries/select-musicians-by-album-year.sql').format(2020)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    subquery = session_orm.query(distinct(Musician.name)).join(Musician.albums).filter(Album.year == 2020)
    for item in session_orm.query(distinct(Musician.name)).filter(~Musician.name.in_(subquery)).order_by(
            Musician.name.asc()):
        print(f'{item}')

    print('\n11. All collections with musician Steve:')
    query = read_query('queries/select-collection-by-musician.sql').format('Steve')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Collection).join(Collection.tracks).join(Album).join(Album.musicians).filter(
            Musician.name == 'Steve').order_by(Collection.name):
        print(f'{item.name}')

    print('\n12. Albums with musicians that play in more than 1 genre:')
    query = read_query('queries/select-albums-by-genres.sql').format(1)
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    for item in session_orm.query(Album).join(Album.musicians).join(Musician.genres).having(func.count(distinct(
            Genre.name)) > 1).group_by(Album.id).order_by(Album.name):
        print(f'{item.name}')

    print('\n13. Tracks that not included in any collections:')
    query = read_query('queries/select-absence-tracks-in-collections.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    # Important! Despite the warning, following expression does not work: "Collection.id is None"
    for item in session_orm.query(Track).outerjoin(Track.collections).filter(Collection.id == None):
        print(f'{item.name}')

    print('\n14. Musicians with shortest track length:')
    query = read_query('queries/select-musicians-min-track-length.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    subquery = session_orm.query(func.min(Track.length))
    for item in session_orm.query(Musician, Track.length).join(Musician.albums).join(Track).group_by(
            Musician.id, Track.length).having(Track.length == subquery).order_by(Musician.name):
        print(f'{item[0].name}, {item[1]}')

    print('\n15. Albums with minimum number of tracks:')
    query = read_query('queries/select-albums-with-minimum-tracks.sql')
    print(f'############################\n{query}\n############################')
    print('----SQL way---')
    res = session_sql.execute(query)
    print(*res, sep='\n')
    print('----ORM way----')
    subquery1 = session_orm.query(func.count(Track.id)).group_by(Track.album_id).order_by(func.count(Track.id)).limit(1)
    subquery2 = session_orm.query(Track.album_id).group_by(Track.album_id).having(func.count(Track.id) == subquery1)
    for item in session_orm.query(Album).join(Track).filter(Track.album_id.in_(subquery2)).order_by(Album.name):
        print(f'{item.name}')
      
      







بالنسبة لأولئك الذين لا يرغبون في الغوص في قراءة الكود ، سأحاول إظهار كيف يبدو SQL "الخام" وبديله في تعبير ORM ، دعنا نذهب!



ورقة الغش لمطابقة استعلامات SQL وتعبيرات ORM



1- عنوان وإصدار الألبومات سنة 2018:

SQL



select name
from albums
where year=2018
      
      





ORM



session_orm.query(Album).filter_by(year=2018)
      
      





2- عنوان ومدة المسار الأطول:

SQL



select name, length
from tracks
order by length DESC
limit 1
      
      





ORM



session_orm.query(Track).order_by(Track.length.desc()).slice(0, 1)
      
      





3. اسم المسارات التي لا تقل مدتها عن 3.5 دقيقة:

SQL



select name, length
from tracks
where length >= 310
order by length DESC
      
      





ORM



session_orm.query(Track).filter(310 <= Track.length).order_by(Track.length.desc())
      
      





4- أسماء المجموعات المنشورة في الفترة من 2018 إلى 2020 متضمنة:

SQL



select name
from collections
where (year >= 2018) and (year <= 2020)
      
      





ORM



session_orm.query(Collection).filter(2018 <= Collection.year, Collection.year <= 2020)
      
      





* لاحظ أنه فيما يلي ، يتم تحديد التصفية باستخدام عامل التصفية ، وليس باستخدام filter_by.



5. المنفذون الذين يتكون اسمهم من كلمة واحدة:

SQL



select name
from musicians
where not name like '%% %%'
      
      





ORM



session_orm.query(Musician).filter(Musician.name.notlike('%% %%'))
      
      





6. اسم المسارات التي تحتوي على كلمة "me":

SQL



select name
from tracks
where name like '%%me%%'
      
      





ORM



session_orm.query(Track).filter(Track.name.like('%%me%%'))
      
      





7- عدد المؤدين في كل نوع:

SQL



select g.name, count(m.name)
from genres as g
left join genres_musicians as gm on g.id = gm.genre_id
left join musicians as m on gm.musician_id = m.id
group by g.name
order by count(m.id) DESC
      
      





ORM



session_orm.query(Genre).join(Genre.musicians).order_by(func.count(Musician.id).desc()).group_by(Genre.id)
      
      





8- عدد الأغاني المضمنة في ألبومات 2019-2020:

SQL



select t.name, a.year
from albums as a
left join tracks as t on t.album_id = a.id
where (a.year >= 2019) and (a.year <= 2020)
      
      





ORM



session_orm.query(Track, Album).join(Album).filter(2019 <= Album.year, Album.year <= 2020)
      
      





9- متوسط ​​طول المسار لكل ألبوم:

SQL



select a.name, AVG(t.length)
from albums as a
left join tracks as t on t.album_id = a.id
group by a.name
order by AVG(t.length)
      
      





ORM



session_orm.query(Album, func.avg(Track.length)).join(Track).order_by(func.avg(Track.length)).group_by(Album.id)
      
      





10- جميع الفنانين الذين لم يصدروا ألبومات في 2020:

SQL



select distinct m.name
from musicians as m
where m.name not in (
    select distinct m.name
    from musicians as m
    left join albums_musicians as am on m.id = am.musician_id
    left join albums as a on a.id = am.album_id
    where a.year = 2020
)
order by m.name
      
      





ORM



subquery = session_orm.query(distinct(Musician.name)).join(Musician.albums).filter(Album.year == 2020)
session_orm.query(distinct(Musician.name)).filter(~Musician.name.in_(subquery)).order_by(Musician.name.asc())
      
      





11- أسماء المصنفات التي يتواجد فيها فنان معين (ستيف):

SQL



select distinct c.name
from collections as c
left join collections_tracks as ct on c.id = ct.collection_id
left join tracks as t on t.id = ct.track_id
left join albums as a on a.id = t.album_id
left join albums_musicians as am on am.album_id = a.id
left join musicians as m on m.id = am.musician_id
where m.name like '%%Steve%%'
order by c.name
      
      





ORM



session_orm.query(Collection).join(Collection.tracks).join(Album).join(Album.musicians).filter(Musician.name == 'Steve').order_by(Collection.name)
      
      





12. اسم الألبومات التي يوجد بها فنانون من أكثر من نوع:

SQL



select a.name
from albums as a
left join albums_musicians as am on a.id = am.album_id
left join musicians as m on m.id = am.musician_id
left join genres_musicians as gm on m.id = gm.musician_id
left join genres as g on g.id = gm.genre_id
group by a.name
having count(distinct g.name) > 1
order by a.name
      
      





ORM



session_orm.query(Album).join(Album.musicians).join(Musician.genres).having(func.count(distinct(Genre.name)) > 1).group_by(Album.id).order_by(Album.name)
      
      





13.اسم المسارات التي لم يتم تضمينها في المجموعات:

SQL



select t.name
from tracks as t
left join collections_tracks as ct on t.id = ct.track_id
where ct.track_id is null
      
      





ORM



session_orm.query(Track).outerjoin(Track.collections).filter(Collection.id == None)
      
      





* لاحظ أنه على الرغم من التحذير في PyCharm ، يجب تكوين شرط التصفية بهذه الطريقة ، إذا قمت بكتابته على النحو الذي اقترحه IDE ("Collection.id is None") ، فلن يعمل.



14. الفنان (الفنانون) الذين كتبوا أقصر مسار من حيث الطول (نظريًا يمكن أن يكون هناك العديد من المسارات):

SQL



select m.name, t.length
from tracks as t
left join albums as a on a.id = t.album_id
left join albums_musicians as am on am.album_id = a.id
left join musicians as m on m.id = am.musician_id
group by m.name, t.length
having t.length = (select min(length) from tracks)
order by m.name
      
      





ORM



subquery = session_orm.query(func.min(Track.length))
session_orm.query(Musician, Track.length).join(Musician.albums).join(Track).group_by(Musician.id, Track.length).having(Track.length == subquery).order_by(Musician.name)
      
      





15. اسم الألبومات التي تحتوي على أقل عدد من المسارات:

SQL



select distinct a.name
from albums as a
left join tracks as t on t.album_id = a.id
where t.album_id in (
    select album_id
    from tracks
    group by album_id
    having count(id) = (
        select count(id)
        from tracks
        group by album_id
        order by count
        limit 1
    )
)
order by a.name
      
      





ORM



subquery1 = session_orm.query(func.count(Track.id)).group_by(Track.album_id).order_by(func.count(Track.id)).limit(1)
subquery2 = session_orm.query(Track.album_id).group_by(Track.album_id).having(func.count(Track.id) == subquery1)
session_orm.query(Album).join(Track).filter(Track.album_id.in_(subquery2)).order_by(Album.name)
      
      





كما ترى ، تتضمن الأسئلة أعلاه كلاً من التحديد البسيط والجداول المنضمة ، بالإضافة إلى استخدام الدالات والاستعلامات الفرعية. كل هذا يمكن القيام به باستخدام SQLAlchemy سواء في وضع SQL أو في وضع ORM. تتيح لك مجموعة العوامل والطرق المتنوعة تنفيذ استعلام عن أي تعقيد.



آمل أن تساعد هذه المادة المبتدئين في كتابة الاستفسارات بسرعة وكفاءة.



All Articles