Get complex response from Android Room with sqlite query - android-sqlite

Based on response to my question here
I have simple table:
id, name, city
and I created a class for it:
#Entity(tableName = "table2")
data class Table2 #JvmOverloads constructor(
#PrimaryKey #ColumnInfo(name = "id") val id: Int? = 0,
#ColumnInfo(name = "name") val name: String? = "",
#ColumnInfo(name = "city") val city: String? = ""
)
I entered next rows:
1 John London
2 Mary Paris
3 John Paris
4 Samy London
I want to get such a result:
London Paris
John 1 1
Mary 0 1
Samy 1 0
Total 2 2
It's not important to get Total row together with John, Mary and Samy lines, now I just want to get a simple response without complex query.
Since I want to get response list of classes containing 3 fields:
city, Int, Int
I created a class for it:
data class CityName constructor(
val name: String? = "",
val countLondon: Int? = 0,
val countParis: Int? = 0
)
Next I tried to create a SQLite query to get a result from Android Room.
#Query("SELECT name,
COUNT(CASE WHEN city = :london THEN 1 ELSE 0 END) 'London',
COUNT(CASE WHEN city = :paris THEN 1 ELSE 0 END) 'Paris'
FROM table2 GROUP BY name")
fun getPivot_CityNameList(london: String, paris: String): List<CityName>
I excluded/included from the query 'London' and 'Paris', I swapped some words etc.
but I always get a result like this:
John, countLondon: null, countParis: null
Mary, countLondon: null, countParis: null
Samy, countLondon: null, countParis: null
It's easy to get subset from a table, like city and id or to get only a count of some field but
I have no idea how to combine subset (name) with two counts in one response.
What is wrong: my query or CityName class response?
How to fix it?
ANSWER
The correct query is
SELECT name,
COUNT(CASE WHEN city = :london THEN 1 END) as countLondon,
COUNT(CASE WHEN city = :paris THEN 1 END) as countParis
FROM table2 GROUP BY name
as mentioned in the accepted answer I missed as countLondon
but also I deleted ELSE 0 from CASE statement

Your CityName fields' names should match your query's columns' aliases.
That's how Room then copies values from SQLite cursor to fields of this class.
So change aliases for columns, and your code should work:
#Query("SELECT name,
COUNT(CASE WHEN city = :london THEN 1 ELSE 0 END) as countLondon, // <- change here
COUNT(CASE WHEN city = :paris THEN 1 ELSE 0 END) as countParis // <- change here
FROM table2 GROUP BY name")
fun getPivot_CityNameList(london: String, paris: String): List<CityName>

Related

postgresql RIGHT Join: limit returned rows

I have the following schema:
expenses
id
name, varchar
cost, double
date, DATE
category_id, int f_key
user_id, int f_key
1
Pizza
22.9
22/08/2022
1
1
2
Pool
34.9
23/08/2022
2
1
categories
id
name, varchar
1
Food
2
Leisure
3
Medicine
4
Fancy food
users_categories(user_id int foreign key, category_id foreign key)
user_id int f_key
category_id int f_key
1
1
1
2
1
3
2
4
And two users with id 1 and 2.
Relation between user and category is many to many.
Problem:
I want to get statistics (total cost amount and count) for all categories. For categories where there are no expenses I want to return 0. Here is my query:
SELECT categories.name as name, count(expenses.name) as count, round(SUM(price)::numeric,2) as sum
FROM expenses
Right JOIN categories ON expenses.category_id = categories.id
and expenses.category_id in (
select users_categories.category_id from users_categories where users_categories.user_id = 1
)
and expenses.id in(
Select expenses.id from expenses
join users_categories on expenses.category_id = users_categories.category_id
and expenses.user_id = 1
AND (extract(year from date) = 2022 OR CAST(2022 AS int) is null)
AND (extract(month from date) = 8 OR CAST(8 AS int) is null)
)
GROUP BY categories.id ORDER BY categories.id
The response is:
name
count
sum
Food
1
22.9
Leisure
1
33.9
Medicine
0
null
Fancy food
0
null
How I should edit my query to eliminate the last row, because this category doesn't belong to the user 1.
In your query you used user_categories as subquery so it will not filter category ids,
Try this Query
SELECT categories.name as name,count(expenses.name) as count, coalesce(round(SUM(price)::numeric,2),0) as sum from
categories
left join users_categories on users_categories.category_id= categories.id
left join expenses ON expenses.category_id = categories.id
AND (extract(year from date) = 2022 OR CAST(2022 AS int) is null)
AND (extract(month from date) = 8 OR CAST(8 AS int) is null)
where users_categories.user_id='1'
GROUP BY categories.name,categories.id ORDER BY categories.id
OUTPUT :
name count sum
Food 1 22.90
Leisure 1 34.90
Medicine 0 0
You want to move expenses.category_id in ... out of the ON condition and into a WHERE clause.
When it is in the ON clause, that means rows which were removed by the in-test just get NULL-fabricated anyway. You want to remove those rows after the NULL-fabrication is done, so that they remain removed. But why do you use that in-test anyway? Seems like it would be much simpler written as another join.
What I understand is that you are trying to get the count and sum of expenses for all the categories related to the user_id 1 within the month of august 2022.
Please try out the following query.
WITH statistics
AS (SELECT e.category_id,
Count(e.*) AS count,
Round(Sum(e.cost), 2) AS sum
FROM expenses e
WHERE e.user_id = 1
AND ( e.date BETWEEN '01/08/2022' AND '31/08/2022' )
GROUP BY e.category_id),
user_category
AS (SELECT uc.category_id,
COALESCE(s.count, 0) AS count,
COALESCE(s.sum, 0) AS sum
FROM users_categories uc
LEFT JOIN statistics s
ON uc.category_id = s.id
WHERE uc.user_id = 1)
SELECT c.NAME,
u.count,
u.sum
FROM categories c
INNER JOIN user_category u
ON u.category_id = c.id;

