I am trying to get the highest and lowest value associated with an account for a 1 year timeframe for a country. This data is pulled from one table.
I will have the highest account return for account one and lowest account return for account two for a country. So 1 result per country.
I've got the following but it doesn't work properly, it actually provides me the highest and lowest values from an incorrect account as it should only work with accounts that have 1 year timeframe as well.
Also forgot to add perhaps ordering the overall result by dpromo_one only do these countries eg country in ('united states','united kingdom','south africa','india','australia') for these selected countries only. Its just got quite complex that it went way over my head.
SELECT DISTINCT acc2.account_name AS account_one, acc5.account_name AS account_two,
MAX( acc2.dpromo_rate ) AS dpromo_one, MIN( acc5.dpromo_rate ) AS dpromo_two,
acc2.deposit_term, acc2.country
FROM accounts acc2
INNER JOIN accounts acc5 ON acc2.country = acc5.country
WHERE acc2.type =2
AND acc5.type =2
AND acc2.deposit_term = '1 Year'
GROUP BY country
overall output example could be the following
for line 1:
Country Bank Highest Bank Lowest
USA BOFA 1yr 1% Wells Fargo 1yr 0.5%
UK HSBC 1yr 0.5% Halifax 1yr 0.25%
Australia CBA 1yr 0.4% NAB 1yr 0.1%
eg the accounts table has the following fields for example that are relevant
account_name
country
dpromo_rate
deposit_term
note that we are having both accounts and rates side by side. my code does this but incorrectly though and thats why it also explains why i have aliases for duplicate field names.
To give you the basics in your output example:-
SELECT DISTINCT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
This is just getting the country, account name with the max rate, the actual max rate, account name with the min rate and the actual min rate.
Not sure where deposit term and provider are wanted in the output, but they would be easy to get from the b or d alias tables.
Note that that this will make a mess should you have multiple accounts in a country which all share the max or min rates.
To limit it to a few countries and order by the max rate:-
SELECT DISTINCT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
WHERE z.country IN ('united states', 'united kingdom', 'south africa', 'india', 'australia')
ORDER BY b.dpromo_rate
To limit it to one per country then you can do this:-
SELECT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
WHERE z.country IN ('united states', 'united kingdom', 'south africa', 'india', 'australia')
GROUP BY z.county
ORDER BY b.dpromo_rate
Note that if 2 accounts in a country have the same rate which is the highest rate for that country then only one will be returned. Which one that is returned is not certain.
It is doing exactly what MySQL says it will do. Columns not included in the group by clause have arbitrary values. In most other databases, the query would simply fail with a syntax error.
Here is a trick to get the names of the accounts:
SELECT substring_index(group_concat(acc2.account_name order by acc2.dpromo_rate desc), ',', 1) AS account_one,
substring_index(group_concat(acc5.account_name order by acc5.dpromo_rate asc), ',', 1) AS account_two,
MAX( acc2.dpromo_rate ) AS dpromo_one, MIN( acc5.dpromo_rate ) AS dpromo_two,
acc2.deposit_term, acc2.country
FROM accounts acc2
INNER JOIN accounts acc5 ON acc2.country = acc5.country
WHERE acc2.type =2 AND acc5.type =2 AND acc2.deposit_term = '1 Year'
GROUP BY country
I think you can simplify the query to a simple aggregation. I don't see why you are doing a join:
SELECT substring_index(group_concat(acc.account_name order by acc.dpromo_rate desc), ',', 1) AS account_one,
substring_index(group_concat(acc.account_name order by acc.dpromo_rate asc), ',', 1) AS account_two,
MAX(acc.dpromo_rate) AS dpromo_one, MIN(acc.dpromo_rate) AS dpromo_two,
acc.deposit_term, acc.country
FROM accounts acc
WHERE acc.type = 2 and acc.deposit_term = '1 Year'
GROUP BY country;
If you intend for the deposit term to only apply to the max, then replace the max with:
max(case when acc.deposit_term = '1 Year' then acc.dpromo_rate end) as dpromo_one
I thing this will give result
select max(dpromo_rate), min(dpromo_rate), country, account_name, provider from accounts
where deposit_term = '1 Year' group by country, provider, account_name
Related
I want to return the personal best for a user, by class & round; but the Date Shot is coming out incorrect. Help please - so frustrating!
SELECT
c.Class,
r.Round,
h.shootdate as 'Date Shot',
max(h.Score) AS 'Personal Best'
FROM history h, classes c, rounds r
WHERE c.id = h.classid AND r.id = h.roundid AND h.userid = 1
GROUP BY c.Class, r.Round
You could use a self join on history table to pick a row with maximum score for each user per classid and roundid
SELECT
c.Class,
r.Round,
h.shootdate as 'Date Shot',
h.Score AS 'Personal Best'
FROM history h
JOIN (
SELECT classid, roundid, max(score) score
FROM history
WHERE userid = 1
GROUP BY classid, roundid
) h1 ON h.classid = h1.classid AND h.roundid = h1.roundid AND h.score = h1.score
JOIN classes c ON c.id = h.classid
JOIN rounds r ON r.id = h.roundid
-- WHERE h.userid = 1 // not necessary
In your query you are picking shootdate which is not present in group by that is why you are not getting correct value where score is max, Also use explicit join syntax to relate your tables
I have 2 queries that work fine separately. Given they are similar, I'd like to consolidate them into one performant query. Seems straightforward as the where clauses are similar. But the sum, count, and min functions all apply to different rows and get in the way.
Context:
Users can score (or rate) a location and get points
User A can refer User B and get referral points when User B first submits a score
Points expire after a certain date
Goal is to build a leaderboard of users and their total points for scoring and referring for a particular location (area/country)
Positional parameters are filled in with hard values for 'Massachusetts', 'United States', and the scoreDateTime expiration date and are unfortunately duplicated in both select subqueries.
Question:
How can the query below be reorganized to combine constraints? There must be a way to start with a list of scores from a specific location after a certain date. The only complication is to get User B's first score date and only offer referral points to User A if it is after the expiration date.
select scoring.userId, scoring.points + referring.points as leaderPoints
from (
select userId, sum(ratingPoints) as points
from scores s, locations l
where s.locationId = l.locationId and
l.locationArea = 'Massachusetts' and
l.locationCountry = 'United States' and
s.scoreDateTime > '2016-04-16 18:50:53.154' and
s.userId != 0
group by s.userId
) as scoring
join (
select u1.userId, count(*) * 20 as points
from users u0
join users u1 on u0.userId = u1.userId
join users u2 on u2.referredByEmail = u1.emailAddress
join scores s on u2.userId = s.userId
join locations l on s.locationId = l.locationId
where l.locationArea = 'Massachusetts' and
l.locationCountry = 'United States' and
scoreDateTime = (
select min(scoreDateTime)
from scores
where userId = u2.userId
) and
scoreDateTime >= '2016-04-16 18:50:53.154'
group by u1.userId
) as referring on scoring.userId = referring.userId
order by leaderPoints desc
limit 10;
This is untested code, but it should do the trick. The Cross Apply is for readability...it'll hurt performance, but this doesn't seem to be a particularly process-intensive query, so I would keep it.
Please give it a try and let me know if you have any questions.
SELECT U.UserID,
ISNULL(SUM(CASE WHEN S.UserID IS NULL THEN 0 ELSE S.ratingPoints END), 0) AS [Rating Points],
ISNULL(SUM(CASE WHEN SS.userID IS NULL THEN 0 ELSE 20 END), 0) AS [Referral Points]
FROM Users U
LEFT OUTER JOIN scores S
ON S.userID = U.userID
AND S.scoreDateTime >= '2016-04-16 18:50:53.154'
LEFT OUTER JOIN locations L
ON S.locationID = L.locationID
AND L.locationArea = 'Massachusetts'
AND L.LocationCountry = 'United States'
LEFT OUTER JOIN Users U2
ON U2.referredByEmail = U.emailAddress
LEFT OUTER JOIN scores SS
ON SS.userID = U2.userID
LEFT OUTER JOIN locations LL
ON SS.locationID = LL.locationID
AND LL.locationArea = 'Massachusetts'
AND LL.locationCountry = 'United States'
AND SS.scoreDateTime >= '2016-04-16 18:50:53.154'
AND SS.scoreDateTime =
(
SELECT MIN(scoreDateTime)
FROM scores
where userID = U2.userID
)
GROUP BY U.userID
EDIT:
Modified answer to remove Cross Apply
Thanks Stan Shaw but I was unable to get your query to work on MySQL to test the results. However, I did notice a special case that was not covered by my original query. A user can get refer points from areas in which they themselves have not submitted scores. As long as the new user scores in that area, they get refer points there.
Here is the final query I'm using. I was not able to consolidate the duplicate where clauses in a way that appeared performant.
select userId, sum(points) as leaderPoints
from (
select s.userId, sum(s.ratingPoints) as points
from scores s, locations l
where s.locationId = l.locationId and
l.locationArea = 'Georgia' and
l.locationCountry = 'United States' and
s.scoreDateTime >= '2016-04-05 03:00:00.000' and
s.userId != 1
group by userId
union
select u1.userId, 20 as points
from users u0, users u1, users u2, scores s, locations l
where u0.userId = u1.userId and
u2.referredByEmail = u1.emailAddress and
u2.userId = s.userId and
s.locationId = l.locationId and
l.locationArea = 'Georgia' and
l.locationCountry = 'United States' and
scoreDateTime >= '2016-04-05 03:00:00.000' and
scoreDateTime = (
select min(scoreDateTime)
from scores
where userId = u2.userId
)
) as pointsEarned
group by userId
order by leaderPoints desc
limit 10
order by leaderPoints desc
limit 100;
I have written a query to get this data, but this data is based from today to 22-01-2016.
I want to write it in a way that it should show the values for January, then February as I want to see how much TotalCredit is being increased monthly.
Please view the data as image
SELECT
City,
Sum(Violations) AS TotalViolations,
SUM(USDSpent) AS 'TotalCredit(USD)'
FROM
(
SELECT
city. NAME AS City,
count(user_credit.booking_id) AS Violations,
Round(SUM(amount / usd_rate_to), 1) AS USDSpent,
user_credit.creation_date AS Date
FROM
user_credit
JOIN booking ON user_credit.booking_id = booking.id
JOIN booking_detail ON booking.id = booking_detail.booking_id
JOIN drivers ON booking_detail.assigned_driver_id = drivers.id
JOIN limo_company ON drivers.limo_company_id = limo_company.id
JOIN city ON limo_company.city_id = city.id
JOIN customer_car_type cct ON booking.customer_car_type_id = cct.id
JOIN service_area ON cct.service_area_id = service_area.id
JOIN currency_exchange ON user_credit.base_currency_exchange_id = currency_exchange.id
WHERE
user_credit.creation_date BETWEEN SUBDATE(CURDATE(), INTERVAL 1 MONTH)
AND NOW()
GROUP BY
Date
) AS tableA
GROUP BY City,month(creation_date)
The goal here is to:
1. Fetch the row with the most recent date from EACH store for EACH ingredient.
2. From this result, compare the prices to find the cheapest store for EACH ingredient.
I can accomplish either the first or second goal in separate queries, but not in the same.
How can i filter out a selection and then apply another filter on the previous result?
EDIT:
I've been having problems with results that i get from MAX and MIN since it just fetches the rest of the data arbitrarily. To avoid this im supposed to join tables on multiple columns (i guess). Im not sure how this will work with duplicate dates etc.
I've included an image of a query and its output data.
If we use ingredient1 as an example, it exists in three separate stores (in one store twice on different dates).
In this case the cheapest current price for ingredient1 would be store3. If the fourth row dated 2013-05-25 was even cheaper, it would still not "win" due to it being out of date.
(Disregard brandname, they dont really matter in this problem.)
Would appreciate any help/input you can offer!
This question is really interesting!
So, first, we get the row with the most recent date from EACH store for EACH ingredient. (It is possible that the most recent dates from each store can be different.)
Then, we compare the prices from each store (regardless of the date) to find the least price for each ingredient.
The query below uses the GROUP_CONCAT function in good measure. Here's a SO question regarding the use of the function.
SELECT
i.name as ingredient_name
, MIN(store_price.price) as price
, SUBSTRING_INDEX(
GROUP_CONCAT(store_price.date ORDER BY store_price.price),
',',
1
) as date
, SUBSTRING_INDEX(
GROUP_CONCAT(s.name ORDER BY store_price.price),
',',
1
) as store_name
, SUBSTRING_INDEX(
GROUP_CONCAT(b.name ORDER BY store_price.price),
',',
1
) as brand_name
FROM
ingredient i
JOIN
(SELECT
ip.ingredient_id as ingredient_id
, stip.store_id as store_id
, btip.brand_id as brand_id
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.ingredient_price_id ORDER BY ip.date DESC),
',',
1
), UNSIGNED INTEGER) as ingredient_price_id
, MAX(ip.date) as date
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.price ORDER BY ip.date DESC),
',',
1
), DECIMAL(5,2)) as price
FROM ingredient_price ip
JOIN store_to_ingredient_price stip ON ip.ingredient_price_id = stip.ingredient_price_id
JOIN brand_to_ingredient_price btip ON ip.ingredient_price_id = btip.ingredient_price_id
GROUP BY
ip.ingredient_id
, stip.store_id) store_price
ON i.ingredient_id = store_price.ingredient_id
JOIN store s ON s.store_id = store_price.store_id
JOIN brand b ON b.brand_id = store_price.brand_id
GROUP BY
store_price.ingredient_id;
You can check the implementation on this SQL Fiddle.
The version below, which ignores the brand, is slightly smaller:
SELECT
i.name as ingredient_name
, MIN(store_price.price) as price
, SUBSTRING_INDEX(
GROUP_CONCAT(store_price.date ORDER BY store_price.price),
',',
1
) as date
, SUBSTRING_INDEX(
GROUP_CONCAT(s.name ORDER BY store_price.price),
',',
1
) as store_name
FROM
ingredient i
JOIN
(SELECT
ip.ingredient_id as ingredient_id
, stip.store_id as store_id
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.ingredient_price_id ORDER BY ip.date DESC),
',',
1
), UNSIGNED INTEGER) as ingredient_price_id
, MAX(ip.date) as date
, CONVERT(SUBSTRING_INDEX(
GROUP_CONCAT(ip.price ORDER BY ip.date DESC),
',',
1
), DECIMAL(5,2)) as price
FROM ingredient_price ip
JOIN store_to_ingredient_price stip ON ip.ingredient_price_id = stip.ingredient_price_id
GROUP BY
ip.ingredient_id
, stip.store_id) store_price
ON i.ingredient_id = store_price.ingredient_id
JOIN store s ON s.store_id = store_price.store_id
GROUP BY
store_price.ingredient_id;
References:
Simulating First/Last aggregate functions in MySQL
This probably needs a couple of sub queries joined together.
This isn't tested (as I don't have your table definitions, nor any test data), but something like this:-
SELECT i.name AS ingredient,
ip.price,
ip.date,
s.name AS storename,
b.name AS brandname
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
INNER JOIN store s
ON store_to_ingredient_price.store_id = store.store_id
INNER JOIN brand_to_ingredient_price btip
ON ingredient_price.ingredient_price_id = brand_to_ingredient_price.ingredient_price_id
INNER JOIN brand b
ON brand_to_ingredient_price.brand_id = brand.brand_id
INNER JOIN
(
SELECT i.ingredient_id,
stip.store_id,
ip.date,
MIN(ip.price) AS lowest_price
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
INNER JOIN
(
SELECT i.ingredient_id,
stip.store_id,
MAX(ip.date) AS latest_date
FROM ingredient i
INNER JOIN ingredient_price ip
ON ingredient.ingredient_id = ingredient_price.ingredient_id
INNER JOIN store_to_ingredient_price stip
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
GROUP BY ingredient_id, store_id
) Sub1
ON i.ingredient_id = Sub1.ingredient_id
AND stip.store_id = Sub1.store_id
AND ip.date = Sub1.latest_date
GROUP BY i.ingredient_id, stip.store_id, ip.date
) Sub2
ON i.ingredient_id = Sub2.ingredient_id
AND stip.store_id = Sub2.store_id
AND ip.date = Sub2.date
AND ip.price = Sub2.lowest_price
Try this:
SELECT `newest`.ingredient, `newest`.store,
`newest`.brand, `newest`.price, `newest`.`latest_date`
FROM
(SELECT ingredient.name AS ingredient, store.name AS store,
brand.name AS brand, ingredient_price.price,
MAX( ingredient_price.date ) AS `latest_date`
FROM ingredient
LEFT OUTER JOIN ingredient_price
ON ingredient.ingredient_id = ingredient_price.ingredient_id
LEFT OUTER JOIN store_to_ingredient_price
ON ingredient_price.ingredient_price_id = store_to_ingredient_price.ingredient_price_id
LEFT OUTER JOIN store
ON store_to_ingredient_price.store_id = store.store_id
LEFT OUTER JOIN brand_to_ingredient_price
ON ingredient_price.ingredient_price_id = brand_to_ingredient_price.ingredient_price_id
LEFT OUTER JOIN brand
ON brand_to_ingredient_price.brand_id = brand.brand_id
GROUP BY ingredient.name) `newest`
ORDER BY `newest`.price
LIMIT 1
The project I'm working on right now needs a reference system (currently they have 50K members). I decided to add ref and ref_id field in members table.
Structure of members table;
id (int auto),
admin (enum (1,0)),
ref (enum (1,0)),
ref_id (int),
country_id (int),
city_id(int),
town_id(int),
totalRef (int),
fullName (varchar),
registrationDate (datetime)
I would like to list referers data which has new members between 2 dates. I wanted to provide a bit more details so I also tried to add country, city, and town in the query. I tried following query but I don't think this is a good approach to go with considering it takes really long time to load ;
SELECT m.id, m.fullName, m.country_id, m.city_id, m.town_id, m.totalRef,
(select name from country where country.id = m.country_id) as countryName,
(select name from city where city.id = m.city_id) as cityName,
(select name from town where town.id = m.town_id) as townName,
(select count(id) from members where members.ref_id = m.id AND ref_id > 0 AND registrationDate BETWEEN '2011.11.04 00:00:00' AND '2011.11.04 23:59:59') as newRef
FROM members as m
WHERE
m.country_id = '224' AND
m.city_id = '4567' AND
m.town_id = '78964' AND
m.admin = '0' AND
m.ref = '1'
ORDER BY newRef DESC
LIMIT 0, 25
I will be glad if you could help me about this problem. Thank you in advance.
Something like this -
SELECT
m.id,
m.fullName,
m.country_id,
m.city_id,
m.town_id,
m.totalRef,
cnt.name countryName,
ct.name cityName,
t.name townName,
m2.newRef
FROM members as m
LEFT JOIN country cnt
ON cnt.id = m.country_id
LEFT JOIN city ct
ON ct.id = m.city_id
LEFT JOIN town t
ON t.id = m.town_id
LEFT JOIN (
SELECT ref_id, COUNT(id) newRef FROM members
WHERE ref_id > 0 AND registrationDate BETWEEN '2011.11.04 00:00:00' AND '2011.11.04 23:59:59'
GROUP BY ref_id
) m2
ON m2.ref_id = m.id
WHERE
m.country_id = '224' AND
m.city_id = '4567' AND
m.town_id = '78964' AND
m.admin = '0' AND
m.ref = '1'
ORDER BY
newRef DESC
LIMIT
0, 25;