Rails and PostgreSQL issue with Having - ruby-on-rails-3

I am trying to retrieve a table from the following Action table:
Columns :ID, Name, Status, Product_ID, User_ID and several other non relevant column for this issue.
Every time a user want a product I create a record like this :
Name = Want, Status = True, Product_ID = ID of the product wanted and User_ID is the ID of the user.
Then every time a user unwant a product I create a record like this :
Name = Want, Status = False, Product_ID = ID of the product unwanted and User_ID is the ID of the user.
I did that because I have other action's name in my table.
Now I'd like to retrieve all the product wanted, so I should retrieve all the last want actions grouped by product_id for a particular user ordered by descending created_at, and then only retrieve the action where the status = true.
So to get all the last want actions grouped by product_id for a User I did that:
Action.select("DISTINCT ON (product_id) product_id,created_at, status, * ").where{(user_id == id) & (name == 'want')}.group("product_id, id, status").order(' product_id,created_at DESC')
That retrieve the last actions per product and user but retrieve both true and false statuses
The only issue is that I don't know how to filter this table to only get the action if it's true.
I tried to do it like this:
Action.select("DISTINCT ON (product_id) product_id,created_at, status, * ").where{(user_id == id) & (name == 'want')}.group("product_id, id, status").having("status = 'true'").order(' product_id,created_at DESC')
But that would give me the last actions where want = true. If the last action is status = false it will retrieve the one before that when status = true.
Here is an idea of what I want but I have no idea how to achieve that with rails:
http://sqlfiddle.com/#!9/e4117/3

You could try adding a subselect to the conditions and remove the group by:
Action.
where( user_id: id, name: "want", status: "true").
where( ["id IN (SELECT max(id) FROM actions WHERE user_id = ?
AND name = 'want' GROUP BY product_id)", id]).
order( "product_id")
You would need to rely on the order of the id column to be the last action for this to work correctly. If you can't do that you could use DISTINCT ON in the subselect:
Action.
where( user_id: id, name: "want", status: "true").
where( ["id IN (SELECT DISTINCT ON (product_id) id FROM actions
WHERE user_id = ? AND name = 'want'
ORDER BY product_id, created_at DESC)", id]).
order( "product_id")

Related

How to Order By on a key inside JSONB column in PostgreSQL

I want to get all the details for a particular userID in DESC order of email_subject which is a key present inside "metadata" column of type JSONB.
Metadata is a JSONB column which has below structure:
{
"emails" : [ {"recipients":["userId1#testsite.com"], "email_subject":"Test Subject1"},
{"recipients":["userId2#testsite.com"], "email_subject":"Test Subject2"}]
}
Query:
SELECT * FROM details
WHERE classification = 'EMAIL'
AND EXISTS (SELECT TRUE FROM jsonb_array_elements(metadata->'emails') x
WHERE x->>'recipients' LIKE ('["%userId1#testsite.com%"]') ORDER BY x->>'email_subject' DESC)
ORDER BY metadata->'emails'->>'email_subject' DESC
The above query is able to give me required results for userId "userId1#testsite.com" but I want the final result to be sorted as per this field :
metadata->'emails'->>'email_subject'
I would appreciate all the inputs/suggestions.

Query friendships table (with pairs of rows) to find mutual statuses

I have the following schema
Relationship:
fromId (fkey)
toId (fkey)
isFriend (boolean)
isBlocked (boolean)
A user requests friendship by adding a record with fromId set to his id and toId set to the friend's id and isFriend=true.
To accept the request, the other user does the same but ids reversed.
They are only friends when both records exist and both have isFriend=true.
I'm trying to make a query that lists a users' friends given an ID (WHERE userId = x)
This is what I started with:
SELECT "a"."fromId" AS "userId1", "a"."toId" AS "userId2", "b"."fromId" AS "userId2", "b"."toId" AS "userId1"
FROM "Relationship" a
INNER JOIN "Relationship" b
ON "a"."fromId" = "b"."toId" AND "b"."fromId" = "a"."toId"
WHERE "a"."isFriend" = true AND "b"."isFriend" = true
This is the result I get with
With this, I'm able to get only the relationships that have records in both directions (friends), but what I'm trying to get (if possible), are only the IDs of the friends of a user
Thanks
select *
from "Relationship" a
where
a."fromId" = x and a."isFriend" and
exists (
select 1
from "Relationship" b
where
b."toId" = x /* b."toId" = a."fromId" */ and
b."isFriend");

Getting rid of the "where" clause if a value = 'ADMIN'

So there is the main table (named Table1) and one of the columns in that table has a column called company name. and there's a another table called Account and in that table it has the usernames of people and the company name that user is associated with. so only the information in table1 associated with the user's company should be shown, unless if they're an admin.
I'm working on an APEX app on Oracle
I currently have a query that looks like this:
Select
SUPPLIER,
sum(NUMBER_OF_TICKETS) TICKETS,
round((sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)*.1),2) COMMISION,
round(sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR),2) TOTAL_SALES,
round((sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)-(sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)*.1)),2) COMPANY_OWE,
CURRENCY
FROM TABLE1
WHERE
Supplier = (select COMPANYNAME from Account where lower(USERNAME)=lower(:APP_USER)) AND
PURCHASE_DATE_AND_TIME >= TO_DATE(:P2_START)
AND PURCHASE_DATE_AND_TIME < TO_DATE(:P2_END)+ 1
group by
SUPPLIER,
CURRENCY
I'm running into trouble with the "WHERE" function.
Because I basically want to have a if...then... (or a case statement in this case)
where
IF
((select COMPANYNAME from Account where lower(USERNAME)=lower(:APP_USER)) == 'COMPANY1' AND (select USERTYPE from Account where lower(USERNAME)=lower(:APP_USER)) == 'ADMIN')
THEN
show all the rows; aka, Supplier = (select COMPANYNAME from BBAccount where lower(USERNAME)=lower(:APP_USER)) in the query
Does anyone have any ideas on how to go about writing a query for this?
I tried doing
Case statement When statement Then NULL
It doesn't work
I tried drawing a diagram, let me know if this makes it more clear:
Diagram
As it is about Apex, I'd suggest you to create a function:
create or replace function f_is_admin_01 (par_app_user in varchar2)
return number
is
-- function returns 1 if PAR_APP_USER is admin; otherwise, it returns 0
l_usertype account.usertype%type;
begin
select a.usertype
into l_usertype
from account a
where lower(a.username) = lower(par_app_user);
return case when l_usertype = 'ADMIN' then 1
else 0
end;
exception
when no_data_found then
return 0;
end;
Now, you can use it in query as
select ...
from account a join table1 t on a.companyname = t.companyname
where (lower(a.username) = lower(:APP_USER) or f_is_admin_01 (:APP_USER) = 1)
and t.purchasedate ...
Such an approach (I mean, having a function) can be useful elsewhere; for example, if you want to show certain page region only to admins - you'd put
return f_is_admin_01(:APP_USER) = 1;
into region's "Server side condition" (its type would be "Function that returns Boolean").
See if it helps.
So there is the main table (named Table1) and one of the columns in that table has a column called company name. and there's a another table called Account and in that table it has the usernames of people and the company name that user is associated with. so only the information in table1 associated with the user's company should be shown, unless if they're an admin
I think that you could simply join - something like:
SELECT ...
FROM TABLE1 t
INNER JOIN ACCOUNTS a
ON lower(a.username) = lower(:APP_USER)
AND (a.companyname = t.companyname OR a.usertype = 'ADMIN')
WHERE
t.purchase_date_and_time >= TO_DATE(:P2_START)
AND t.purchase_date_and_time < TO_DATE(:P2_END) + 1
GROUP BY ...
The inner join on ACCOUNTS is there to implement the filtering logic: the user must either the user belongs to the same company, or have the admin type.
EXISTS(...) is your friend:
Select SUPPLIER
,sum(NUMBER_OF_TICKETS) TICKETS
,round((sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)*.1),2) COMMISION
,round(sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR),2) TOTAL_SALES
,round((sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)
-(sum(NUMBER_OF_TICKETS*AMOUNT_PAYABLE__FOREIGN_CUR)*.1)),2) COMPANY_OWE
, CURRENCY
FROM TABLE1 t
WHERE EXISTS (
SELECT 1
from Account x
where x.Supplier = t.COMPANYNAME
AND lower(x.USERNAME) = lower(:APP_USER))
AND x.PURCHASE_DATE_AND_TIME >= TO_DATE(:P2_START)
AND x.PURCHASE_DATE_AND_TIME < TO_DATE(:P2_END)+ 1
)
group by SUPPLIER, CURRENCY
;