Postgres: Update JSON Array and remove element matched with key

Given the table below
drop table if exists documents;
create table documents(docu_id text, attachments jsonb);
insert into documents values
('001',
'[{"province":"test province","city":"test city","barangay":"test barangay,"street":"test ST"},
{"province":"test province 2","city":"test city 2","barangay":"test barangay 2","street":"test street 2"}]'
),
('002',
'[{"province":"test province 2 1":"VALENZUELA CITY","test barangay 2 1":"test barangay 2 1","street":"test street 2 1"},
{"province":"test province 2 2","city":"test city 2 2","barangay":"test barangay 2 2","street":"test strett 2 2"}]'
);
How can i update json array matching street key word: test ST
expected output
[{"province":"test province 2","city":"test city 2","barangay":"test barangay 2","street":"test street 2"}]
[{"province":"test province 2 1":"VALENZUELA CITY","test barangay 2 1":"test barangay 2 1","street":"test street 2 1"},
{"province":"test province 2 2","city":"test city 2 2","barangay":"test barangay 2 2","street":"test strett 2 2"}]
demo:db<>fiddle
SELECT
docu_id,
jsonb_agg(elements.value) -- 3
FROM
documents,
jsonb_array_elements(attachments) AS elements -- 1
WHERE elements ->> 'street' != 'test ST' -- 2
GROUP BY docu_id -- 3
Expand array elements into one row each with jsonb_array_elements()
Filter these elements by whatever you like
Group remaining elements again into a JSON array using aggregate function jsonb_agg()
Update:
UPDATE documents d
SET attachments = s.new_attachments
FROM (
SELECT
docu_id,
jsonb_agg(elements.value) AS new_attachments
FROM
documents,
jsonb_array_elements(attachments) AS elements
WHERE elements.value ->> 'street' != 'test ST'
GROUP BY docu_id
) s
WHERE d.docu_id = s.docu_id
demo:db<>fiddle
Updating remaining value from json matched data instead no update happened. it will update as null or empty json.
UPDATE street_lov SET street = sub
FROM COALESCE((SELECT jsonb_agg(elements.value) as elements FROM street_lov,
jsonb_array_elements(street::jsonb) AS elements
WHERE elements.value ->> 'street' != 'test ST'
AND id = (SELECT id FROM street_id)
GROUP BY id ), '[]') sub where street_lov.id = (select id from street_id)
RETURNING street_lov.id,street

T-SQL select distinct based on highest values from another column

I have a table that contains number of tryouts, customerID, status of that one tryout and some other columns with various data.
Of course a single customerID can have multiple number of tryouts ( in the real table first tryout is number 1, second one number 2 etc.).
Ex.
Customer ID = 1, tryout = 1
Customer ID = 1, tryout = 2
Customer ID = 1, tryout = 3
Customer ID = 2, tryout = 1
Customer ID = 3, tryout = 1
Customer ID = 3, tryout = 2
And I want to have all distinct customerIDs but for each one only the row, that contains the highest tryout number for each customer in one table with data from all the other columns as well.
Ex.
tryouts, customerID, status, data1, data2
How can I achieve that ?
If you only want the customer ID and tryout value then you can try the following:
SELECT customerID, MAX(tryout) AS max_tryout
FROM yourTable
GROUP BY customerID
If you want the entire record, then one option would be to use ROW_NUMBER():
SELECT t.customerID, t.tryout, t.status, t.data1, t.data2
FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY customerID ORDER BY tryout DESC) rn
) t
WHERE t.rn = 1
Try
SELECT
CustomerID,
MAX(tryout) AS [Max tryout]
FROM
TheTable
GROUP BY
CustomerID
This should give you what you want

Obtaining certain value from child table

