CRUD Android-студия

Я работаю над мобильным приложением на Android Studio для университетского проекта. Однако я не могу изменить имя таблицы в своем CRUD, и если я это сделаю, это приведет к сбою приложения.

Вот мой код:

AdminMathsActivity.java:

package com.example.schoolapp.Admin;

import android.app.AlertDialog;
import android.database.Cursor;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.schoolapp.R;

public class AdminMathsActivity extends AppCompatActivity {

    DataBaseHelper2 peopleDB;

    Button btnAddData, btnViewData,btnUpdateData,btnDelete;
    EditText etName,etEmail,etTVShow,etID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_admin_maths);

        peopleDB = new DataBaseHelper2(this);

        etID = (EditText) findViewById(R.id.etID);
        etName = (EditText) findViewById(R.id.etNewName);
        etEmail = (EditText) findViewById(R.id.etNewEmail);
        etTVShow = (EditText) findViewById(R.id.etNewTVShow);
        btnAddData = (Button) findViewById(R.id.btnAddData);
        btnViewData = (Button) findViewById(R.id.btnViewData);
        btnUpdateData = (Button) findViewById(R.id.btnUpdateData);
        btnDelete = (Button) findViewById(R.id.btnDelete);

        AddData();
        ViewData();
        UpdateData();
        DeleteData();
    }

    public void AddData() {
        btnAddData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String name = etName.getText().toString();
                String email = etEmail.getText().toString();
                String tvShow = etTVShow.getText().toString();

                boolean insertData = peopleDB.addData(name, email, tvShow);

                if (insertData == true) {
                    Toast.makeText(AdminMathsActivity.this, "Vos données ont bien été insérez!", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(AdminMathsActivity.this, "Vos données n’ont pas été insérez, veuillez réessayer.", Toast.LENGTH_LONG).show();
                }
            }
        });
    }

    public void ViewData(){
        btnViewData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Cursor data = peopleDB.showData();

                if (data.getCount() == 0) {
                    display("Erreur", "Aucune données n'a été insérez.");
                    return;
                }
                StringBuffer buffer = new StringBuffer();
                while (data.moveToNext()) {
                    buffer.append("ID Note: " + data.getString(0) + "\n");
                    buffer.append("ID Eleve: " + data.getString(1) + "\n");
                    buffer.append("Intitule: " + data.getString(2) + "\n");
                    buffer.append("Note: " + data.getString(3) + "\n");

                    display("Toutes les données insérez :", buffer.toString());
                }
            }
        });
    }

    public void display(String title, String message){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(true);
        builder.setTitle(title);
        builder.setMessage(message);
        builder.show();
    }

    public void UpdateData(){
        btnUpdateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int temp = etID.getText().toString().length();
                if (temp > 0) {
                    boolean update = peopleDB.updateData(etID.getText().toString(), etName.getText().toString(),
                            etEmail.getText().toString(), etTVShow.getText().toString());
                    if (update == true) {
                        Toast.makeText(AdminMathsActivity.this, "Vos données ont bien été modifiés!", Toast.LENGTH_LONG).show();
                    } else {
                        Toast.makeText(AdminMathsActivity.this, "Vos données n’ont pas été modifiés, veuillez réessayer.", Toast.LENGTH_LONG).show();
                    }
                } else {
                    Toast.makeText(AdminMathsActivity.this, "Veuillez entrer un ID pour effectuer une modification!", Toast.LENGTH_LONG).show();
                }
            }
        });
    }

    public void DeleteData(){
        btnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int temp = etID.getText().toString().length();
                if (temp > 0){
                    Integer deleteRow = peopleDB.deleteData(etID.getText().toString());
                    if (deleteRow > 0){
                        Toast.makeText(AdminMathsActivity.this, "Vos données ont bien été supprimé!", Toast.LENGTH_LONG).show();
                    }else{
                        Toast.makeText(AdminMathsActivity.this, "Vos données n’ont pas été supprimé, veuillez réessayer.", Toast.LENGTH_LONG).show();
                    }
                }else{
                    Toast.makeText(AdminMathsActivity.this, "Veuillez entrer un ID pour effectuer une suppression!", Toast.LENGTH_LONG).show();
                }
            }
        });
    }

}

