Select / group by multiple columns but count just the values of one column - sql

I have 2 tables.
Table #1: orders
order_id | crit_1 | crit_2 | crit_3 | other
01 | A00 | GER | 49er | x
02 | A00 | GER | 49er | x
03 | A00 | USA | 49er | x
04 | C80 | DEN | 66er | x
05 | B50 | GER | 99er | x
The table orders has 3 important criteria but doesn't have the criterion_4. There is another table with the order_positions which contains multiple criterion_4 entries for each order_id.
Table #2: classifications
crit_1 | crit_2 | crit_3 | crit_4 | class_1 | class_2
A00 | GER | 49er | 4711 | A | 11
A00 | GER | 49er | 4712 | A | 21
A00 | USA | 49er | 4711 | D | 12
A00 | USA | 49er | 4712 | D | 21
B50 | GER | 99er | 4801 | B | 12
B50 | GER | 99er | 4802 | B | 12
B50 | GER | 99er | 4803 | B | 14
C80 | DEN | 66er | 4904 | C | 22
C80 | DEN | 66er | 4905 | C | 21
The table classifications contains classifications for:
orders = class_1 = combination of crit_1, crit_2 & crit_3
order_positions = class_2 = combination of crit_1, crit_2, crit_3
& crit_4
I have a query where I join classifications.class_1 on the table orders to create a list of all orders and their respective classification.
select
orders.order_id,
orders.crit_1,
orders.crit_2,
orders.crit_3,
classifications.class_1
from
orders
left join
classifications
on
orders.crit_1=classifications.crit_1 and
orders.crit_2=classifications.crit_2 and
orders.crit_3=classifications.crit_3
where
orders.others = "..."
group by
orders.order_id,
orders.crit_1,
orders.crit_2,
orders.crit_3,
classifications.class_1
I need a GROUP BY at the end since the table classifications contains multiple entries with the combination of crit_1, crit_2 and crit_3. But this isn't a problem since the needed classification_1 is always the same for each combination of crit_1, crit_2 and crit_3.
Now I want to create another query where I count just the number of each classification_1 for the orders. Something like this:
class_1 | number
A | 12
B | 5
C | 18
. | .
But I don't know how without the whole selection of orders.order_id, orders.crit_1, orders.crit_2, orders.crit_3 and classifications.class_1
I just want to count the class_1 classifications for the query above.
Any suggestions?
edit
I tried it like suggested by Kaushik Nayak:
select
--orders.order_id,
--orders.crit_1,
--orders.crit_2,
--orders.crit_3,
classifications.class_1,
count(*)
from
orders
left join
classifications
on
orders.crit_1=classifications.crit_1 and
orders.crit_2=classifications.crit_2 and
orders.crit_3=classifications.crit_3
where
orders.others = "..."
group by
--orders.order_id,
--orders.crit_1,
--orders.crit_2,
--orders.crit_3,
classifications.class_1
But the results are not correct and I have no idea how to reproduce those numbers.
A few examples:
| class_1 | query w/ | query w/o | query |
| | group by | group by | count(*) |
---------------------------------------------
| A | 654 | 2179 | 1024 |
| B | 371 | 1940 | 667 |
| C | 94 | 238 | 247 |
When I use my query with group by then I get 654 entries for class_1 = A.
When I make my query without group bythen I get 2179 entries for class_1= A.
And when I try the query with Count(*) then I get 1024 entries for class_1 = A.
The last one is definitely not correct.

Just use GROUP BY class_1 for your classifications table and add an EXISTS condition to check if there is an order.
SELECT
c.class_1,
COUNT(c.class_1) "number"
FROM
classifications c
WHERE
EXISTS (
SELECT
1
FROM
orders o
WHERE
o.crit_1 = c.crit_1
AND o.crit_2 = c.crit_2
AND o.crit_3 = c.crit_3
)
GROUP BY
c.class_1
ORDER BY
1;

Related

select count of sold products with 2 attributes on different rows

