尽管没有INNER JOIN的查询仍在工作,但INNER JOIN查询返回null

时间:2019-01-26 19:41:08

标签: android sqlite android-sqlite android-room

我正在从两个表查询并将数据组合到一个POJO。

我有一个带有一些条目的表和一个带有类别的表。

我正在查询以下语句:

@Transaction
@Query("SELECT $BOOKENTRIES.*, $CATEGORIES.$ID AS $PREFIX_CATEGORY$ID, $CATEGORIES.$NAME AS $PREFIX_CATEGORY$NAME, $CATEGORIES.$ICON_ID AS $PREFIX_CATEGORY$ICON_ID, $CATEGORIES.$COLOR AS $PREFIX_CATEGORY$COLOR FROM $BOOKENTRIES INNER JOIN $CATEGORIES ON $BOOKENTRIES.$CATEGORY_ID = $CATEGORIES.$ID WHERE $BOOKENTRIES.$ID = :id")
fun get(id: Long): BookEntry?

$BOOKENTRIES.$CATEGORY_ID列可以为null。 如果删除与内部join语句有关的所有内容,则查询有效。

POJO:

class BookEntry(
        @Embedded var entity: BookEntryEntity = BookEntryEntity(),
        @Embedded(prefix = PREFIX_CATEGORY) var category: Category? = null,
        contacts: List<Contact.ContactEntity>? = null
) : Parcelable {

    @Relation(
            entity = Contact.ContactEntity::class,
            parentColumn = ID,
            entityColumn = BOOKENTRY_ID
    )
    var embeddedContacts: List<Contact.ContactEntity>? = contacts

    var id: Long
        get() = entity.id
        set(value) {
            entity.id = value
        }

    var title: String
        get() = entity.title
        set(value) {
            entity.title = value
        }

    var date: Date
        get() = entity.date
        set(value) {
            entity.date = value
        }

    var value: Double
        get() = entity.value
        set(value) {
            entity.value = value
        }

    var notes: String
        get() = entity.notes
        set(value) {
            entity.notes = value
        }

    var entryType: Int
        get() = entity.entryType
        set(value) {
            entity.entryType = value
        }

    fun contacts(context: Context, onFinish: (List<Contact>?) -> Unit) = GlobalScope.launch(Dispatchers.Main) {
        onFinish(contacts(context))
    }

    suspend fun contacts(context: Context): List<Contact>? = withContext(Dispatchers.IO) {
        return@withContext context.hasPermission(Manifest.permission.READ_CONTACTS).takeIf { it }?.run {
            embeddedContacts?.map {
                Contact(it.id, it.bookEntryId, it.contactId, ContactsLoader(context).loadContactName(it.contactId), it.hasPaid)
            } ?: listOf()
        }
    }

    val isClaimOrDebt: Boolean
        get() = entryType == Type.Claim || entryType == Type.Debt

    object Type {
        const val Earning = 0
        const val Expense = 1
        const val Claim = 2
        const val Debt = 3
    }

    @Entity(tableName = Database.Table.BOOKENTRIES)
    data class BookEntryEntity(
            @PrimaryKey(autoGenerate = true) @ColumnInfo(name = ID) var id: Long = 0,
            @ColumnInfo(name = TITLE) var title: String = "",
            @ColumnInfo(name = DATE) var date: Date = Date(),
            @ColumnInfo(name = VALUE) var value: Double = 0.0,
            @ColumnInfo(name = NOTES) var notes: String = "",
            @ColumnInfo(name = ENTRYTYPE) var entryType: Int = Type.Earning,
            @ColumnInfo(name = CATEGORY_ID) var categoryId: Long? = null
    ) : Parcelable {

        constructor(parcel: Parcel) : this(
                parcel.readLong(),
                parcel.readString() as String,
                Date(parcel.readLong()),
                parcel.readDouble(),
                parcel.readString() as String,
                parcel.readInt()
        )

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            parcel.writeLong(id)
            parcel.writeString(title)
            parcel.writeLong(date.time)
            parcel.writeDouble(value)
            parcel.writeString(notes)
            parcel.writeInt(entryType)
        }

        override fun describeContents(): Int {
            return 0
        }

        companion object CREATOR : Parcelable.Creator<BookEntryEntity> {
            override fun createFromParcel(parcel: Parcel): BookEntryEntity {
                return BookEntryEntity(parcel)
            }

            override fun newArray(size: Int): Array<BookEntryEntity?> {
                return arrayOfNulls(size)
            }
        }
    }

    //region Parcelable

    constructor(parcel: Parcel) : this(
            parcel.readParcelable(BookEntryEntity::class.java.classLoader) as BookEntryEntity,
            parcel.readParcelable(Category::class.java.classLoader),
            parcel.createTypedArrayList(Contact.ContactEntity)
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeParcelable(entity, flags)
        parcel.writeParcelable(category, flags)
        parcel.writeList(embeddedContacts)
    }

    override fun describeContents(): Int {
        return 0
    }

    //endregion

    companion object {

        fun create(id: Long = 0, title: String, date: Date, value: Double, notes: String, entryType: Int, categoryId: Long? = null, contacts: List<Contact.ContactEntity>? = null): BookEntry {
            return BookEntry(BookEntryEntity(id = id, title = title, date = date, value = value, notes = notes, entryType = entryType, categoryId = categoryId), contacts = contacts)
        }

        fun createClaimEntry(title: String, date: Date, value: Double, notes: String, categoryId: Long? = null, contacts: List<Contact.ContactEntity>?): BookEntry {
            return create(title = title, date = date, value = value, notes = notes, entryType = Type.Claim, categoryId = categoryId, contacts = contacts)
        }

        fun createDebtEntry(title: String, date: Date, value: Double, notes: String, categoryId: Long? = null, contacts: List<Contact.ContactEntity>?): BookEntry {
            return create(title = title, date = date, value = value, notes = notes, entryType = Type.Debt, categoryId = categoryId, contacts = contacts)
        }

        @JvmField
        val CREATOR = object : Parcelable.Creator<BookEntry> {
            override fun createFromParcel(parcel: Parcel): BookEntry {
                return BookEntry(parcel)
            }

            override fun newArray(size: Int): Array<BookEntry?> {
                return arrayOfNulls(size)
            }
        }
    }
}