Код DataBaseHelper2.java:

package com.example.schoolapp.Admin;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DataBaseHelper2 extends SQLiteOpenHelper {

    public static final String DATABASE_NAME = "people.db";
    public static final String TABLE_NAME = "people_table";
    public static final String COL1 = "ID";
    public static final String COL2 = "NAME";
    public static final String COL3 = "EMAIL";
    public static final String COL4 = "TVSHOW";


    public DataBaseHelper2(Context context) {
        super(context, DATABASE_NAME, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTable = "CREATE TABLE " + TABLE_NAME + " (ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
                " NAME TEXT, EMAIL TEXT, TVSHOW TEXT)";
        db.execSQL(createTable);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP IF TABLE EXISTS " + TABLE_NAME);
        onCreate(db);
    }

    public boolean addData(String name, String email, String tvShow){
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put(COL2,name);
        contentValues.put(COL3,email);
        contentValues.put(COL4, tvShow);

        long result  = db.insert(TABLE_NAME, null, contentValues);

        if (result == -1){
            return false;
        }else{
            return true;
        }
    }

    public Cursor showData(){
        SQLiteDatabase db = this.getWritableDatabase();
        Cursor data = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
        return data;
    }

    public boolean updateData(String id, String name, String email, String tvShow){
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put(COL1,id);
        contentValues.put(COL2,name);
        contentValues.put(COL3,email);
        contentValues.put(COL4,tvShow);
        db.update(TABLE_NAME, contentValues, "ID = ?", new String[] {id});
        return true;
    }

    public Integer deleteData(String id){
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TABLE_NAME, "ID = ?", new String[] {id});
    }

}

activity_admin_maths.xml:

