Return rows if either a SQL "having" clause or a secondary condition is met - sql

I'm trying to return rows that meet the criteria of either a HAVING clause, or a secondary condition outside of it. I'm not sure how to work this logic into the same SELECT statement. I'm using Microsoft SQL Server 2008.
Let's say I have one table that has information about some products, and a second table that has information about the sales figures for these products. I want to select all blue-colored products that have either sold 500+ units OR were first launched in 2020, regardless of the sales numbers.
The former is captured by a HAVING clause that sums up some values that represent sales figures. If, however, this clause returns false, I want the product to still be included in the returned rows if the product launch year is "2020".
By this logic, I would expect all of the following products to be included in the results, because they meet at least 1 of the conditions:
{"name" => "toaster", "total_sales" => 525, "launch_year" => 2010} # meets the "500+ units sold" condition
{"name" => "oven", "total_sales" => 100, "launch_year" => 2020} # meets the "product was launched in 2020" condition
{"name" => "fridge", "total_sales" => 600, "launch_year" => 2020} # meets both conditions
but I would not expect the following products to be included in the results, because they meet neither of the conditions:
{"name" => "couch","total_sales" => 50, "launch_year" => 1990} # does not meet either condition (<500 units sold, and product not launched in 2020)
{"name" => "chair", "total_sales" => 499, "launch_year" => 2019} # does not meet either condition (<500 units sold, and product not launched in 2020)
This is the query itself, although it won't execute successfully:
SELECT product.name, product.launch_year, (SUM(sales.units_sold)) as total_sales
FROM product_info as product
LEFT JOIN price_data as sales on product.id = sales.id
WHERE product.color = 'blue'
GROUP BY product.name, product.launch_year
HAVING (SUM(sales.units_sold)) >= 500 OR product.launch_year = '2020'
The OR condition at the very end returns an invalid column name '2020' error, as it is syntactically incorrect. However, it represents the logic I am trying to achieve. Is it possible to incorporate this kind of conditional situation into the HAVING clause? Or must they exist in two separate SELECT statements? Or is there a completely different way to go about doing this?
I've checked for other answers on here, but I've only seen instances where a comparison of the same type was made within a HAVING clause, as opposed to a comparison between the HAVING clause and a completely separate condition.

Related

Cakephp query to get last single field data from multi user

I have a table called Transaction with relation User, Transaction has a field called balance.
Data looks like:
id user_id balance
1 22 365
2 22 15
3 22 900
4 32 100
4 32 50
I need all users associative data and last insert balance field of User. For example here id=3 is last inserted data for user_id=22.
In raw SQL I have tried this:
select * from transactions where id in (select max(id) from transactions group by user_id)
If I add here a inner join I know I can also retrieve User data. But how can I do this in CakePHP?
IMHO, subqueries are ugly in CakePHP 2.x. You may as well hard code the SQL statement and execute it through query(), as suggested by #AgRizzo in the comments.
However, when it comes to retrieving the last (largest, oldest, etc.) item in a group, there is a more elegant solution.
In this SQL Fiddle, I've applied the technique described in
Retrieving the last record in each group
The CakePHP 2.x equivalent would be:
$this->Transaction->contains('User');
$options['fields'] = array("User.id", "User.name", "Transaction.balance");
$options['joins'] = array(
array('table' => 'transactions',
'alias' => 'Transaction2',
'type' => 'LEFT',
'conditions' => array(
'Transaction2.user_id = Transaction2.user_id',
'Transaction.id < Transaction2.id'
)
),
);
$options['conditions'] = array("Transaction2.id IS NULL");
$transactions=$this->Transaction->find('all', $options);

Active Record query to match every subset element

In my RoR application, I've got a database lookup similar to this one:
Client.joins(:products).where({'product.id' => [1,2,3]})
Unfortunately this will return all clients that have bought product 1, 2 or 3 but I only want to get back the clients, that bought all of the three products. In other words, I'd like to write a query that matches for n elements in a given set.
Are there any elegant solutions for this?
This is not really elegant. But it should translate into the needed SQL.
Client.joins(:products).
where({'products.id' => [1,2,3]}).
group('users.id').
having('COUNT(DISTINCT products.id) >= 3')
Same answer with more dynamic way
ids = [1,2,3]
Client.joins(:products).
where({'products.id' => ids}).
group('users.id').
having('COUNT(DISTINCT products.id) >= ?', ids.size)

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

How can I retrieve the newest record in each group in a DBIx::Class resultset search?

I'm using group_by in a DBIx::Class resultset search. The result returned for each group is always the row in the group with the lowest id (i.e the oldest row in the group). I'm looking for a way to get the row with the highest id (i.e. the newest row in the group) instead.
The problem is fundamentally the same as this:
Retrieving the last record in each group
...except that I'm using DBIx::Class not raw SQL.
To put the question in context:
I have a table of music reviews
review
------
id
artist_id
album_id
pub_date
...other_columns...
There can be multiple reviews for any given artist_id/album_id.
I want the most recent reviews, in descending date order, with no more than one review per artist_id/album_id.
I tried to do this using:
$schema->resultset('Review')->search(
undef,
{
group_by => [ qw/ artist_id album_id / ],
order_by => { -desc => 'pub_date' },
}
);
This nearly works, but returns the oldest review in each group instead of the newest.
How can I get the newest?
For this to work you are relying on broken database behaviour. You should not be able to select columns from a table when you use group by unless they use an aggregate function (min, max etc.) or are specified in the group by clause.
In MySQL, even the manual admits this is wrong - though it supports it.
What I think you need to do is get the latest dates of the reviews, with max(pub_date):
my $dates = $schema->resultset('Review')->search({},
{
select => ['artist_id', 'album_id', {max => 'pub_date'}],
as => [ qw(artist_id album_id recent_pub_date) ],
group_by => [ qw(artist_id album_id) ],
}
);
Then loop through to get the review:
while (my $review_date = $dates->next) {
my $review = $schema->resultset('Review')->search({
artist_id => $review_date->artist_id,
album_id => $review_date->album_id,
pub_date => $review_date->get_column('recent_pub_date'),
})->first;
}
Yep - it's more queries but it makes sense - what if two reviews are on the same date - how should the DB know which one to return in the select statement?

How to define new instantaneous variable row by row - RAILS3 BEGINNER

I was hoping somebody may be able to point me in the right direction...
I have a database called Info and use a find command to select the rows in this database which match a certain criteria
#matching = Info.find( :all, :conditions => ["product_name = ?", distinctproduct], :order => 'Price ASC')
I then pull out the cheapest of these items
#cheapest = #matching.first
Finally, I would like to create an instantaneous array which contains a list of #cheapest for a number of different search criteria. i.e. row 1 in #allcheapest is #cheapest for criteria 1, row 2 in #allcheapest is #cheapest for criteria 2, ...
Any help would be great, thanks in advance
Info.where(:product_name => distinct_product.to_s).order('Price ASC').first
to select the cheapest price for the product_name. Without more insight into how your database is structured, it is difficult to suggest how to obtain the latter, but you may try
Info.where(:product_name => distinct_product.to_s).order('Price ASC').group(:product_name)