I'm trying to sort through my table to find the frequent categories in my orders. After conducting this query
SELECT
ccd.cart_id,
mp.category_name,
ccd.quantity
FROM
customer_orders co
JOIN customer_cart_dtls ccd
ON co.order_cart = ccd.cart_id
JOIN merchant_products mp
ON ccd.product_id = mp.product_id
which yields this result
So from that query Cart #2006........63 has 9 items. 1 from eatables, 3 From fruits, 2 From cleaning, and 3 from Snacks. All of them quantity 1 except for the second entry of cleaning which has two. How can I alter my query so that I get 10 items all with quantity 1?
Which would look like this
You want to split the individual rows into multiple rows. One method uses recursive CTEs:
WITH RECURSIVE t as (
SELECT ccd.cart_id, mp.category_name, ccd.quantity
FROM customer_orders co JOIN
customer_cart_dtls ccd
ON co.order_cart = ccd.cart_id JOIN
merchant_products mp
ON ccd.product_id = mp.product_id
),
cte as (
SELECT cart_id, category_name, quantity, 1 as n
FROM t
UNION ALL
SELECT cart_id, category_name, quantity, n + 1
FROM cte
WHERE n < quantity
)
SELECT cart_id, category_name, 1 as quantity
FROM cte;
Here is a db<>fiddle.
EDIT:
You can join in a list of quantities -- easier if you have a tally table of some sort:
SELECT ccd.cart_id, mp.category_name, 1 as quantity
FROM customer_orders co JOIN
customer_cart_dtls ccd
ON co.order_cart = ccd.cart_id JOIN
merchant_products mp
ON ccd.product_id = mp.product_id JOIN
(SELECT 1 as n UNION ALL
SELECT 2 as n UNION ALL
SELECT 3 as n UNION ALL
SELECT 4 as n UNION ALL
SELECT 5 as n
) n
ON n.n <= ccd.quantity;
You can also construct the table using variables from an existing table (if it is big enough):
(select (#rn := #rn + 1) as n
from customer_orders cross join
(select #rn := 0) params
limit 100 -- say that 100 is big enough
) n
Are you trying to count how many items come from each category by splitting every item into an individual row and then using COUNT? If so, I don't think you necessarily need to go down that route. It will likely be a lot easier to simply use the SUM aggregate function after grouping by category_name. It might look something like this:
SELECT mp.category_name, SUM(ccd.quantity)
FROM customer_orders AS co
JOIN customer_card_dtls AS ccd ON co.order_cart = ccd.cart_id
JOIN merchant_products AS mp ON ccd.product_id = mp.product_id
GROUP BY mp.category_name
If you want to also see cart IDs then just add the appropriate columns to your SELECT and GROUP BY statements
Related
How can I select the ID of a row with the max value of another column in a query that joins multiple tables?
For example, say I have three tables. tblAccount which stores a grouping of users, like a family. tblUser which stores the users, each tied to a record from tblAccount. And each user can be part of a plan, stored in tblPlans. Each plan has a Rank column that determines it's sorting when comparing the levels of plans. For example, Lite is lower than Premium. So the idea is that each user can have a separate plan, like Premium, Basic, Lite etc..., but the parent account does not have a plan.
How can I determine the highest plan in the account with a single query?
tblAccount
PKID
Name
1
Adams Family
2
Cool Family
tblUsers
PKID
Name
AccountID
PlanID
1
Bob
1
3
2
Phil
2
2
3
Suzie
2
1
tblPlans
PKID
Name
Rank
1
Premium
3
2
Basic
2
3
Elite
4
4
Lite
1
Here's the result I'm hoping to produce:
AccountID
Name
HighestPlanID
PlanName
2
Adams Family
1
Premium
I've tried:
SELECT U.AccountID, A.Name, MAX(P.Rank) AS Rank, P.PKID as HighestPlanID, P.Name as PlanName
FROM tblPlans P
INNER JOIN tblUsers U ON U.PlanID = P.PKID
INNER JOIN tblAccounts A ON U.AccountID = A.PKID
WHERE U.AccountID = 2
and the query will not always work, selecting the MAX of Rank does not select entire row's values from tblPlans.
I am looking for a solution that is compatible with mysql-5.6.10
You can join the tables and use ROW_NUMBER() to identify the row you want. Then filtering is ieasy.
For example:
select *
from (
select a.*, p.*,
row_number() over(partition by a.pkid order by p.rank desc) as rn
from tblaccount a
join tblusers u on u.accountid = a.pkid
join tblplans p on p.pkid = u.planid
) x
where rn = 1
Inside the subquery you can add where u.accountid = 2 to retrieve a single account of interest, instead of all of them.
With the help of #the-impaler, I massaged their answer a bit and came out with something very similar:
select *
from (
select a.*, p.*
from tblaccount a
join tblusers u on u.accountid = a.pkid
join tblplans p on p.pkid = u.planid
where u.accountid = 2
order by p.rank desc
) x limit 1
The subquery sorts each user by plan rank from top to bottom, and then the top level query selects the top most row with limit 1. It seems to work!
I have two tables:
data
id[int] balance[float] category[id]
1 10.2 1
2 0.12 2
3 112.42 1
4 2.3 3
categories
id[int] name[varchar] start_at[float]
1 high 10.5
2 low 105.2
3 mid 0.7
I want to query the categories and join the data. For each categorie I want the sum of all data balances added to the start_at value of categories:
This is where I started with:
select sum(d.balance) as balancesum, c.name
from data d
left join categories c on c.id = d.category
group by d.category
What I want to know is, how can I add the start_at value of categories to the balancesum value?
SELECT c.name, c.start_at + SUM(d.balance) as balancesum
FROM categories c
JOIN data d ON c.id = d.category
GROUP BY c.name, c.start_at
You can use next approach:
select
c.name, balancesum, ifnull(balancesum, 0) + start_at
from categories c
left join (
-- calculate sum of balances per category
-- and join sums to data table
select category, sum(d.balance) as balancesum
from data d
group by d.category
) b on b.category = c.id;
Here you can play with live query
My tables looks like this
sales
----------------------------------------------------------
id ordernumber quantity category_id price
1 402-9182243-8008368 1 3 22.95
2 406-3666671-8627555 2 3 6.95
3 303-1935495-5532309 1 1 7.95
4 171-5799800-1198702 1 2 159.95
5 403-2398078-4901169 2 2 18.95
category
--------------
id name
1 bikes
2 shoes
3 planes
returns
--------------
id ordernumber quantity costs
1 402-9182243-8008368 1 22.95
2 402-9182243-8008368 5.95 // return shipping fee
And here is my query
SELECT c.name,
SUM(v.quantity) AS sold, # wrong
SUM(s.quantity * s.price) AS turnover, # wrong
SUM(r.costs) AS returncosts,
FROM sales AS s
INNER JOIN categories AS c ON c.id = s.category_id
LEFT JOIN returns AS r ON r.ordernumber = s.ordernumber
GROUP BY c.name
I have some inner joins with aggregate functions.
But I also need "return" with a "Left Join" (I think).
And with Left Join, my aggregate functions dont work anymore.
Left Join adds additional rows. Additional data, for sum().
I need a single query, so every column is sortable later.
I would be happy about any help. Best Regards
It's a semi cartesian product because ordernumber is not unique in returns table.
We can see what's happening if we remove the aggregate functions and return the detail rows.
One possible approach is to pre-aggregate returns in an inline view, so that unique values of ordernumber are returned.
Assuming ordernumber is unique in sales table, then something like this:
SELECT c.name
, SUM(s.quantity) AS sold
, SUM(s.quantity * s.price) AS turnover
, SUM(r.returncosts) AS returncosts
FROM sales s
JOIN categories c
ON c.id = s.category_id
LEFT
JOIN ( SELECT t.ordernumber
, SUM(t.costs) AS returncosts
FROM returns t
GROUP
BY t.ordernumber
) r
ON r.ordernumber = s.ordernumber
GROUP
BY c.name
You can sum the quantity separately from the LEFT JOIN in a sub query as follows:
SELECT t1.name, t1.sold, t1.turnover, SUM(r.costs) AS returncosts
FROM(
SELECT c.name,
SUM(s.quantity) AS sold,
SUM(s.quantity * s.price) AS turnover
FROM sales AS s
INNER JOIN categories AS c ON c.id = s.category_id
GROUP BY name
) t1
LEFT JOIN returns AS r ON r.ordernumber = s.ordernumber
GROUP BY t1.name, t1.sold, t1.turnover
I'm trying to query
number of courses passed,
the earliest course passed
time taken to pass first course, for each student who is not currently expelled.
The tricky part here is 2). I constructed a sub-query by mapping the course table onto itself but restricting matches only to datepassed=min(datepassed). The query appears to work for a very sample, but when I try to apply it to my full data set (which would return ~1 million records) the query takes impossibly long to execute (left it for >2 hours and still wouldn't complete).
Is there a more efficient way to do this? Appreciate all your help!
Query:
SELECT
S.id,
COUNT(C.course) as course_count,
C2.course as first_course,
DATEDIFF(MIN(C.datepassed),S.dateenrolled) as days_to_first
FROM student S
LEFT JOIN course C
ON C.studentid = S.id
LEFT JOIN (SELECT * FROM course GROUP BY studentid HAVING datepassed IN (MIN(datepassed))) C2
ON C2.studentid = C.studentid
WHERE YEAR(S.dateenrolled)=2013
AND U.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
ORDER BY S.id
Student table
id status dateenrolled
1 graduated 1/1/2013
3 graduated 1/1/2013
Expelled table
id dateexpelled
2 5/1/2013
Course table
studentid course datepassed
1 courseA 5/1/2014
1 courseB 1/1/2014
1 courseC 2/1/2014
1 courseD 3/1/2014
3 courseA 1/1/2014
3 couseB 2/1/2014
3 courseC 3/1/2014
3 courseD 4/1/2014
3 courseE 5/1/2014
SELECT id, course_count, days_to_first, C2.course first_course
FROM (
SELECT S.id, COUNT(C.course) course_count,
DATEDIFF(MIN(datepassed),S.dateenrolled) as days_to_first,
MIN(datepassed) min_datepassed
FROM student S
LEFT JOIN course C ON C.studentid = S.id
WHERE S.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND S.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
) t1 LEFT JOIN course C2
ON C2.studentid = t1.id
AND C2.datepassed = t1.min_datepassed
ORDER BY id
I would try something like:
SELECT s.id, f.course,
COALESCE( DATEDIFF( c.first_pass,s.dateenrolled), 0 ) AS days_to_pass,
COALESCE( c.num_courses, 0 ) AS courses
FROM student s
LEFT JOIN
( SELECT studentid, MIN(datepassed) AS first_pass, COUNT(*) AS num_courses
FROM course
GROUP BY studentid ) c
ON s.id = c.studentid
JOIN course f
ON c.studentid = f.studentid AND c.first_pass = f.datepassed
LEFT JOIN expelled e
ON s.id = e.id
WHERE s.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND e.id IS NULL
This query assumes a student can pass only one course on a given day, otherwise you can get more than one row for a student as its possible to have many first courses.
For performance it would help to have an index on dateenrolled in student table and a composite index on (studentid,datepassed) in courses table.
SELECT art.*,arg. FROM rd_articles AS art
LEFT JOIN rd_argument AS arg ON art.cat=arg.id WHERE art.enabled=1 ORDER BY art.id DESC
LIMIT 10
This is simple join query
Article table structure is
ID cat Description Date
1 1 Abc 08-01-2014
2 1 Aaa 10-01-2014
3 2 Abcv 11-01-2014
4 3 Aaa 12-01-2014
5 3 Aaa 14-01-2014
Arguments table is
ID Name
1 A
2 B
3 C
I want pick last updated(Date) one item from each cat.
How ?
This assumes that the enabled column is in rd_articles:
SELECT art.*, arg.*
FROM (
SELECT * FROM rd_articles
INNER JOIN (
SELECT cat, MAX(date) AS maxdate
FROM rd_articles
WHERE enabled = 1
GROUP BY cat
) md ON rd_articles.cat = md.cat AND rd_articles.date = md.maxdate
) art
LEFT JOIN rd_argument AS arg ON art.cat = arg.id
The innermost query gets the maximum date for each category, then joins it to the rd_articles table to get only those rd_articles rows that have the latest date for each article. That becomes the cat alias, which is then left-joined to the arguments table just like in your original query. You can add the LIMIT 10 at the end if needed; I wasn't sure what to do with that.
Note that if there's a tie for a category's latest date, you'll get more than one row for each category. If a tie could happen you'll need to break the tie somehow, for example by using the description or the ID. Let me know if that's the case and I'll update my answer.
SELECT ART.*, ARG.*
FROM ARTICLE AS ART
INNER JOIN RD_AGRUEMENT AS ARG
ON ARG.ID = ART.ID
WHERE (ID, DATE) IN
(SELECT ID, MAX(DATE) FROM ARTICLE GROUP BY ID)