<?xml version = "1.0" encoding = "utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".Admin.AdminMathsActivity">

    <TextView
        android:id = "@+id/textView1"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:layout_below = "@+id/btnAddData"
        android:layout_alignParentStart = "true"
        android:layout_alignParentLeft = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:text = "ID :"
        android:textAppearance = "?android:attr/textAppearanceLarge"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toTopOf = "parent" />

    <EditText
        android:id = "@+id/etID"
        android:layout_width = "391dp"
        android:layout_height = "wrap_content"
        android:layout_alignTop = "@+id/textView1"
        android:layout_alignEnd = "@+id/btnUpdateData"
        android:layout_alignRight = "@+id/btnUpdateData"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:layout_toRightOf = "@+id/textView1"
        android:hint = "Utilisation pour modifier et supprimer"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/textView1" />

    <TextView
        android:id = "@+id/textView2"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:layout_alignParentStart = "true"
        android:layout_alignParentLeft = "true"
        android:layout_alignParentTop = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:text = "ID de l'eleve"
        android:textAppearance = "?android:attr/textAppearanceLarge"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintHorizontal_bias = "0.498"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/etID" />

    <EditText
        android:id = "@+id/etNewName"
        android:layout_width = "391dp"
        android:layout_height = "wrap_content"
        android:layout_alignBottom = "@+id/textView6"
        android:layout_alignParentEnd = "true"
        android:layout_alignParentRight = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:layout_toRightOf = "@+id/textView6"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/textView2" />


    <TextView
        android:id = "@+id/textView4"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:layout_below = "@+id/textView2"
        android:layout_alignParentStart = "true"
        android:layout_alignParentLeft = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "12dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:text = "Intitule"
        android:textAppearance = "?android:attr/textAppearanceLarge"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintHorizontal_bias = "0.498"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/etNewName" />


    <EditText
        android:id = "@+id/etNewEmail"
        android:layout_width = "391dp"
        android:layout_height = "wrap_content"
        android:layout_alignParentEnd = "true"
        android:layout_alignParentRight = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/textView4" />

    <TextView
        android:id = "@+id/textView5"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:layout_below = "@+id/textView2"
        android:layout_alignParentStart = "true"
        android:layout_alignParentLeft = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        android:text = "Note"
        android:textAppearance = "?android:attr/textAppearanceLarge"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintHorizontal_bias = "0.498"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/etNewEmail" />


    <EditText
        android:id = "@+id/etNewTVShow"
        android:layout_width = "391dp"
        android:layout_height = "wrap_content"
        android:layout_alignParentEnd = "true"
        android:layout_alignParentRight = "true"
        android:layout_marginStart = "8dp"
        android:layout_marginLeft = "8dp"
        android:layout_marginTop = "8dp"
        android:layout_marginEnd = "8dp"
        android:layout_marginRight = "8dp"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/textView5" />


    <Button
        android:id = "@+id/btnAddData"
        android:layout_width = "110dp"
        android:layout_height = "wrap_content"
        android:layout_alignParentStart = "true"
        android:layout_alignParentLeft = "true"
        android:layout_marginStart = "64dp"
        android:layout_marginLeft = "64dp"
        android:layout_marginTop = "20dp"
        android:text = "Ajouter"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/etNewTVShow" />

    <Button
        android:id = "@+id/btnViewData"
        android:layout_width = "110dp"
        android:layout_height = "wrap_content"
        android:layout_marginTop = "20dp"
        android:layout_marginEnd = "64dp"
        android:layout_marginRight = "64dp"
        android:layout_toEndOf = "@+id/btnAddData"
        android:layout_toRightOf = "@+id/btnAddData"
        android:text = "Visualiser"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/etNewTVShow" />

    <Button
        android:id = "@+id/btnUpdateData"
        android:layout_width = "110dp"
        android:layout_height = "wrap_content"
        android:layout_below = "@+id/etNewEmail"
        android:layout_marginStart = "64dp"
        android:layout_marginLeft = "64dp"
        android:layout_marginTop = "4dp"
        android:layout_toEndOf = "@+id/btnViewData"
        android:layout_toRightOf = "@+id/btnViewData"
        android:text = "Modifier"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/btnAddData" />

    <Button
        android:id = "@+id/btnDelete"
        android:layout_width = "110dp"
        android:layout_height = "wrap_content"
        android:layout_marginTop = "4dp"
        android:layout_marginEnd = "64dp"
        android:layout_marginRight = "64dp"
        android:text = "Supprimer"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintTop_toBottomOf = "@+id/btnViewData" />

</android.support.constraint.ConstraintLayout>

А вот журнал ошибок:

 2019-03-01 15:41:51.277 10663-10663/com.example.schoolapp E/SQLiteLog: (1) no such table: people_table2
    2019-03-01 15:41:51.282 10663-10663/com.example.schoolapp E/SQLiteDatabase: Error inserting EMAIL=Fonction TVSHOW=15/20 NAME=1
        android.database.sqlite.SQLiteException: no such table: people_table2 (code 1 SQLITE_ERROR): , while compiling: INSERT INTO people_table2(EMAIL,TVSHOW,NAME) VALUES (?,?,?)
            at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
            at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
            at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
            at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
            at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
            at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
            at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1562)
            at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1433)
            at com.example.schoolapp.Admin.DataBaseHelper2.addData(DataBaseHelper2.java:43)
            at com.example.schoolapp.Admin.AdminMathsActivity$1.onClick(AdminMathsActivity.java:52)
            at android.view.View.performClick(View.java:6597)
            at android.view.View.performClickInternal(View.java:6574)
            at android.view.View.access$3100(View.java:778)
            at android.view.View$PerformClick.run(View.java:25885)
            at android.os.Handler.handleCallback(Handler.java:873)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:193)
            at android.app.ActivityThread.main(ActivityThread.java:6669)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

В принципе, если я изменю имя таблицы, например, на people_table2, данные не будут вставлены в SQLite.

вы хотите изменить имя таблицы? если не позволит? он падает? Я думаю, что журнал сбоев был бы здесь очень полезен ... также «проблема», с которой вы сталкиваетесь, не имеет ничего общего с макетом Android или активностью Android.

letsCode 28.02.2019 20:08

