join two tables on match and no match - mysql

I Have two table
One is Catgory
Category Value
ABC 1
DEF 2
ALL 3
Second table Job
JOB Category
123 ABC
234 DEF
456 GGG
778 TTT
I need to add join these two table on Category. But I need Answer as below
JOB VALUE
123 1
234 2
456 3
778 3
Basically if category match then get value assigned to this. But if there is ALL in category column and have value then if category doesn't match then shows value for ALL for unmatched value as in example of job 456 and 778.

You can use two left joins:
select j.*, coalesce(c.value, c_default.value) as value
from job j left join
category c
on j.category = c.category left join
category c_default
on c_default.category = 'ALL';
You could also use a correlated subquery:
select j.*,
(select c.value
from category c
where c.category in ('ALL', j.category)
order by (c.category = j.category) desc
limit 1
) as value
from job j;

Use an outer join and coalesce
select job, Coalesce(c.value, (select value from category where category='ALL')) as value
from job j
left join category c on c.category=j.category
You could also use a window function to rank matching and non matching categories
with j as (
select j.job, c.value, Row_Number() over(partition by job order by case when c.category='all' then 1 else 0 end) rn
from job j
join category c on c.category=j.category or c.category='ALL'
)
select job,value
from j
where rn=1

Related

Merging MySQL database rows into columns

I have a database with the structure, where id and name are the key
id
name
cp
time
1
abc
1
10
1
abc
2
3
1
abc
3
12
2
xyx
1
12
2
xyx
2
11
2
xyx
2
13
and I need a query to merge it into a new table structure where its ID and name are only 1 row with the following structure, with time in each column value.
id
name
cp1
cp2
cp3
1
abc
10
3
12
2
xyz
12
11
13
Any help is appreciated thank you.
Assuming that you have a typo in your data and the 6th record should have a cp of 3, then you can use conditional aggregation:
SELECT t.id,
t.name,
MAX(CASE WHEN cp = 1 THEN t.time END) AS cp1,
MAX(CASE WHEN cp = 2 THEN t.time END) AS cp2,
MAX(CASE WHEN cp = 3 THEN t.time END) AS cp3
FROM T AS t
GROUP BY t.id, t.name;
If you are guaranteed to have one record per combination of id, name and cp then the MAX is largely irrelevant because you are taking the MAX of just one row, so it is deterministic. If you could have duplicates then you may need additional logic to determine which of the multiple records should be returned, or if you want to apply different aggregation (e.g. SUM).
Example on DB Fiddle
You can join your table 3 times:
SELECT m.id as id,
m.name as name,
m1.time as cp1,
m2.time as cp2,
m3.time as cp3
FROM mytable m -- This is the base table
LEFT JOIN mytable m1 ON -- This is a join to take cp1 if present
m.id = m1.id
AND m.name = m1.name
AND m1.cp = 1
LEFT JOIN mytable m2 ON -- This is a join to take cp2 if present
m.id = m2.id
AND m.name = m2.name
AND m2.cp = 2
LEFT JOIN mytable m3 ON -- This is a join to take cp3 if present
m.id = m3.id
AND m.name = m3.name
AND m3.cp = 3
GROUP BY m.id,
m.name,
m1.time,
m2.time,
m3.time
It has been joined 3 times to be sure that if you have any of cp1, cp2 or cp3 null it works. If you are sure that there are no rows absence for cp1 cp2 cp3 you can leave just 2 inner joins (instead of 3 left joins).
Note that this solution works with any relational database, not only mysql because there is no reference to special function of the database, but only standard SQL joins.
GROUP BY will summarize each id and name. And GROUP_CONCAT will give you the list of cp's. Here's a sample query.
SELECT id, name, GROUP_CONCAT(cp) as cps
FROM your_table
GROUP BY id, name

How to sum up two different table values

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

How to correctly separate this query

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

SQL subquery to return MIN of a column and corresponding values from another column

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.

Join two MySQL Tables and get result from categories

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)