SQL select records based on collection result from another query

I am stuck a bit on this step: there are two tables:
booking (bookingId, id, userId)
timetableSlot (id,classId)
"id" linkes both tables.
Task - to show all classes,booked by user.
In the Servlet i can select all bookings for particular user from the booking table:
int userId=Integer.parseInt(request.getParameter("loggedInUserId"));
BookingDB allUserBookings = new BookingDB();
Collection<BookingDB> userBookings = new ArrayList<BookingDB>();
userBookings = allUserBookings.displayUserBookings(userId);
The result is a collection of records for one user bookings with many timetable slot ids. How can I pass all those ids to select corresponding records from timetableSlot table? Select collection based on collection? Thank you
SELECT * FROM ttslot WHERE id IN (
SELECT id FROM booking WHERE booking.userId=76
)

rails understanding sql code to write tests

I want to figure out what is checked with this method and then I will test according to this.
partner.rb
def get_record_count
self.administrator? ? ContactRecord.where("partner_id in ( SELECT id FROM partners WHERE company_id = ? )",self.company_id).size : self.contact_record.size
end
If that partner is an administrator then it matches it's company_id with fellow partners. Then it retrieves associated ContactRecords where partner_id = fellow ids and return back the size of total such records : number of matched contact records.
If it is not an administrator, it gives back it's contact_record.size
ActiveRecord substitute for:
ContactRecord.where("partner_id in ( SELECT id FROM partners WHERE company_id = ?)", self.company_id).size
ContactRecord.where(:partner_id => Partner.where(:partner_id => self.company_id).pluck(:id)).size
1.
SELECT id FROM partners WHERE company_id = ?
This part returns list of ids of "partners" with company_id = id of current Object (Model). Name this list as PARTNERS_LIST
2.
ContactRecord.where("partner_id in ( SELECT id FROM partners WHERE company_id = ? )",self.company_id)
And this part finds ContactRecords with "partner_id" belongs to PARTNERS_LIST.
3.
self.administrator? ? ContactRecord.where("partner_id in ( SELECT id FROM partners WHERE company_id = ? )",self.company_id).size
Finally this returns count of ContactRecords, finded at stage 2.