пожалуйста, поделитесь логом ошибок

Jins Lukose 01.03.2019 12:06

@JinsLukose я обновил исходный пост журналом ошибок.

Alexandre De Coriolis 01.03.2019 12:50

@AlexandreDeCoriolis ошибка в том, что таблица не создана. пожалуйста, очистите данные и попробуйте. В противном случае проверьте, существует ли таблица в проводнике устройств в студии Android.

Jins Lukose 05.03.2019 05:23

Пожалуйста, обновите имя версии sql с 1 по 2 super(context, DATABASE_NAME, null, 1); до super(context, DATABASE_NAME, null, 2);

Android Geek 11.06.2019 05:56
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
522
1

Ответы 1

Обычно вы не изменяете имя таблицы, если только вы не изменяете структуру и, в конечном итоге, не переименовываете переименованную таблицу обратно в ее исходное имя.

Однако вы можете использовать ИЗМЕНИТЬ ТАБЛИЦУ для переименования таблицы. Однако обратите внимание, что при этом вам придется соответствующим образом изменить приложение, чтобы измененное имя таблицы использовалось повсюду.

Если вы просто хотите изменить имя таблицы, используемое при первом создании таблицы (просто измените public static final String TABLE_NAME = "people_table"; на public static final String TABLE_NAME = "people_table2";), то есть в методе onCreate, но после того, как приложение было ранее запущено с использованием более старого имени таблицы. Тогда самый простой способ: -

  1. Внесите изменения в код,
  2. Сделайте 1 из следующего
    1. удалить/очистить данные приложения или
    2. удалить приложение
    3. или в вашем случае увеличьте версию базы данных (например, измените super(context, DATABASE_NAME, null, 1); на super(context, DATABASE_NAME, null, 2);)
  3. перезапустите приложение.

    • Обратите внимание, что выполнение 2.1 или 2.2 выше приведет к потере всех данных, которые были сохранены в базе данных. Сохранить данные возможно, но это сложнее. Использование версии 2.3 приведет к созданию новой таблицы с сохранением старой.

Причина в том, что метод onCreate запускается только при создании самой базы данных.

Дополнительный

Поскольку синтаксис оператора DROP TABLE неверен. Если вы измените имя таблицы И увеличите версию, приложение вылетит с ошибкой, например: -

2019-03-01 16:25:12.956 5963-5963/aaa.adminmaths E/AndroidRuntime: FATAL EXCEPTION: main
    Process: aaa.adminmaths, PID: 5963
    android.database.sqlite.SQLiteException: near "IF": syntax error (code 1 SQLITE_ERROR): , while compiling: DROP IF TABLE EXISTS people_table
        at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
        at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
        at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
        at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
        at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1769)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1698)
        at aaa.adminmaths.DataBaseHelper2.onUpgrade(DataBaseHelper2.java:33)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:398)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:298)
        at aaa.adminmaths.DataBaseHelper2.showData(DataBaseHelper2.java:54)
        at aaa.adminmaths.AdminMathsActivity$2.onClick(AdminMathsActivity.java:65)
        at android.view.View.performClick(View.java:6597)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access$3100(View.java:778)
        at android.view.View$PerformClick.run(View.java:25885)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

Это потому, что синтаксис должен быть DROP TABLE IF EXISTS the_table_name_to_drop

Это то, что я пытался сделать. Как только я изменю (public static final String TABLE_NAME = "people_table";) на (public static final String TABLE_NAME = "people_table2";), вставка не будет работать. Но если я продолжаю (public static final String TABLE_NAME = "people_table";), он работает нормально.

Alexandre De Coriolis 01.03.2019 05:48

@AlexandreDeCoriolis Если вы попытались изменить номер версии и, таким образом, использовать при обновлении, то при обновлении не удастся, потому что у вас есть DROP IF TABLE EXISTS вместо DROP TABLE IF EXISTS. Ответ включает в себя этот вывод. Копирование кода выше, похоже, работает нормально (отметив, что я не понимаю, какой язык используется).

MikeT 01.03.2019 06:37

Другие вопросы по теме