类别实体:

@Entity(tableName = Database.Table.CATEGORIES)
data class Category(
    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = ID) var id: Long = 0,
    @ColumnInfo(name = NAME) var name: String = "",
    @ColumnInfo(name = ICON_ID) var iconId: Int = 0,
    @ColumnInfo(name = COLOR) @ColorInt var color: Int = DEFAULT_COLOR
) : Parcelable {

    constructor(parcel: Parcel) : this(
        parcel.readLong(),
        parcel.readString() as String,
        parcel.readInt()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeLong(id)
        parcel.writeString(name)
        parcel.writeInt(iconId)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object {

        @JvmField
        val CREATOR = object : Parcelable.Creator<Category> {
            override fun createFromParcel(parcel: Parcel): Category {
                return Category(parcel)
            }

            override fun newArray(size: Int): Array<Category?> {
                return arrayOfNulls(size)
            }
        }
    }
}

以及数据库的架构:

"entities": [
  {
    "tableName": "bookentries",
    "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `date` INTEGER NOT NULL, `value` REAL NOT NULL, `notes` TEXT NOT NULL, `entrytype` INTEGER NOT NULL, `category_id` INTEGER)",
    "fields": [
      {
        "fieldPath": "id",
        "columnName": "id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "title",
        "columnName": "title",
        "affinity": "TEXT",
        "notNull": true
      },
      {
        "fieldPath": "date",
        "columnName": "date",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "value",
        "columnName": "value",
        "affinity": "REAL",
        "notNull": true
      },
      {
        "fieldPath": "notes",
        "columnName": "notes",
        "affinity": "TEXT",
        "notNull": true
      },
      {
        "fieldPath": "entryType",
        "columnName": "entrytype",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "categoryId",
        "columnName": "category_id",
        "affinity": "INTEGER",
        "notNull": false
      }
    ],
    "primaryKey": {
      "columnNames": [
        "id"
      ],
      "autoGenerate": true
    },
    "indices": [],
    "foreignKeys": []
  },
  {
    "tableName": "categories",
    "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `color` INTEGER NOT NULL)",
    "fields": [
      {
        "fieldPath": "id",
        "columnName": "id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "name",
        "columnName": "name",
        "affinity": "TEXT",
        "notNull": true
      },
      {
        "fieldPath": "iconId",
        "columnName": "icon_id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "color",
        "columnName": "color",
        "affinity": "INTEGER",
        "notNull": true
      }
    ],
    "primaryKey": {
      "columnNames": [
        "id"
      ],
      "autoGenerate": true
    },
    "indices": [],
    "foreignKeys": []
  },
  {
    "tableName": "bookentrycontacts",
    "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookentry_id` INTEGER NOT NULL, `contact_id` INTEGER NOT NULL, `has_paid` INTEGER NOT NULL, FOREIGN KEY(`bookentry_id`) REFERENCES `bookentries`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
    "fields": [
      {
        "fieldPath": "id",
        "columnName": "id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "bookEntryId",
        "columnName": "bookentry_id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "contactId",
        "columnName": "contact_id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "hasPaid",
        "columnName": "has_paid",
        "affinity": "INTEGER",
        "notNull": true
      }
    ],
    "primaryKey": {
      "columnNames": [
        "id"
      ],
      "autoGenerate": true
    },
    "indices": [
      {
        "name": "index_bookentrycontacts_bookentry_id",
        "unique": false,
        "columnNames": [
          "bookentry_id"
        ],
        "createSql": "CREATE  INDEX `index_bookentrycontacts_bookentry_id` ON `${TABLE_NAME}` (`bookentry_id`)"
      }
    ],
    "foreignKeys": [
      {
        "table": "bookentries",
        "onDelete": "CASCADE",
        "onUpdate": "NO ACTION",
        "columns": [
          "bookentry_id"
        ],
        "referencedColumns": [
          "id"
        ]
      }
    ]
  },
  {
    "tableName": "reminders",
    "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bookentry_id` INTEGER NOT NULL, `worker_uuid` TEXT NOT NULL, `date` INTEGER NOT NULL, FOREIGN KEY(`bookentry_id`) REFERENCES `bookentries`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
    "fields": [
      {
        "fieldPath": "id",
        "columnName": "id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "bookEntryId",
        "columnName": "bookentry_id",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "workerUUID",
        "columnName": "worker_uuid",
        "affinity": "TEXT",
        "notNull": true
      },
      {
        "fieldPath": "fireDate",
        "columnName": "date",
        "affinity": "INTEGER",
        "notNull": true
      }
    ],
    "primaryKey": {
      "columnNames": [
        "id"
      ],
      "autoGenerate": true
    },
    "indices": [
      {
        "name": "index_reminders_bookentry_id",
        "unique": true,
        "columnNames": [
          "bookentry_id"
        ],
        "createSql": "CREATE UNIQUE INDEX `index_reminders_bookentry_id` ON `${TABLE_NAME}` (`bookentry_id`)"
      }
    ],
    "foreignKeys": [
      {
        "table": "bookentries",
        "onDelete": "CASCADE",
        "onUpdate": "NO ACTION",
        "columns": [
          "bookentry_id"
        ],
        "referencedColumns": [
          "id"
        ]
      }
    ]
  }
]

这是什么问题?

3 个答案:

答案 0 :(得分:0)

因此,我查看了您提到的以下查询,它对我来说看起来不错:

SELECT $BOOKENTRIES.*, $CATEGORIES.$ID AS $PREFIX_CATEGORY$ID, 
$CATEGORIES.$NAME AS $PREFIX_CATEGORY$NAME, $CATEGORIES.$ICON_ID AS 
$PREFIX_CATEGORY$ICON_ID, $CATEGORIES.$COLOR AS $PREFIX_CATEGORY$COLOR FROM 
$BOOKENTRIES INNER JOIN $CATEGORIES ON $BOOKENTRIES.$CATEGORY_ID = 
$CATEGORIES.$ID WHERE $BOOKENTRIES.$ID = :id

能否请您分享表,以便我为您提供确切的答案。好吧,现在您能在没有过滤器的情况下尝试再次尝试吗?

SELECT $BOOKENTRIES.*, $CATEGORIES.$ID AS $PREFIX_CATEGORY$ID, 
$CATEGORIES.$NAME AS $PREFIX_CATEGORY$NAME, $CATEGORIES.$ICON_ID AS 
$PREFIX_CATEGORY$ICON_ID, $CATEGORIES.$COLOR AS $PREFIX_CATEGORY$COLOR FROM 
$BOOKENTRIES INNER JOIN $CATEGORIES ON $BOOKENTRIES.$CATEGORY_ID = 
$CATEGORIES.$ID

答案 1 :(得分:0)

我将查询更改为以下内容:

@Transaction
@Query("SELECT $BOOKENTRIES.*, $CATEGORIES.$ID AS $PREFIX_CATEGORY$ID, $CATEGORIES.$NAME AS $PREFIX_CATEGORY$NAME, $CATEGORIES.$ICON_ID AS $PREFIX_CATEGORY$ICON_ID, $CATEGORIES.$COLOR AS $PREFIX_CATEGORY$COLOR FROM $BOOKENTRIES LEFT JOIN $CATEGORIES ON $BOOKENTRIES.$CATEGORY_ID = $CATEGORIES.$ID WHERE $BOOKENTRIES.$ID = :id")
fun get(id: Long): BookEntry?

由于我要从表“ b”加载数据作为表“ a”的附加信息,因此必须使用LEFT JOIN。 (请参阅https://stackoverflow.com/a/6188334/5994190

答案 2 :(得分:0)

问题似乎是您的Categories表为空。然后,使用内部联接不会获得任何结果。您想要的是外部联接。在这种情况下,左联接。将INNER替换为LEFT,然后看看会得到什么。