I am trying to generate a report of every product sold of SKUABC in size 34 with inseam 33 (it is available in 33 and 31 inseam).
Table - orders_products
Table - Orders:
+-----------+------------------------+--+
| Orders_id | date_purchased | |
+-----------+------------------------+--+
| 46198 | 2020-10-18 19:43:25 | |
| 46199 | 2020-10-19 19:43:25 | |
| 46200 | 2020-10-22 19:43:25 | |
+-----------+------------------------+--+
Table - orders_products
+--------------------+-----------+-------------+----------------+--+
| orders_products_id | Orders_id | products_id | products_mode | QTY
+--------------------+-----------+-------------+----------------+--+
| 42154907 | 46198 | 878 | SKUABC |1 |
| 42154908 | 46198 | 878 | SKUABC |1 |
| 42154909 | 46198 | 282 | DIFFSKU |1 |
+--------------------+-----------+-------------+----------------+--+
Table - Orders_products_attributes (showing order_id 46198 only):
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
| orders_products_attribute_id | orders_id | orders_products_id | Product options | Product_options_value | |
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
| 167618 | 46198 | 42155189 | Color | Green | |
| 167619 | 46198 | 42155189 | Inseam | 33 | |
| 167620 | 46198 | 42155189 | Size | 34 | |
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
my sql so far:
SELECT distinct o.orders_id, op.products_model, opa.products_options_values, sum(op.products_quantity)
FROM orders o
LEFT JOIN orders_products op
ON o.orders_id = op.orders_id
LEFT JOIN orders_products_attributes opa
on op.orders_id = opa.orders_id
WHERE op.products_model in ('SKUABC')
and opa.`products_options_values` in ('36')
and o.date_purchased > '2020-10-13'
If I add in :
and opa.`products_options_values` in ('31')
it returns no results, the reason being because the inseam and size rows are separate. and the problem with the above code is that it is combining any orders/ordered products where the inseam is both 33 or 31 but I want it to be separate.
My desired out would be
+--------+------------+------------+-------------------+
| model | attribute1 | attribute2 | quantity sold sum |
+--------+------------+------------+-------------------+
| ABCSKU | 34 | 33 | 120 |
+--------+------------+------------+-------------------+
Here is a fun solution: select two products_options_values and label those in different name then everything will be easy
SELECT distinct o.orders_id, op.products_model, opa.products_options_values
AS Inseem,opa2.products_options_values AS Size, sum(op.products_quantity)
FROM orders o
LEFT JOIN orders_products op
ON o.orders_id = op.orders_id
LEFT JOIN orders_products_attributes opa
ON op.orders_id = opa.orders_id
LEFT JOIN orders_products_attributes opa2
ON op.orders_id = opa.orders_id
--your condition below
then just use opa for inseem and opa2 for size.It is stupid but work.You can even make the rows data null by adding some condition with Product option column for easier insert later.

SQL - joining 3 tables and choosing newest logged entry per id

I got rather complicated riddle to solve. So far I'm unlocky.
I got 3 tables which I need to join to get the result.
Most important is that I need highest h_id per p_id. h_id is uniqe entry in log history. And I need newest one for given point (p_id -> num).
Apart from that I need ext and name as well.
history
+----------------+---------+--------+
| h_id | p_id | str_id |
+----------------+---------+--------+
| 1 | 1 | 11 |
| 2 | 5 | 15 |
| 3 | 5 | 23 |
| 4 | 1 | 62 |
+----------------+---------+--------+
point
+----------------+---------+
| p_id | num |
+----------------+---------+
| 1 | 4564 |
| 5 | 3453 |
+----------------+---------+
street
+----------------+---------+-------------+
| str_id | ext | name |
+----------------+---------+-------------+
| 15 | | Mein st. 33 | - bad name
| 11 | | eck st. 42 | - bad name
| 62 | abc | Main st. 33 |
| 23 | efg | Back st. 42 |
+----------------+---------+-------------+
EXPECTED RESULT
+----------------+---------+-------------+-----+
| num | ext | name |h_id |
+----------------+---------+-------------+-----+
| 3453 | efg | Back st. 42 | 3 |
| 4564 | abc | Main st. 33 | 4 |
+----------------+---------+-------------+-----+
I'm using Oracle SQL. Tried using query below but result is not true.
SELECT num, max(name), max(ext), MAX(h_id) maxm FROM history
INNER JOIN street on street.str_id = history._str_id
INNER JOIN point on point.p_id = history.p_id
GROUP BY point.num
In Oracle, you can use keep:
SELECT p.num,
MAX(h.h_id) as maxm,
MAX(s.name) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as name,
MAX(s.ext) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as ext
FROM history h INNER JOIN
street s
ON s.str_id = h._str_id INNER JOIN
point p
ON p.p_id = h.p_id
GROUP BY p.num;
The keep syntax allows you to do "first()" and "last()" for aggregations.

