Mysql row number with order by very slow - php

I have this query
select courses.id, y.qs_world, courses.name_en as name,
courses.description_en as description,
source_link, courses.slug, fee, duration, courses.university_id, college_id,
study_level_id, application_fee, courses.currency_id
from courses
left join university_ranks as y on courses.university_id = y.university_id
and y.year = '2021'
left join universities on courses.university_id = universities.id
left join countries on countries.id = universities.country_id where courses.status = 1
order by ROW_NUMBER() OVER (PARTITION BY countries.id ORDER BY courses.id)
This query is taking too long to execute, but it is working well if I remove the last row.
I used indexing but nothing different.
The EXPLAIN notes are to Using temporary,Using filesort but I want to improve the query without using temporary or filesort
How can I achieve this?
UPDATE:
I tried this query but same speed
SELECT * FROM (
SELECT
`courses`.`id`,`courses`.`status`, `y`.`qs_world`, `courses`.`name_en` as `name`, `courses`.`description_en` as `description`,
`source_link`, `courses`.`slug`, `fee`, `duration`, `courses`.`university_id`, `college_id`,
`study_level_id`, `application_fee`, `courses`.`currency_id`, `countries`.`id` as country_id
FROM
courses
left join `university_ranks` as `y` on `courses`.`university_id` = `y`.`university_id`
and `y`.`year` = '2021'
left join `universities` on `courses`.`university_id` = `universities`.`id`
left join `countries` on `countries`.`id` = `universities`.`country_id`
) UserCourse where status = 1
order by ROW_NUMBER() OVER (PARTITION BY country_id ORDER BY id)

countries.id as country_id --> universities.country_id
then remove
left join `countries` ON `countries`.`id` = `universities`.`country_id`
Move where status = 1 into the inner query.
It seems like
order by ROW_NUMBER() OVER (PARTITION BY country_id ORDER BY id)
could be replaced by
ORDER BY country_id, id
Get rid of the outer query
Don't say LEFT unless the 'right' table might have a missing row. (It confuses the reader as to your intent.)

Related

Can't find the correct SQL function ( SUM/COUNT - LEFT JOIN )

My issue with SQL function, is that I often mix SUM / COUNT, or even where I can put a WHERE clause and a GROUP BY, short version, SQL is one of my flaws.
Ok, now here's my current issue, I need to make a SQL function (in my PHP Model) that get the number of order I had "today" and their total cost, but I need to separate the result by country (sorry for my english) , for example the result I want should look like this (let's say I had 20 orders today) :
U.S : 15 orders (for a total of 288$) , CANADA : 5 orders (for a total of 94$)
I tried many things, and the only two that didn"t give me an error were these two (but far from the result I need) :
SELECT sum(o.amount) AS totalmoney, p.name, p.id, p.name_clean, p.iso_code
FROM orders AS o
LEFT JOIN customers AS c ON o.id_customer = c.id
LEFT JOIN countries AS p ON c.country = p.id
WHERE DATE(o.date_create) = CURDATE()
SELECT sum(o.amount) AS totalmoney, o.date_create, p.name, COUNT(p.id) AS totalpays,
p.name_clean, p.iso_code, c.country
FROM orders AS o
LEFT JOIN customers AS c ON o.id_customer = c.id
LEFT JOIN countries AS p ON c.country = p.id
GROUP BY o.date_create
My three tables are (table name : column usefull) :
countries : id - name
customers : id - country_id
orders : id - id_customer - amount ($) - date_create
Can you please help me?
Thank you for your time.
------------- EDIT WITH THE ANSWER I USED ---------------
Thanks to everyone who participate and helped me to have a better understanding of SQL.
SELECT sum(orders.amount) total, countries.name
FROM orders
INNER JOIN customers ON orders.id_customer = customers.id
INNER JOIN countries ON countries.id = customers.country_id
WHERE orders.date_create = ?
GROUP BY customers.id
It's exactly what I needed.
You’re wanting to group by country on a certain date, therefore your group by is country, and the where is the date:
SELECT sum(orders.amount) total, countries.name
FROM orders
INNER JOIN customers ON orders.id_customer = customers.id
INNER JOIN countries ON countries.id = customers.country_id
WHERE orders.date_create = ?
GROUP BY customers.id
Inner join is appropriate here, because the joins should only produce a single row for customer and country per order.
Try with that:
-- PostgreSQL
drop table orders;
drop table countries;
drop table customers;
CREATE TABLE countries
(
id integer not null,
name varchar(200)
);
insert into countries(id, name) values (1, 'MEXICO');
insert into countries(id, name) values (2, 'US');
insert into countries(id, name) values (3, 'CANADA');
create table customers
(
id integer not null,
name varchar(200),
country integer not null
);
insert into customers(id, name, country) values (1, 'Huey',1);
insert into customers(id, name, country) values (2, 'Dewey',2);
insert into customers(id, name, country) values (3, 'Louie',3);
create table orders
(
id integer not null,
id_customer integer not null,
amount double precision,
date_create date
);
insert into orders values (1, 1, 500.0, '20200103');
insert into orders values (2, 1, 1000, '20200103');
insert into orders values (3, 2, 500, '20200103');
insert into orders values (4, 3, 500, '20200103');
insert into orders values (5, 1, 500, '20200103');
SELECT COUNT(*) AS num_orders, sum(o.amount) AS totalmoney, p.name
FROM orders AS o
INNER JOIN customers AS c ON o.id_customer = c.id
INNER JOIN countries AS p ON c.country = p.id
WHERE DATE(o.date_create) = '20200103'
group by p.name
order by num_orders desc;
you can use over whith sum and count
select sum(amount) over(partition by contry_id) as totalamount,
count(contry_id) over(partition by contry_id) as totalcount,
name, ord.id
from orders ord left join customers cus on ord.customer_id = cus.id
left join countries con on cus.contry_id = con.id
where date_create = [date] --using string as a pseudo date when testing, so change is needed
here is a db<>fiddle with some pseudo data.
this will also get the data group by created date which match the OP need more imo.
select distinct sum(amount) over(partition by contry_id,date_create) as totalamount,
count(contry_id) over(partition by contry_id,date_create) as totalcount,
name, date_create
from orders ord left join customers cus on ord.customer_id = cus.id
left join countries con on cus.contry_id = con.id
pros with window function : over is that you can have different aggregate function with different 'condition' and don't need to group by every non aggregate column which usually lead to a lot of subqueries.
This should do what you want it to do:
SELECT
c.name,
sq.totalmoney
FROM
countries as c
JOIN
(SELECT
SUM(o.amount) AS totalmoney,
c.id
FROM
(SELECT
*
FROM
orders
WHERE
DATE(date_create) = CURDATE()
) AS o
JOIN
customers AS c
ON
o.id_customer = c.id
GROUP BY c.id) AS sq
on c.id = sq.id;

