I have a roomdatabase with 4 tables, I want to use GROUP BY and also SUM both in ONE query.
so what I had done till now:
#Query("SELECT *, SUM(increase) FROM transactions GROUP BY user_id")
fun groupBy(): LiveData<List<Transactions>>?
But SUM doesnt work(It shows the first increase by user_id.
For instanse: I have 2 users named MAX and Amir with user id 1 and 2
Amir(userId 1) submit 100$ increase and again 50$ increase.
Max(userId2) submit 80$ increase and again 10$ increase.
Please look at the result:
D/TAG: groupBy: 1 100
D/TAG: groupBy: 2 80
It supposed to show:
D/TAG: groupBy: 1 150
D/TAG: groupBy: 2 90
one point:
I have a field in database that named trans_id which stands for transactionId.
Each time I submit increase or decrease or anything else thet related to user and money my app automaticly genereates one id and it is transactionId each time it autmaticly generate transactionId but It is not as same as last one.
Where I use It?
When I want to get all user's transactions
Function where I read data from(in user list fragment):
private fun groupBy() {
mUserListViewModel.groupBy()?.observe(viewLifecycleOwner, {
mUserListViewModel.group.value = it
it.forEach {
Log.d("TAG", "groupBy: ${it.userId} // ${it.increase}")
}
})
}
My viewModel:
val group = MutableLiveData<List<Transactions>>()
fun groupBy(): LiveData<List<Transactions>>? {
return mTransactionDAO.groupBy()
}
Data class:
data class Transactions(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "trans_id")
var transId: Long = 0L,
#ColumnInfo(name = "user_id", index = true) // <<<<< best to have an index on the column, not required
var userId: Long?,
#ColumnInfo(name = "create_date")
var createDate: String?,
#ColumnInfo(name = "bank_id")
var bankId: Long?,
#ColumnInfo(name = "description")
var description: String?,
#ColumnInfo(name = "increase")
var increase: String?,
#ColumnInfo(name = "decrease")
var decrease: String?,
#ColumnInfo(name = "loan_number")
var loanNumber: String?,
#ColumnInfo(name = "total")
var total: String?,
#ColumnInfo(name = "type")
var type: String?,
#ColumnInfo(name = "loan_id")
var loanId: Long?
)
My database
If you need more code, let me know in comments section
When you use group by clause then column name is required remove * and add specific column name like this.
#Query("SELECT user_id, SUM(increase) FROM transactions GROUP BY user_id")
fun groupBy(): LiveData<List<Transactions>>?
Your code does not read the summed increase but the column increase of your table because you use * which selects all the columns.
The correct way to write the query is:
#Query("SELECT user_id, SUM(increase) AS increase FROM transactions GROUP BY user_id")
fun groupBy(): LiveData<List<userTotals>>?
so that you get 2 columns: the user_id and the the total of increase aliased as increase.
You also need to create a new class for the results of the query:
data class userTotals(val user_id: Long?, val increase: Long?)
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
Well... I don't know where to start with this... I'm doing a course where the teacher has not taught us anything about databases and now he want us to do an app with kotlin and sqlite where we make an Activity with a button "New table" where user can create a database table with a name and dynamic fields. I've been searching all day about it and I got nothing.
I thought about create a CRUD but I'm with the trouble that I don't know how to make any of this dynamically.
For now I have this SQLiteHelper that I saw it was necessary to make and I put const values to test it because I don't have any clue how to pass the values from the user view.
class AdminSQLiteOpenHelper(context: Context, name: String, factory: CursorFactory?, version: Int) : SQLiteOpenHelper(context, name, factory, version) {
companion object{
const val DATABASE_NAME = "test.db"
var TABLE_NAME = "Testing_Table"
const val COL_1 = "ID"
var COL_2 = "NAME"
var COL_3 = "SURNAME"
var COL_4 = "YEARS"
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("create table TABLE_NAME(COL_1 primary key autoincrement, COL_2 text, COL_3 text, COL_4 int)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("drop table if exists TABLE_NAME")
onCreate(db)
}
}
And I have this activity
class MainActivity2Crear : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_activity2_crear)
val buttonCrear = findViewById<Button>(R.id.buttonCrear)
buttonCrear.setOnClickListener{
val register = ContentValues()
register.put(COL_1, textView.getText().toString())
TABLE_NAME = register.toString()
val admin = AdminSQLiteOpenHelper(this, TABLE_NAME, null, 1)
val bd = admin.writableDatabase
bd.insert(TABLE_NAME, null, register)
bd.close()
textView.setText("")
Toast.makeText(this, "Table created", Toast.LENGTH_SHORT).show()
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
val button = findViewById<Button>(R.id.button)
button.setOnClickListener{
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
}
}
I just did create button for now because I don't know what I'm doing...
I know probably this question will have downvotes but I just want some help or a tutorial or something that help me to understand how to make this exercise...
You first issue that you will encounter is that you are trying to create a table using CREATE TABLE TABLE_NAME (COL_1 PRIMARY KEY AUTOINCREMENT .....
The table that will be created (attempted) will be TABLE_NAME not Testing_Table as the variable name is embedded within the String rather than being resolved and appended to the string.
There are multiple inclusions of variables in Strings.
Another error is that you have PRIMARY KEY AUTOINCREMENT autoincrement can only be used for an alias of the rowid which must be defined using specifically INTEGER PRIMARY KEY.
You don't need AUTOINCREMENT as INTEGER PRIMARY KEY will do what you want (increment the value of the ID column so the first will be 1, then likely 2, then likely 3 ....). AUTOINCREMENT is basically a constraint/rule that says the number MUST be higher (for your testing it will be (not that it really matters)).
I'd suggest the following that is based upon your code (but without the click handling) that successfully creates the table, inserts some rows (not tables) in the table and then extracts them and writes the extracted data to the log.
First the modified AdminSQLiteOpenHelper :-
class AdminSQLiteOpenHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object{
const val DATABASE_NAME = "test.db"
const val DATABASE_VERSION = 1;
var TABLE_NAME = "Testing_Table"
const val COL_1 = "ID"
var COL_2 = "NAME"
var COL_3 = "SURNAME"
var COL_4 = "YEARS"
}
val db = this.writableDatabase //<<<<<<<<<< ADDED
override fun onCreate(db: SQLiteDatabase) {
//<<<<<<<<<< NUMEROUS CHANGES >>>>>>>>>>
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME +
"("
+ COL_1 + " INTEGER PRIMARY KEY," // no need for AUTOINCREMENT you want INTEGER PRIMARY KEY not PRIMARY KEY
+ COL_2 + " TEXT,"
+ COL_3 + " TEXT,"
+ COL_4 + " INTEGER" +
")"
)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("drop table if exists " + TABLE_NAME) //<<<<<<<<<<< CHANGED
onCreate(db)
}
//<<<<<<<<<< ADDED >>>>>>>>>
fun insertRow(name: String, surname: String, years: Int): Long {
val cv = ContentValues()
cv.put(COL_2,name)
cv.put(COL_3,surname)
cv.put(COL_4,years)
return db.insert(TABLE_NAME,null,cv);
}
//<<<<<<<<<< ADDED >>>>>>>>>
fun getAllRowsFromTestingTable(): Cursor {
return db.query(TABLE_NAME,null, null,null,null,null, COL_3)
}
}
please note the changes made in comparison to yours.
Now an Activity (MainActivity) that does the work of adding and extracting data from the database:-
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get an instance of the DB Helper with full class scope
val db = AdminSQLiteOpenHelper(this)
// Add Some data
db.insertRow("Fred","Bloggs",10)
db.insertRow("Jane","Doe",25)
// Get all the data as a Cursor
val cursor = db.getAllRowsFromTestingTable()
// traverse the cursor writing data to the log
while(cursor.moveToNext()) {
Log.d("MYDATA",
"ID = " + cursor.getLong(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_1)) +
" First Name = " + cursor.getString(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_2)) +
" Surname = " + cursor.getString(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_3)) +
" Years = " + cursor.getInt(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_4))
)
}
cursor.close() //<<<<<<<<<< should ALWAYS close cursor when done
}
}
When run it produces the following in the LOG :-
2021-04-09 17:03:01.531 D/MYDATA: ID = 1 First Name = Fred Surname = Bloggs Years = 10
2021-04-09 17:03:01.531 D/MYDATA: ID = 2 First Name = Jane Surname = Doe Years = 25
Additional
Here's a revised MainActivity that has a button and will add a row to the table when it is clicked.
The rows are all the same with the exception of the years value, it being randomly generated.
After inserting the contents of the database are written to the log.
No changes to AdminSQLiteOpenHelper
:-
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get an instance of the DB Helper with full class scope
val db = AdminSQLiteOpenHelper(this)
val buttonCrear = this.findViewById<Button>(R.id.buttonCrear)
buttonCrear.setOnClickListener{
db.insertRow("Button","Click", Random.nextInt(10,9999))
logData(db)
}
// Add Some data
db.insertRow("Fred","Bloggs",10)
db.insertRow("Jane","Doe",25)
// Get all the data as a Cursor
logData(db)
}
// Function replaces the previous logging of the data
fun logData(db: AdminSQLiteOpenHelper) {
val cursor = db.getAllRowsFromTestingTable()
while(cursor.moveToNext()) {
Log.d("MYDATA",
"ID = " + cursor.getLong(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_1)) +
" First Name = " + cursor.getString(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_2)) +
" Surname = " + cursor.getString(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_3)) +
" Years = " + cursor.getInt(cursor.getColumnIndex(AdminSQLiteOpenHelper.COL_4))
)
}
cursor.close() //<<<<<<<<<< should ALWAYS close cursor when done
}
}
Here's an example of the Log after clicking the button a few times :-
Before any Clicks
2021-04-09 18:45:47.813 D/MYDATA: ID = 1 First Name = Fred Surname = Bloggs Years = 10
2021-04-09 18:45:47.813 D/MYDATA: ID = 2 First Name = Jane Surname = Doe Years = 25
After 1st Click
2021-04-09 18:45:58.567 D/MYDATA: ID = 1 First Name = Fred Surname = Bloggs Years = 10
2021-04-09 18:45:58.567 D/MYDATA: ID = 3 First Name = Button Surname = Click Years = 9910
2021-04-09 18:45:58.567 D/MYDATA: ID = 2 First Name = Jane Surname = Doe Years = 25
After 2nd Click
2021-04-09 18:45:59.675 D/MYDATA: ID = 1 First Name = Fred Surname = Bloggs Years = 10
2021-04-09 18:45:59.675 D/MYDATA: ID = 3 First Name = Button Surname = Click Years = 9910
2021-04-09 18:45:59.675 D/MYDATA: ID = 4 First Name = Button Surname = Click Years = 8263
2021-04-09 18:45:59.676 D/MYDATA: ID = 2 First Name = Jane Surname = Doe Years = 25
After 3rd Click
2021-04-09 18:46:00.611 D/MYDATA: ID = 1 First Name = Fred Surname = Bloggs Years = 10
2021-04-09 18:46:00.611 D/MYDATA: ID = 3 First Name = Button Surname = Click Years = 9910
2021-04-09 18:46:00.611 D/MYDATA: ID = 4 First Name = Button Surname = Click Years = 8263
2021-04-09 18:46:00.612 D/MYDATA: ID = 5 First Name = Button Surname = Click Years = 9625
2021-04-09 18:46:00.613 D/MYDATA: ID = 2 First Name = Jane Surname = Doe Years = 25
I am new at Kotlin and trying to catch up the language.
I have a function that returns DayofWeek as a key and an Int as a value.
The issue that I am facing is that I need to take a list of object that has another list of object inside that has an Int value which I need to save and increment for every time I see the same value.
Here is my function -
class OrdersAnalyzer {
data class Order(val orderId: Int, val creationDate: LocalDateTime, val orderLines: List<OrderLine>)
data class OrderLine(val productId: Int, val name: String, val quantity: Int, val unitPrice: BigDecimal)
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> {
val map: MutableMap<DayOfWeek, Int>? = mutableMapOf(
Pair(DayOfWeek.SUNDAY, 0),
Pair(DayOfWeek.MONDAY, 0),
Pair(DayOfWeek.TUESDAY, 0),
Pair(DayOfWeek.WEDNESDAY, 0),
Pair(DayOfWeek.THURSDAY, 0),
Pair(DayOfWeek.FRIDAY, 0),
Pair(DayOfWeek.SATURDAY, 0)
)
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
var quantity = 0
map?.put(dayOfWeek, quantity)
}
return map!!
}
}
So the issues I am facing right now are 2 -
1) How can I increment the value of each pair when it is the corrent DayOfWeek? I don't want to replace, I want to add it to the last value.
2) When returning the Map, I do not want to return the DayOfWeeks that have the value of 0. How can I do that?
Here is a modified version of the elegant answer provided by Arjan, with some test code.
Updated the answer after the comment from Alon Shlider -- now counting all order item quantities grouped by day of the week:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { sumItemQuantities(it.value) }
fun sumItemQuantities(orders: List<Order>) =
orders.flatMap { it.orderLines.map { line -> line.quantity } }.sum()
fun main() {
val orders = listOf(
Order(
1,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(6, "laptop", 28, 1200.toBigDecimal())
)
),
Order(
2,
LocalDateTime.now().minusDays(1),
listOf(
OrderLine(496, "VR headset", 6, 400.toBigDecimal())
)
)
)
println(totalDailySales(orders))
}
Output:
{FRIDAY=28, SATURDAY=6}
With this approach, Kotlin functions do the grouping and counting for you. The groupBy function creates a map from DayOfWeek to a list of orders (grouping all orders with the same day of week in a list). The mapValues function transforms that map by replacing the lists with the result of the sumItemQuantities function (for each list).
In the for loop in your code, you can retrieve the current quantity for a specific day (or use zero if it isn't set yet), increase it by the right amount and then store it. To return only the map entries with non zero values, you could filter (return totalsPerDay.filter { it.value > 0 }) or start with an empty map. This is your function with some changes:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> {
val totalsPerDay = mutableMapOf<DayOfWeek, Int>()
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
val currentQuantity = totalsPerDay[dayOfWeek] ?: 0
// This is not the best way to increment by the sum of the order
// item quantities...
val orderItemQuantities = sumItemQuantities(listOf(order))
totalsPerDay[dayOfWeek] = currentQuantity + orderItemQuantities
}
return totalsPerDay
}
Output after calling it:
println(OrdersAnalyzer().totalDailySales(orders))
{FRIDAY=28, SATURDAY=6}
Updated answer (also thanks to Freek de Bruijn), with some test code.
I think it would be something like this:
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { it.value.flatMap { it.orderLines } }
.mapValues { it.value.map { it.quantity } }
.mapValues { it.value.sum() }
groupBy creates a Map where the values are of type List<Order>, so you need to call a few steps to convert these values to Int. First we use flatMap to convert List<Order> to List<OrderLine> (map would convert to List<List<OrderLine>>). Then we use map to get the quantities out of List<OrderLine>, and finally sum() to add up all those quantities.
val orders = listOf(
Order(
2,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(5, "monitor", 10, 200.toBigDecimal()),
OrderLine(4, "keyboard", 5, 50.toBigDecimal())
)
)
)
println(totalDailySales(orders))
This results in the output:
{FRIDAY=15}
I have a class ABC like this
public class ABC{
public int Id {get;set;}
public int UserCount {get;set;}
}
Now I add following records to a list of type ABC
List<ABC> lstABC = new List<ABC>();
lstABC.Add(new ABC(){Id=1,UserCount=5});
lstABC.Add(new ABC(){Id=2,UserCount=15});
lstABC.Add(new ABC(){Id=3,UserCount=3});
lstABC.Add(new ABC(){Id=4,UserCount=20});
I've another list of type int
List<int> lstIds = new List<int>();
lstIds.Add(1);
lstIds.Add(3);
lstIds.Add(4);
Now i want to find out the minimum value of UserCount by comparing both the list where Id in lstABC should match the items presesnt in lstIds. I can get the minimum value of UserCount by using loops but is there any another way to get the value in an optimized way by avoiding loops?
You can use Enumerable.Join to link both lists:
var joined = from id in lstIds
join x in lstABC
on id equals x.Id
select x;
int minValue = joined.Min(x => x.UserCount);
This is more efficient than loops since Join uses a set to find matching items.
There's more than one way to skin a cat, this is a little bit less efficient:
int minValue = lstABC.Where(x => lstIds.Contains(x.ID)).Min(x => x.UserCount);
Try below code:
int min = lstABC.Min(entry => entry.UserCount);
var a = lstABC.Where(e => e.UserCount == min).FirstOrDefault();
var resul = lstIds.Where(e => e.Equals(a.Id)).FirstOrDefault();
var result = lstABC.Where(n =>n.Id == resul).FirstOrDefault();
I have a TABLE in SQL Database there is a columns in
TABLE ID,Subject,Body,Status,TimeDate in the 400 row data and each i have take a Id as a P_Key and Identity Specification is Yes.
Here is Id = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 etc..
I want to select greater Id from table based on saved old id like i have saved ID 12 in im getting same id like this with Linq Query below:
public static int CheckId()
{
DataClassesDataContext con = new DataClassesDataContext(Globals.con);
var q = from v in con.TABLE
where v.Id== 12 & v.Status == Active
select v.Id;
foreach (var val in q)
{
return Convert.ToInt32(val);
}
return 0;
}
the i can return a greater id then 12. and there is also one issue. if there is greater ID is Missing from DB example Id 13 is missing then i will get Id 14 in that case. please let me know how can i get id like this i want from db using linq query.
Use Min
return con.<TABLE>
.Where(v=>v.ID > 12)
.Select(v=>v.ID)
.DefaultIfEmpty()
.Min();
I made a sample for you
List<Int32> test = new List<Int32>{1,2,3,4,5,6,7,8,9,10,11,12,14,13,15,16};
var min = test.Where(x=>x>12).Min();
Gives result 13 only, even when 14 is the first bigger
In Your case
//get a table object
Table table = new Table() //if you want whole row.
table = con.Table.Where(x=>x.id>12).MIN();
Based on the code you already have:
DataClassesDataContext con = new DataClassesDataContext(Globals.con);
var q = from v in con.TABLE
where v.Id > 12 & v.Status == Active
orderby v.Id
select v.Id;
return q.Take(1); // to return the whole row
// or
return q.Take(1).Id; // to return only the Id
This would return the first row meeting the criterias (id > 12, status = active). Add error handling code as needed.
how group by in Linq with 2 Field ?
(from i in info
group i by i.OrderId into g
select new { orderId = g.Key, infos = g });
not only order by with order Id but with two field like ...
group by i.orderId And i.City
how this will do?
I believe you want something like this:
var result = from i in info
group i by new { OrderId = i.OrderId, City = i.City } into g
select new { OrderId = g.Key, Infos = g };
Creating the key as an anonymous type simply allows LINQ to use the default equality comparers for all the fields of the anonymous type, which should do the job in most situations.
As a follow-up to Noldorin's answer, you can omit the field names on the anonymous type when they match the fields you're setting them to.
Example:
var result = from i in info
group i by new { i.OrderId, i.City } into g
select new { OrderId = g.Key, Infos = g };
Another follow-up to the Noldorin's and Josh Einstein's answers...OrderID will take on the entire key...which in this case is a new object with two properties, OrderID and City. If your final result set needs the OrderID to be the OrderID, then you'll need to do the following:
var result = from i in info
group i by new { i.OrderId, i.City } into g
select new { OrderId = g.Key.OrderId, Infos = g };