I have two tables:
Customer (Parent)
Agreement (Child)
I need to display a certain value in my query if one of the many agreement statusID's has a certain value (e.g. written off). The join between the two tables is CustomerID.
So if a customer has 3 agreements and 2 agreements have a statusID of 1 and one has 5, I need to display a certain value. I only want to return one row in this query rather than the 3 which would occur in a typical join
Any suggestions?
select
CustomerID,
max(case when StatusId = 1 then 1 else 0 end) as HasStatus1,
max(case when StatusId = 2 then 1 else 0 end) as HasStatus2
--etc.
from Customer
left join Agreement
group by Customer.CustomerID
This will return a single row per customer due to the group by, with flags indicating if they have any agreements in each status of concern - if you're looking this up for a single CustomerID you'd obviously throw a where statement in there, and you could remove the group by as well (you'd have to remove CustomerID from the result set though of course).
Taking into account your comment, you'd want something like:
;with grouped as (
select
CustomerID,
max(case when StatusId = 1 then 1 else 0 end) as HasStatus1,
max(case when StatusId = 2 then 1 else 0 end) as HasStatus2,
max(case when StatusId = 5 then 1 else 0 end) as HasStatus5
--etc.
from Customer
left join Agreement
group by Customer.CustomerID
)
select
CustomerID,
case
when HasStatus5 = 1 then 5
when (HasStatus1 = 1 OR HasStatus2) and <no other status>) then 1
--etc.
else <Can't return StatusId here because there might be more than one... so whatever your default actually is> END as Result
from grouped

Self join to lowest occurrence of group

I have a problem in T-SQL that I find difficult to solve.
I have a table with groups of records, grouped by key1 and key2. I order each group chronologically by date. For each record, I want to see if there existed a record before (within the group and with lower date) for which the field "datafield" forms an allowed combination with the current record's "datafield". For the allowed combinations, I have a table called AllowedCombinationsTable.
I wrote following code to achieve it:
WITH Source AS (
SELECT key1, key2, datafield, date1,
ROW_NUMBER() OVER(PARTITION BY key1, key2 ORDER BY date1 ASC) AS dateorder
FROM table
)
SELECT L.key1, L.key2, L.datafield, DC.datafield2
FROM Source AS L
LEFT JOIN AllowedDataCombinationsTable DC
ON D.datafield1 = L.datafield
LEFT JOIN Source AS R
ON R.Key1 = L.Key1
AND R.Key2 = L.Key2
AND R.dateorder < L.dateorder
AND DC.datafield2 = L.datafield
-- AND "pick the one record with lowest dateorder"
Now for each of these possible combination records, I want to pick the first one (see placeholder in code). How can I do it most efficiently?
EDIT: OK let's say for the source, only showing group (1, 1):
**Key1 Key2 Datafield Date DateOrder**
1 1 "Horse" 1-Jan-2010 1
1 1 "Horse" 2-Jan-2010 2
1 1 "Sheep" 3-Jan-2010 3
1 1 "Dog" 4-Jan-2010 4
1 1 "Cat" 5-Jan-2010 5
AllowedCombinationsTable:
**Datafield1 Datafield**
Cat Sheep (and Sheep Cat)
Cat Horse (and Horse Cat)
Dog Horse (and Horse Dog)
After my join I have now:
**Key1 Key2 Datafield Date DateOrder JoinedCombination JoinedCombinationDateOrder**
1 1 "Horse" 1-Jan-2010 1 NULL NULL
1 1 "Horse" 2-Jan-2010 2 NULL NULL
1 1 "Sheep" 3-Jan-2010 3 NULL NULL
1 1 "Dog" 4-Jan-2010 4 "Horse" 1
1 1 "Dog" 4-Jan-2010 4 "Horse" 2
1 1 "Cat" 5-Jan-2010 5 "Horse" 1
1 1 "Cat" 5-Jan-2010 5 "Horse" 2
1 1 "Cat" 5-Jan-2010 5 "Sheep" 3
I want to display only the first "Horse" for record 4 "Dog", and also only the first "Horse" for record 5 "Cat".
Get it? ;)
I think this may do it--don't have data set up to test the query with. Check the comments for rationale.
WITH Source AS (
SELECT key1, key2, datafield, date1,
ROW_NUMBER() OVER(PARTITION BY key1, key2 ORDER BY date1 ASC) AS dateorder
FROM table
)
SELECT L.key1, L.key2, L.datafield, DC.datafield2
FROM Source AS L
LEFT JOIN AllowedDataCombinationsTable DC
ON DC.datafield1 = L.datafield -- DC Alias
LEFT JOIN Source AS R
ON R.Key1 = L.Key1
AND R.Key2 = L.Key2
AND DC.datafield2 = R.datafield -- Changed alias from L to R
AND R.dateorder = 1 -- Pick out lowest one
AND R.dateorder < L.dateorder -- Make sure it's not the same one
Well, I don't use WITH or OVER, so this is a different approach.. I might be over-simplifying something, but without having the data in front of me this is what I came up with:
SELECT distinct a.Key1, a.Key2, a.Datafield,
ISNULL(b.Datafield,'') as Datafield1,
ISNULL(b.Date,a.Date) as `Date`,
MIN(a.DateOrder) as DateOrder
FROM Source a
LEFT JOIN Source b
ON a.Key1 = b.Key1
AND a.Key2 = b.Key2
AND a.Dateorder <> b.Dateorder
LEFT JOIN AllowedDataCombinationsTable c
ON a.Datafield = c.Datafield
AND b.Datafield = c.Datafield1
GROUP BY a.Key1, a.Key2, a.Datafield, ISNULL(b.Datafield,''), ISNULL(b.Date,a.Date)