MySQL - Having problems with COUNT() in query with GROUP BY

I have a pretty big query which is used by an ajax call to return and also sort active items. From my understanding sub queries should be avoided where possible and since this query will be called very often, I would like to do just that.
At the moment everything is fine except for the COUNT(b.bic) AS bids. If there are two(2) bids the query returns four(4), if there are 4, it returns 8, and so on. I've tried grouping by other columns ... but no luck.
Some of the tables. I hope each of the column names are pretty self explanatory:
countries_ship - each item can be shipped to multiple countries so item_id can be duplicate.
id item_id country_id ship_cost
countries
id country_code country_name
item_expire - not sure if item_expire should have it's own table.
id item_id exp_date
bids - Just as countries_ship, item_id can be duplicate. This is where the bids are stored.
id item_id user_id bid previous_bid bid_date
The query:
$q = $this->db->mysqli->prepare("
SELECT c.ship_cost,
c.item_id,
co.country_name,
co.id AS co_id,
i.id,
i.user_id,
i.item_start,
i.item_title,
i.item_number,
i.item_year,
i.item_publisher,
i.item_condition,
i.item_description,
i.item_location,
e.exp_date AS exp_date,
i.active,
CAST(u.fb_id AS CHAR(50)) AS fb_id,
u.user_pic,
MAX(b.bid) AS maxbid,
COUNT(b.bid) AS bids,
p.publisher_name,
t.tag_name
FROM countries_ship c
JOIN items i
ON c.item_id = i.id
JOIN item_expire e
ON c.item_id = e.item_id
JOIN users u
ON i.user_id = u.id
LEFT JOIN bids b
ON i.id = b.item_id
LEFT JOIN publishers p
ON i.item_publisher = p.id
LEFT JOIN tags_rel tr
ON c.item_id = tr.item_id
JOIN tags t
ON t.id = tr.tag_id
LEFT JOIN countries co
ON i.item_location = co.id
WHERE ".$where."
GROUP BY c.item_id ORDER BY ".$order." ".$limit."");
You may try
COUNT(distinct b.bic) AS bids
This will ignore duplicates due to joins

MySql : left join users table with transactions table return several rows

I have 2 tables : users and paypal_transactions
For each user, we have an id (named user_id in paypal_transactions table)
A user may have several paypal_transactions. Relation one to many
I need to grab the latest transaction id (ordered by date_dt DESC) when I do my query
My current query :
SELECT `Transaction`.*, `User`.*, `Tipster`.`username`
FROM `pronostics_framework`.`users` AS `User`
LEFT JOIN `pronostics_framework`.`users` AS `Tipster` ON (`User`.`tipster_id` = `Tipster`.`id`)
LEFT JOIN `pronostics_framework`.`paypal_transactions` AS `Transaction` ON (`User`.`id` = `Transaction`.`user_id`)
ORDER BY `User`.`id` DESC
LIMIT 500
Currently with one transaction per user, it works fine. BTW with many transactions I still get the 1st entry from paypal_transactions table (the oldest, but I want the latest from now).
I did many tries, without success.
Thanks for your help !
Here you go:
SELECT `Transaction`.*, `User`.*, `Tipster`.`username`
FROM `pronostics_framework`.`users` AS `User`
LEFT JOIN `pronostics_framework`.`users` AS `Tipster` ON (`User`.`tipster_id` = `Tipster`.`id`)
LEFT JOIN (SELECT user_id, MAX(date_dt) AS max_date
FROM `pronostics_framework`.paypal_transactions
GROUP BY user_id) AS max_trans
ON User.id = max_trans.user_id
LEFT JOIN `pronostics_framework`.`paypal_transactions` AS `Transaction`
ON (max_trans.user_id = `Transaction`.`user_id` AND max_trans.max_date = Transation.date_dt)
ORDER BY `User`.`id` DESC
LIMIT 500
Another way, based on the first query in the first answer at Retrieving the last record in each group:
SELECT `Transaction`.*, `User`.*, `Tipster`.`username`
FROM `pronostics_framework`.`users` AS `User`
LEFT JOIN `pronostics_framework`.`users` AS `Tipster` ON (`User`.`tipster_id` = `Tipster`.`id`)
LEFT JOIN `pronostics_framework`.`paypal_transactions` AS `Transaction` ON Transaction.user_id = User.id
LEFT JOIN `pronostics_framework`.paypal_transactions AS Transactions1
ON Transactions1.user_id = Transactions.user_id AND Transactions1.user_id > Transactions.user_id
WHERE Transactions1.user_id IS NULL
ORDER BY `User`.`id` DESC
LIMIT 500

mysql query become slow when data reached upto 100000

I build a query a month ago on a website. It was working fine. But after a month I was informed that the website become very slow to load the page.
When I search for the problem, I found that my query is executing very slow to fetch the data from mysql database. Then I check for the database and found that the 4 tables which I was using by joins, have around 216850, 167634, 64000, 931 rows respectively.
I have already have indexed that tables. So, where I'm lacking. Please help guys.
[Edit]
Table1: user_alert
Records: 216850
DB Type: InnoDB
Indexes: id(primary)
Table2: orders
Records: 167634
DB Type: InnoDB
Indexes: id(primary), order_id, customer_id
Table3: user_registration
Records: 64000 around
DB Type: InnoDB
Indexes: id(primary), email_address
Table4: cities
Records: 931
DB Type: InnoDB
Indexes: id(primary)
Query:
SELECT uas.alert_id, uas.user_id, uas.status, ur.first_name, ur.last_name, ur.email_address, o.order_id,
CASE WHEN ct.city_name IS NULL THEN uas.city_name ELSE ct.city_name END AS city_name
FROM `user_alert` uas
LEFT JOIN orders o ON o.customer_id = uas.user_id
LEFT JOIN user_registration ur ON ur.id = uas.user_id
LEFT JOIN `cities` ct ON ct.city_id = uas.city_id
WHERE uas.status = '1'
GROUP BY uas.user_id
ORDER BY uas.create_date DESC
GROUP BY is used to aggregate values up. For example if you wanted the count of orders by a user you could use COUNT(o.order_id).....GROUP BY uas.user_id. There are multiple orders for each user, but the aggregate function is just counting them here. However if you just select o.order_id when you have a GROUP BY uas.user_id it doesn't know which of the possibly many order_id values to return for that user id.
In this case it possibly doesn't matter as it looks like the order table is the only one where there is multiple rows per use. If you want the latest one you could just use MAX(o.order_id) (assuming that the order_id is assigned is order). But if you wanted the order value it becomes more difficult.
SELECT uas.alert_id, uas.user_id, uas.status, ur.first_name, ur.last_name, ur.email_address, MAX(o.order_id) AS LatestOrderId,
IFNULL(ct.city_name, uas.city_name) AS city_name
FROM `user_alert` uas
LEFT JOIN orders o ON o.customer_id = uas.user_id
LEFT JOIN user_registration ur ON ur.id = uas.user_id
LEFT JOIN `cities` ct ON ct.city_id = uas.city_id
WHERE uas.status = '1'
GROUP BY uas.user_id
ORDER BY uas.create_date DESC
If you wanted the (say) value of the latest order then it becomes more difficult.
SELECT uas.alert_id, uas.user_id, uas.status, ur.first_name, ur.last_name, ur.email_address, Sub1.MaxOrderId AS LatestOrderId, o.order_value
IFNULL(ct.city_name, uas.city_name) AS city_name
FROM `user_alert` uas
LEFT JOIN (SELECT customer_id, MAX(order_id) AS MaxOrderId FROM orders GROUP BY customer_id) Sub1 ON Sub1.customer_id = uas.user_id
LEFT OUTER JOIN orders o ON o.customer_id = Sub1.user_id AND o.order_id = Sub1.MaxOrderId
LEFT JOIN user_registration ur ON ur.id = uas.user_id
LEFT JOIN `cities` ct ON ct.city_id = uas.city_id
WHERE uas.status = '1'
ORDER BY uas.create_date DESC
Or doing a bit of a fiddle based on GROUP_CONCAT
SELECT uas.alert_id, uas.user_id, uas.status, ur.first_name, ur.last_name, ur.email_address,
SUBSTRING_INDEX(GROUP_CONCAT(o.order_id ORDER BY o.order_id DESC), ',', 1) AS LatestOrderId,
SUBSTRING_INDEX(GROUP_CONCAT(o.order_value ORDER BY o.order_id DESC), ',', 1) AS LatestOrderValue,
IFNULL(ct.city_name, uas.city_name) AS city_name
FROM `user_alert` uas
LEFT OUTER JOIN orders o ON o.customer_id = uas.user_id AND o.order_id = Sub1.MaxOrderId
LEFT JOIN user_registration ur ON ur.id = uas.user_id
LEFT JOIN `cities` ct ON ct.city_id = uas.city_id
WHERE uas.status = '1'
GROUP BY uas.user_id
ORDER BY uas.create_date DESC

Mysql GROUP BY and ORDER BY DESC [duplicate]

This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Closed 9 years ago.
This is my full query:
SELECT * FROM `clientgroupassign`
LEFT JOIN `clients` ON `clientgroupassign`.clientId = `clients`.clientId
LEFT JOIN `users` ON `carerId` = `userId`
LEFT JOIN
(SELECT * FROM
(SELECT * FROM `contacts` WHERE `contactGroup` = 4 ORDER BY `contactId` DESC)
as `contacts`
GROUP BY (`contactClientId`)
) AS `contacts` ON `contactClientId` = `clients`.clientId
WHERE groupId = 4
ORDER BY `clients`.clientId
There is a problem with third join causing the script to execute for about 1 minute. When I run it separattly in PMA:
SELECT * FROM (SELECT * FROM `contacts` WHERE `contactGroup` = 4 ORDER BY `contactId` DESC) AS `contacts` GROUP BY (`contactClientId`)
it still gets very long to execute.
What I want is to get one, last added row from contacts for each client who is in the group 4(client can be in various groups).
Thanks.
To get "last added row from contacts for each client who is in the group 4" try this:
SELECT
c.*
FROM(
SELECT
contactClientId,
MAX(contactId) as cid
FROM
contacts
WHERE
contactGroup = 4
GROUP BY
contactClientId
ORDER BY
NULL
) as tmp
INNER JOIN contacts as c
ON c.contactId = tmp.cid
AND c.contactClientId = tmp.contactClientId
If contactId is PK in contacts then the second join clause is not needed.
By default, MySQL sorts all GROUP BY col1, col2, ... queries as if you
specified ORDER BY col1, col2, ... in the query as well. If you
include an ORDER BY clause explicitly that contains the same column
list, MySQL optimizes it away without any speed penalty, although the
sorting still occurs. If a query includes GROUP BY but you want to
avoid the overhead of sorting the result, you can suppress sorting by
specifying ORDER BY NULL.
The complete docs.
Full query:
SELECT * FROM `clientgroupassign`
LEFT JOIN `clients` ON `clientgroupassign`.clientId = `clients`.clientId
LEFT JOIN `users` ON `carerId` = `userId`
LEFT JOIN
(
SELECT
c.*
FROM(
SELECT
contactClientId,
MAX(contactId) as cid
FROM
contacts
WHERE
contactGroup = 4
GROUP BY
contactClientId
ORDER BY
NULL
) as tmp
INNER JOIN contacts as c
ON c.contactId = tmp.cid
AND c.contactClientId = tmp.contactClientId
) AS `contacts` ON `contactClientId` = `clients`.clientId
WHERE groupId = 4
ORDER BY `clients`.clientId

Categories