How to print the students name in this query?

The concerned tables are as follows:
students(rollno, name, deptcode)
depts(deptcode, deptname)
course(crs_rollno, crs_name, marks)
The query is
Find the name and roll number of the students from each department who obtained
highest total marks in their own department.
Consider:
i) Courses of different department are different.
ii) All students of a particular department take same number and same courses.
Then only the query makes sense.
I wrote a successful query for displaying the maximum total marks by a student in each department.
select do.deptname, max(x.marks) from students so
inner join depts do
on do.deptcode=so.deptcode
inner join(
select s.name as name, d.deptname as deptname, sum(c.marks) as marks from students s
inner join crs_regd c
on s.rollno=c.crs_rollno
inner join depts d
on d.deptcode=s.deptcode
group by s.name,d.deptname) x
on x.name=so.name and x.deptname=do.deptname group by do.deptname;
But as mentioned I need to display the name as well. Accordingly if I include so.name in select list, I need to include it in group by clause and the output is as below:
Kendra Summers Computer Science 274
Stewart Robbins English 80
Cole Page Computer Science 250
Brian Steele English 83
expected output:
Kendra Summers Computer Science 274
Brian Steele English 83
Where is the problem?
I guess this can be easily achieved if you use window function -
select name, deptname, marks
from (select s.name as name, d.deptname as deptname, sum(c.marks) as marks,
row_number() over(partition by d.deptname order by sum(c.marks) desc) rn
from students s
inner join crs_regd c on s.rollno=c.crs_rollno
inner join depts d on d.deptcode=s.deptcode
group by s.name,d.deptname) x
where rn = 1;
To solve the problem with a readable query I had to define a couple of views:
total_marks: For each student the sum of their marks
create view total_marks as select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno;
dept_max: For each department the highest total score by a single student of that department
create view dept_max as select deptcode, max(total) max_total from total_marks group by deptcode;
So I can get the desidered output with the query
select a.deptcode, a.rollno, a.name from total_marks a join dept_max b on a.deptcode = b.deptcode and a.total = b.max_total
If you don't want to use views you can replace their selects on the final query, which will result in this:
select a.deptcode, a.rollno, a.name
from
(select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno) a
join (select deptcode, max(total) max_total from (select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno) a_ group by deptcode) b
on a.deptcode = b.deptcode and a.total = b.max_total
Which I'm sure it is easily improvable in performance by someone more skilled then me...
If you (and anybody else) want to try it the way I did, here is the schema:
create table depts ( deptcode int primary key auto_increment, deptname varchar(20) );
create table students ( rollno int primary key auto_increment, name varchar(20) not null, deptcode int, foreign key (deptcode) references depts(deptcode) );
create table course ( crs_rollno int, crs_name varchar(20), marks int, foreign key (crs_rollno) references students(rollno) );
And here all the entries I inserted:
insert into depts (deptname) values ("Computer Science"),("Biology"),("Fine Arts");
insert into students (name,deptcode) values ("Turing",1),("Jobs",1),("Tanenbaum",1),("Darwin",2),("Mendel",2),("Bernard",2),("Picasso",3),("Monet",3),("Van Gogh",3);
insert into course (crs_rollno,crs_name,marks) values
(1,"Algorithms",25),(1,"Database",28),(1,"Programming",29),(1,"Calculus",30),
(2,"Algorithms",24),(2,"Database",22),(2,"Programming",28),(2,"Calculus",19),
(3,"Algorithms",21),(3,"Database",27),(3,"Programming",23),(3,"Calculus",26),
(4,"Zoology",22),(4,"Botanics",28),(4,"Chemistry",30),(4,"Anatomy",25),(4,"Pharmacology",27),
(5,"Zoology",29),(5,"Botanics",27),(5,"Chemistry",26),(5,"Anatomy",25),(5,"Pharmacology",24),
(6,"Zoology",18),(6,"Botanics",19),(6,"Chemistry",22),(6,"Anatomy",23),(6,"Pharmacology",24),
(7,"Sculpture",26),(7,"History",25),(7,"Painting",30),
(8,"Sculpture",29),(8,"History",24),(8,"Painting",30),
(9,"Sculpture",21),(9,"History",19),(9,"Painting",25) ;
Those inserts will load these data:
select * from depts;
+----------+------------------+
| deptcode | deptname |
+----------+------------------+
| 1 | Computer Science |
| 2 | Biology |
| 3 | Fine Arts |
+----------+------------------+
select * from students;
+--------+-----------+----------+
| rollno | name | deptcode |
+--------+-----------+----------+
| 1 | Turing | 1 |
| 2 | Jobs | 1 |
| 3 | Tanenbaum | 1 |
| 4 | Darwin | 2 |
| 5 | Mendel | 2 |
| 6 | Bernard | 2 |
| 7 | Picasso | 3 |
| 8 | Monet | 3 |
| 9 | Van Gogh | 3 |
+--------+-----------+----------+
select * from course;
+------------+--------------+-------+
| crs_rollno | crs_name | marks |
+------------+--------------+-------+
| 1 | Algorithms | 25 |
| 1 | Database | 28 |
| 1 | Programming | 29 |
| 1 | Calculus | 30 |
| 2 | Algorithms | 24 |
| 2 | Database | 22 |
| 2 | Programming | 28 |
| 2 | Calculus | 19 |
| 3 | Algorithms | 21 |
| 3 | Database | 27 |
| 3 | Programming | 23 |
| 3 | Calculus | 26 |
| 4 | Zoology | 22 |
| 4 | Botanics | 28 |
| 4 | Chemistry | 30 |
| 4 | Anatomy | 25 |
| 4 | Pharmacology | 27 |
| 5 | Zoology | 29 |
| 5 | Botanics | 27 |
| 5 | Chemistry | 26 |
| 5 | Anatomy | 25 |
| 5 | Pharmacology | 24 |
| 6 | Zoology | 18 |
| 6 | Botanics | 19 |
| 6 | Chemistry | 22 |
| 6 | Anatomy | 23 |
| 6 | Pharmacology | 24 |
| 7 | Sculpture | 26 |
| 7 | History | 25 |
| 7 | Painting | 30 |
| 8 | Sculpture | 29 |
| 8 | History | 24 |
| 8 | Painting | 30 |
| 9 | Sculpture | 21 |
| 9 | History | 19 |
| 9 | Painting | 25 |
+------------+--------------+-------+
I take chance to point out that this database is badly designed. This becomes evident with course table. For these reasons:
The name is singular
This table does not represent courses, but rather exams or scores
crs_name should be a foreign key referencing the primary key of another table (that would actually represent the courses)
There is no constrains to limit the marks to a range and to avoid a student to take twice the same exam
I find more logical to associate courses to departments, instead of student to departments (this way also would make these queries easier)
I tell you this because I understood you are learning from a book, so unless the book at one point says "this database is poorly designed", do not take this exercise as example to design your own!
Anyway, if you manually resolve the query with my data you will come to this results:
+----------+--------+---------+
| deptcode | rollno | name |
+----------+--------+---------+
| 1 | 1 | Turing |
| 2 | 6 | Bernard |
| 3 | 8 | Monet |
+----------+--------+---------+
As further reference, here the contents of the views I needed to define:
select * from total_marks;
+----------+-----------+--------+-------+
| deptcode | name | rollno | total |
+----------+-----------+--------+-------+
| 1 | Turing | 1 | 112 |
| 1 | Jobs | 2 | 93 |
| 1 | Tanenbaum | 3 | 97 |
| 2 | Darwin | 4 | 132 |
| 2 | Mendel | 5 | 131 |
| 2 | Bernard | 6 | 136 |
| 3 | Picasso | 7 | 81 |
| 3 | Monet | 8 | 83 |
| 3 | Van Gogh | 9 | 65 |
+----------+-----------+--------+-------+
select * from dept_max;
+----------+-----------+
| deptcode | max_total |
+----------+-----------+
| 1 | 112 |
| 2 | 136 |
| 3 | 83 |
+----------+-----------+
Hope I helped!
Try the following query
select a.name, b.deptname,c.marks
from students a
, crs_regd b
, depts c
where a.rollno = b.crs_rollno
and a.deptcode = c.deptcode
and(c.deptname,b.marks) in (select do.deptname, max(x.marks)
from students so
inner join depts do
on do.deptcode=so.deptcode
inner join (select s.name as name
, d.deptname as deptname
, sum(c.marks) as marks
from students s
inner join crs_regd c
on s.rollno=c.crs_rollno
inner join depts d
on d.deptcode=s.deptcode
group by s.name,d.deptname) x
on x.name=so.name
and x.deptname=do.deptname
group by do.deptname
)
Inner/Sub query will fetch the course name and max marks and the outer query gets the corresponding name of the student.
try and let know if you got the desired result
Dense_Rank() function would be helpful in this scenario:
SELECT subquery.*
FROM (SELECT Student_Total_Marks.rollno,
Student_Total_Marks.name,
Student_Total_Marks.deptcode, depts.deptname,
rank() over (partition by deptcode order by total_marks desc) Student_Rank
FROM (SELECT Stud.rollno,
Stud.name,
Stud.deptcode,
sum(course.marks) total_marks
FROM students stud inner join course course on stud.rollno = course.crs_rollno
GROUP BY stud.rollno,Stud.name,Stud.deptcode) Student_Total_Marks,
dept dept
WHERE Student_Total_Marks.deptcode = dept.deptname
GROUP BY Student_Total_Marks.deptcode) subquery
WHERE suquery.student_rank = 1

