Complex SQL converted to active record - sql

I have been puzzling over the following query, and how it could be done using active record.
select * from links where id in
(select id from
(select votable_id as id, count(votable_id) as count from votes where vote_scope = 'flag' and votable_type = 'Link' group by votable_id )
as x where count < 3);
I am currently using just the raw SQL query in my finder at the moment (find_by_sql), but was wondering if there was a more 'railsy' way of doing this?
EDIT
Thanks to Joachim Isaksson the query can be squashed to
select * from links where id in
(select votable_id from votes
where vote_scope = 'flag'
and votable_type = 'Link'
group by votable_id
HAVING COUNT(*) < 3) ;

Let's start with me not being a rails guru in any capacity, and I can't test run this so a bit "running blind". In other words, take this with a grain of salt :)
Rewriting your query as a join (assuming here your links table has id and one more field called link for the sake of the GROUP BY;
SELECT links.*
FROM links
JOIN votes
ON links.id = votes.votable_id
AND votes.vote_scope = 'flag'
AND votes.votable_type = 'Link'
GROUP BY votes.votable_id, links.id, links.link
HAVING COUNT(*) < 3;
(SQLfiddle to test with)
...should make something like this work (line split for readability)
Link.joins("JOIN votes ON links.id = votes.votable_id
AND votes.vote_scope = 'flag' AND votes.votable_type = 'Link'")
.group("votes.votable_id, links.id, links.link")
.having("COUNT(*) < 3")
Having your associations set up correctly may allow you to do the join in a more "railsy" way too.

Related

Using the results of one SQL query in another query (Athena)

I'm a bit stuck on how can do this so hoping someone can point me in the right direction.
I have this simple query:
SELECT acchl.is_current,
acchl.aircraft_registration_number,
acchl.aircraft_transponder_code
FROM fleets.aircraft_all_history_latest acchl
WHERE acchl.is_current = true
Which I then want to use the results from this query to find all the duplicate aircraft transponder codes along with the aircraft registration numbers with something like a self join to fetch the actual rows that may have duplicate values using something like this:
select s.id, s.col_maybe_dups
from sometab s
join (select col_maybe_dups as cd
from sometab
group by cd
having count(*) > 1) x
on x.cd = s.col_maybe_dups;
Looks good!
You could WITH it too...
WITH data as (
SELECT acchl.is_current,
acchl.aircraft_registration_number,
acchl.aircraft_transponder_code
FROM fleets.aircraft_all_history_latest acchl
WHERE acchl.is_current
)
select * from data
where aircraft_transponder_code in
(select aircraft_transponder_code
from data
group by 1
having count(*) > 1
)

Merging two query results in a materialized view

Im trying to merge two SELECT results into one view.
The first query returns the id's of all registered users.
The second query goes through an entire table and counts how many victories a player has and returns the id of the player and number of wins.
What I'm trying to do now is to merge these two results, so that if the user has wins it states how many but if he doesn't then it says 0.
I tried doing it like this:
SELECT profile.user_id
FROM profile
FULL JOIN ( SELECT player_game_data.user_id,
count(player_game_data.user_id) AS wins
FROM player_game_data
WHERE player_game_data.is_winner = 1
GROUP BY player_game_data.user_id) t2 ON profile.user_id::text = t2.user_id::text;
But in the end it only returns id's of the players and there isn't a count column:
What am I doing wrong?
Is this what you want?
select p.*,
(select count(*)
from player_game_data pg
where pg.user_id = p.user_id and pg.is_winner = 1
) as num_wins
from profile p;
Or, if all users have played at least one game, you can use conditional aggregation:
select pg.user_id,
count(*) filter (where pg.is_winner = 1)
from player_game_data pg
group by pg.user_id;
Or, if is_winner only takes on the values of 0 and 1:
select pg.user_id, sum(ps.is_winner)
from player_game_data pg
group by pg.user_id;
Thanks for the help Gordon. I've got it to work now.
The final query looks like this :
SELECT p.user_id,
( SELECT count(*) AS count
FROM player_game_data pg
WHERE pg.user_id::text = p.user_id::text AND pg.is_winner = 1) AS wins,
( SELECT count(*) AS count
FROM player_game_data pg
WHERE pg.user_id::text = p.user_id::text AND pg.is_winner = 0) AS losses,
( SELECT count(*) AS count
FROM player_game_data pg
WHERE pg.user_id::text = p.user_id::text) AS games_played
FROM profile p;
And when I run it I get the result that i wanted:

using correlated subquery in the case statement

I’m trying to use a correlated subquery in my sql code and I can't wrap my head around what I'm doing wrong. A brief description about the code and what I'm trying to do:
The code consists of a big query (ALIASED AS A) which result set looks like a list of customer IDs, offer IDs and response status name ("SOLD","SELLING","IRRELEVANT","NO ANSWER" etc.) of each customer to each offer. The customers IDs and the responses in the result set are non-unique, since more than one offer can be made to each customer, and a customer can have different response for different offers.
The goal is to generate a list of distinct customer IDs and to mark each ID with 0 or 1 flag :
if the ID has AT LEAST ONE offer with status name is "SOLD" or "SELLING" the flag should be 1 otherwise 0. Since each customer has an array of different responses, what I'm trying to do is to check if "SOLD" or "SELLING" appears in this array for each customer ID, using correlated subquery in the case statement and aliasing the big underlying query named A with A1 this time:
select distinct
A.customer_ID,
case when 'SOLD' in (select distinct A1.response from A as A1
where A.customer_ID = A1.customer_ID) OR
'SELLING' in (select distinct A1.response from A as A1
where A.customer_ID = A1.customer_ID)
then 1 else 0 end as FLAG
FROM
(select …) A
What I get is a mistake alert saying there is no such object as A or A1.
Thanks in advance for the help!
You can use exists with cte :
with cte as (
<query here>
)
select c.*,
(case when exists (select 1
from cte c1
where c1.customer_ID = c.customer_ID and
c1.response in ('sold', 'selling')
)
then 1 else 0
end) as flag
from cte c;
You can also do aggregation :
select customer_id,
max(case when a.response in ('sold', 'selling') then 1 else 0 end) as flag
from < query here > a;
group by customer_id;
With statement as suggested by Yogesh is a good option. If you have any performance issues with "WITH" statement. you can create a volatile table and use columns from volatile table in your select statement .
create voltaile table as (select response from where response in ('SOLD','SELLING').
SELECT from customer table < and join voltaile table>.
The only disadvantge here is volatile tables cannot be accessed after you disconnect from session.

SQL Views (id + count_table1_column1 + count_table2_column_1)

Im doing following query to select out a serialnumber from table Alerts, and then count how many alerts there is for that serialnumber together with the count on how many measurements there also is for that serialnumber. Measurements is stored in another table. (first 2 queries is jsut there to show you the result for better understanding)
SELECT InstrumentSerialNumber FROM [dbo].[CloudMeasurements]
SELECT InstrumentSerialNumber FROM [dbo].[CloudAlerts]
SELECT
DISTINCT InstrumentSerialNumber,
(SELECT COUNT(*) FROM [CloudAlerts] WHERE [CloudAlerts].InstrumentSerialNumber = InstrumentSerialNumber) AS Alerts,
(SELECT COUNT(*) FROM [CloudMeasurements] WHERE [CloudMeasurements].InstrumentSerialNumber = InstrumentSerialNumber) AS Measurements
FROM [CloudAlerts]
Result
See picture for result of the query.
I assume it respond with Count(*) summarized which makes it wrong from my perspective. How do I write this?
Greetings
Try joining the results of their groups:
SELECT
A.InstrumentSerialNumber,
A.TotalAlerts,
ISNULL(M.TotalMeasurements, 0) TotalMeasurements
FROM
(SELECT InstrumentSerialNumber, COUNT(*) TotalAlerts FROM [CloudAlerts] GROUP BY InstrumentSerialNumber) AS A
LEFT JOIN (SELECT InstrumentSerialNumber, COUNT(*) TotalMeasurements FROM [CloudMeasurements] GROUP BY InstrumentSerialNumber)
AS M ON M.InstrumentSerialNumber = A.InstrumentSerialNumber

Updating a field based on a count of records from a different table

I need to set a value to 1 in table where the count for a given role in another table is > 1.
The table cursillo.members has fields memid and hsd. Table cursillo.teams has fields memid (the link between the two) a field called movement and another called role.
What I am trying to accomplish is something similar to this:
update cursillo.members_eligible p, cursillo.teams pp
set p.hsd=1
where p.memid = pp.memid AND (pp.role = 'HSD' OR pp.role = 'SD')
and pp.movement = 'Cursillo' AND count(pp.role) > 1;
or this:
update members_eligibile
set hsd=1
from teams
where teams.memid=members_eligible.memid
and (teams.role = 'HSD' OR teams.role = 'SD')
and teams.movement = 'Cursillo'
and count(teams.role) > 1;
In other words if a given memid has more than one record in the cursillo.teams table where the value of the role is equal to either HSD or SD, then set cursillo.members_eligible.hsd to 1.
I can't figure out to handle the count() part.
Thanks,
Mike Reed
Possible duplicate question:
MySQL - Using COUNT(*) in the WHERE clause
Try using the 'having' keyword
Here's the relevant section from the linked answer (in this case, a select statement):
select gid
from `gd`
group by gid
having count(*) > 10
order by lastupdated desc
It looks like 'having' can only be used in select statments though:
http://www.mysqltutorial.org/mysql-having.aspx
So you may need to have your update's where clause include a select statement:
update members_eligibile
set hsd=1
from teams
where teams.memid=members_eligible.memid
and (teams.role = 'HSD' OR teams.role = 'SD')
and teams.movement = 'Cursillo'
and members_eligible.memid IN
(SELECT members_eligible.memid from members_eligible where teams.memid=members_eligible.memid having count(teams.role) > 1);
You will have to adjust the select statement, I haven't tested it
SQL Server 2005+
UPDATE x
SET x.hsd = 1
FROM (SELECT e.hsd, COUNT(*) OVER (PARTITION BY t.role) AS cnt
FROM teams t JOIN members_eligibile e ON t.memid = e.memid
WHERE (t.role = 'HSD' OR t.role = 'SD') AND t.movement = 'Cursillo') x
WHERE x.cnt > 1