SQL Question Looking Up Value in Same Table

Trying to use a self join in SQL to look up a value in the table and apply it.
Her's what I got:
+-----------------+-----+--------+-----------+
| Acutal Output | | | |
+-----------------+-----+--------+-----------+
| TRKID | Fac | NewFac | BAG_TRKID |
| 449 | 11 | 11 | 999 |
| 473 | 11 | 11 | 737 |
| 477 | 11 | 11 | 737 |
| 482 | 11 | 11 | 737 |
| 737 | 89 | 89 | |
| Desired Out Put | | | |
| TRKID | Fac | NewFac | BAG_TRKID |
| 449 | 11 | 11 | 999 |
| 473 | 11 | 89 | 737 |
| 477 | 11 | 89 | 737 |
| 482 | 11 | 89 | 737 |
| 737 | 89 | 89 | |
+-----------------+-----+--------+-----------+
Here's the code below. I can't seem to get the table that I want. The Bag TrkID's Facility Num is not becoming the TrkID New Facility Num.
Select
TABLEA.TRKID,
TABLEA.FAC,
NVL(TABLEA.FAC, TABLEB.FAC) as NEWFAC,
TABLEA.BAG_TRKID
FROM
(
Select
HSD. TRKID,
HSD.NLPT as FAC,
SBPD.BAG_TRKID
From
HSD
LEFT JOIN
SBPD
ON
SBPD.BAG_TRKID = HSD. TRKID
Where
HSD.SCANDT BETWEEN ‘Yesterday’ and ‘Today’
) TABLEA
LEFT JOIN
(
Select
HSD. TRKID,
HSD.NLPT as FAC,
SBPD.BAG_TRKID
From
HSD
LEFT JOIN
SBPD
ON
SBPD.BAG_TRKID = HSD. TRKID
Where
HSD.SCANDT BETWEEN ‘Yesterday’ and ‘Today’
) TABLEB
ON
TABLEA.TRKID = TABLEB.BAG_TRKID
Perhaps something like
select a.TrkID, a."Facility Number", a.BAG_TRKID, b.TrkID as "NEW Fac"
from tbl a
left join tbl b on (a.TrkID = b.trk_id_reference)
Given the limited information that you've shared, I was able to achieve the expected output with the following query:
SELECT a.TrkID, a.facility_number, a.bag_trkid, b.facility_number as new_facility_number
FROM test_tbl AS a
LEFT JOIN test_tbl AS b ON a.bag_trkid = b.trkid OR (a.bag_trkid IS NULL AND b.trkid = a.trkid);
You want to get the new_facility_number for a row based on its bag_trkid (which can be achieved by this: LEFT JOIN test_tbl AS b ON a.bag_trkid = b.trkid).
BUT the trick is to account for the cases when the Left Table (which I refer as a) does not have a bag_trkid. In this case, we will keep the new_facility_number to be the same as a.facility_number, joining the tables on the trkid solely: OR (a.bag_trkid IS NULL AND b.trkid = a.trkid)

How to re-key a hierarchy in a table?

I have a two tables
cars: contains hierarchy data about cars
+-----+-------------+-----------+
| id | description | parent_id |
+-----+-------------+-----------+
| 1 | All cars | 1 |
| 30 | Toyota | 1 |
| 34 | Yaris | 30 |
| 65 | Yaris | 30 |
| 87 | Avensis | 30 |
| 45 | Avensis | 30 |
| 143 | Skoda | 1 |
| 199 | Octavia | 143 |
| 12 | Yeti | 143 |
+-----+-------------+-----------+
car_mapping: contains mapping data where duplicate cars (with different ids) are mapped to one id.
+--------+----------+--------+
| car_id | car_name | map_id |
+--------+----------+--------+
| 34 | Yaris | 1 |
| 65 | Yaris | 1 |
| 87 | Avensis | 2 |
| 45 | Avensis | 2 |
| 199 | Octavia | 3 |
| 12 | Yeti | 4 |
| 30 | Toyota | 5 |
| 143 | Skoda | 6 |
| 1 | All cars | 0 |
+--------+----------+--------+
Now, the idea is to create a third table, cars_new, based on cars and car_mapping which removes duplicates and re-keys the hierarchy in the cars table based on the map_id field in the car_mapping table. Here is the resulting cars_new:
+--------+----------+---------------+
| map_id | car_name | parent_map_id |
+--------+----------+---------------+
| 0 | All | 0 |
| 1 | Yaris | 5 |
| 2 | Avensis | 5 |
| 3 | Octavia | 6 |
| 4 | Yeti | 6 |
| 5 | Toyota | 0 |
| 6 | Skoda | 0 |
+--------+----------+---------------+
Here is the SQL Fiddle for this question. Any ideas how to re-key this hiearchy?
select distinct cm.map_id, cm.car_name, cm2.map_id parent_map_id
from cars c, car_mapping cm, car_mapping cm2
where c.id = cm.car_id
and c.parent_id = cm2.car_id(+)
order by cm.map_id;
PS: in your car_mapping table, you need one extra line (first one below) to get exactly the result you want:
+--------+----------+--------+
| car_id | car_name | map_id |
+--------+----------+--------+
| 1 | All | 0 |
| 34 | Yaris | 1 |
| 65 | Yaris | 1 |
Etc..
Based on #Majid LAISSI's accepted answer, this seems to work both in Oracle and SQL Server:
select distinct cm.map_id, cm.car_name, cm2.map_id as parent_map_id
from cars c
left outer join car_mapping cm on c.id = cm.car_id
left outer join car_mapping cm2 on c.parent_id = cm2.car_id
order by cm.map_id;
You don't have a hierarchy, and you're better off not creating one. Observe that your "cars" table doesn't describe cars; it merely assigns a string to a number (and another number to that number). Right from the get-go, "all cars" isn't a car, and "Toyota" is a car manufacturer, not a car.
The solution -- which would help with your uniqueness issue and simplify your queries -- is to use one table for each distinct thing:
manufactures { mfg_id, name } -- e.g. GM, Ford
makes { make_id, name, mfg_id } -- e.g. Chevrolet, Lincoln; links to manufactures
models { name, make_id } -- e.g. Yaris, etc.; links to makes.
Be sure to make "name" unique in each of the tables to prevent spurious IDs from being created.
This will let you assign new attributes to these things as they arise, such as the years they were made or how many were sold, or how many doors each model comes in. It will also let you prevent "relations" of Ford to GM or, say, making Yaris the parent of "all cars".
(BTW, I suggest you eschew "map" or "mapping" in a table name, because it doesn't say anything. Every table relates the elements in the row to each other. Every table maps the key to its values. The good news is that your car_mapping table disappears in the new design.)
As for how to convert the existing cars table, it will be a nuisance. Assuming cars_mapping is right, you'll be able to insert into each table, joining to it and taking the min(id) while grouping by name. You'll need three such queries, followed by some careful eyeballing to check for, er, misalignment.