MYSQL Tuple in IN CLause with wildcard matching - mysql

I have the following problem. suppose I have a table containing an inventory of cars:
VIN, MAKE, MODEL, PRICE
AND I have another table containing various car classes( eg, compact, mid-sized, suv, etc)
MAKE, MODEL, CLASS
JEEP, NULL , SUV
FORD, EXPLORER, SUV
TOYOTA, YARIS, COMPACT
NULL, CUBE, COMPACT
...
I am wondering how can I select all cars in the database of a certain class? Perhaps, I want to know how many cars of each class exists?
I can write a query like this.
SELECT COUNT(*) FROM CARS WHERE (MAKE, MODEL) IN (SELECT MAKE, MODEL FROM CLASSES WHERE CLASS = 'SUV')
The problem here, I wont actually be able to deal with NULLs in my data. I want to say that all models of JEEP are SUVs, or any MAKE car that's called CUBE would be a compact car.
Could this be done assuming there are no conflicts? Can I have a priority set up, like All Porches are CARS except the Cayenne which is an SUV by doing something like this:
MAKE, MODEL, CLASS
PORCHE, NULL , CAR
PORCHE, CAYENNE, SUV
If this isn't possible with MySQL, is there a better DB technology out there that would be good at this. Lets assume that The CLASSES Table would Contain 50K+ Rows and The CARS Table would contain 5M+ Rows. I am looking for a fast way of performing such a query in the database, and not needing to fetch millions of rows to process in a script? Also What if its not just Make and Model, but also sub-model, engine, etc..
Also, I simplified the data quite a bit, there are 4 levels of hierarchy.

Assuming that make and model cannot be both null for a given class. You can use the ifnull() function in MySQL:
select cl.class, count(*)
from cars c inner join class cl
on c.make = ifnull(cl.make,c.make)
and c.model=ifnull(cl.model,c.model)
group by cl.class
You may want to add an index on columns makes and model for faster access. Since classes have fewer rows using an inner join (as above) will restrict the number of rows returned.

You can write your query like this:
SELECT COUNT(*)
FROM CARS c join
classes cl
on (c.make = cl.make or cl.make is null) and
(c.model = cl.model or cl.model is null) and
cl.class = 'SUV'
The problem is that you might get duplicates. So the better count starts with:
select count(distinct c.vin)
. . .
This should work for the two cases you gave, for JEEP and CUBE.

SELECT s.class, COUNT(*)
FROM car c, car_class s
WHERE c.make = s.make
AND c.model = s.model
GROUP BY s.class
should give you something like
SUV 14
COMPACT 32
TRUCK 11
etc.

Related

MySQL Query - Lowest Values with Multiple Tables / Joins

i am requesting some help for a query to be used on a custom golf website.
what i need is to find the lowest score per player per course. my club has 3 nine hole loops, 27 holes in total, but i want to find the lowest per 9 holes (i.e. course as i am describing it).
i have the following database structure (note, i haven’t put in all rows, only those that are pertinent to the query i am stuggling with).
Golf DB ERP Diagram
a query to get the full set of data would be (note some field names are different - the diagram was trying to better descriptive…):
select * from round r, round_hole rh, player p, course_nine c, course_hole ch
where r.r_id = rh.rh_rid
and p.id = r.r_pid
and c.cn_nine = r.r_nine
and ch.ch_nine = c.cn_nine
and rh.rh_hid = ch.ch_no
a snapshot of the results are:
Full query ouput
however, i then need to filter it as above, into "per player, per course”
i am presuming this is some subquery, join, temp table or “in” type statement, but struggling, particularly as it spans multiple tables.
any help is appreciated
This can be accomplished using some simple aggregation. As long as you are able to properly join all of your tables, you can do this:
SELECT player, course, MIN(score) AS lowestScore
FROM myTables
GROUP BY player, course;

Looking for an efficient way to translate values in a huge MariaDB table by information in another, very small table

Problem:
In a table a with 10 mio rows I have a column a.profession_id of coded professions of customers (0, 1, 2, ...).
In another very small table b I have some (not all) "translations" for these professions (0 = unemployed, 1 = worker, ...).
Goal:
I want to enhance my huge table with the existing Information in an efficient (fast) way.
Not working:
INNER JOIN since I don't have all translations
Classic LEFT JOIN since an index on a.profession_id will not be used and an index on b.profession_id won't Speed things up enogh
SELECT * FROM a LEFT JOIN b ON a.profession_id = b.profession_id
Solution:
Non so far, do you have a clever input?
If you absolutely have to do the translation, and can't avoid doing it...
The second best option (purely in terms of database performance and no other considerations) is to ignore the small table and use a static expression in the SELECT list:
SELECT r.profession_id
, CASE r.profession_id
WHEN 0 THEN 'unemployed'
WHEN 1 THEN 'worker'
WHEN 2 THEN '...'
ELSE ''
END AS profession_name
FROM table_with_10_mio_rows r
There's drawbacks to that approach, if that profession lookup list isn't static. (If that had been had a static list, and profession isn't an entity in our data model, we might have actually considered implementing profession as a enum datatype.)
As a separate step, to help us quickly fill in that CASE expression, we could make use of the lookup table:
SELECT CONCAT(' WHEN ',t.id,' THEN ''',REPLACE(t.name,'''',''''''),'''') AS expr
FROM small_profession_lookup_table t
ORDER BY id
If some professions are much higher frequency, move those to the top of the list.

How do I make the rows of a lookup table into the columns of a query?

I have three tables: students, interests, and interest_lookup.
Students has the cols student_id and name.
Interests has the cols interest_id and interest_name.
Interest_lookup has the cols student_id and interest_id.
To find out what interests a student has I do
select interests.interest_name from `students`
inner join `interest_lookup`
on interest_lookup.student_id = students.student_id
inner join `interests`
on interests.interest_id = interest_lookup.interest_id
What I want to do is get a result set like
student_id | students.name | interest_a | interest_b | ...
where the column name 'interest_a' is a value in interests.name and
the interest_ columns are 0 or 1 such that the value is 1 when
there is a record in interest_lookup for the given
student_id and interest_id and 0 when there is not.
Each entry in the interests table must appear as a column name.
I can do this with subselects (which is super slow) or by making a bunch of joins, but both of these really require that I first select all the records from interests and write out a dynamic query.
You're doing an operation called a pivot. #Slider345 linked to (prior to editing his answer) another SO post about doing it in Microsoft SQL Server. Microsoft has its own special syntax to do this, but MySQL does not.
You can do something like this:
SELECT s.student_id, s.name,
SUM(i.name = 'a') AS interest_a,
SUM(i.name = 'b') AS interest_b,
SUM(i.name = 'c') AS interest_c
FROM students s
INNER JOIN interest_lookup l USING (student_id)
INNER JOIN interests i USING (interest_id)
GROUP BY s.student_id;
What you cannot do, in MySQL or Microsoft or anything else, is automatically populate columns so that the presence of data expands the number of columns.
Columns of an SQL query must be fixed and hard-coded at the time you prepare the query.
If you don't know the list of interests at the time you code the query, or you need it to adapt to changing lists of interest, you'll have to fetch the interests as rows and post-process these rows in your application.
What your trying to do sounds like a pivot.
Most solutions seem to revolve around one of the following approaches:
Creating a dynamic query, as in Is there a way to pivot rows to columns in MySQL without using CASE?
Selecting all the attribute columns, as in How to pivot a MySQL entity-attribute-value schema
Or, identifying the columns and using either a CASE statement or a user defined function as in pivot in mysql queries
I don't think this is possible. Actually I think this is just a matter of data representatioin. I would try to use a component to display the data that would allow me to pivot the data (for instance, the same way you do on excel, open office's calc, etc).
To take it one step further, you should think again why you need this and probably try to solve it in the application not in the database.
I know this doesn't help much but it's the best I can think of :(

group_concat many times?

I'm trying to make a MySQL query that uses different group_concat with data from the same LEFT JOIN. An example tables could be:
Cars table
carid | license_plate
Tires table (means used tires)
carid | model | width
From these tables i want to obtain a list of tire models classified by their width, with the idea that there are only two different possible widths and I'm loading it from a car card page.
SELECT name,
if(width=205,group_concat(model ORDER BY model ASC),'') as width_205,
if(width=225,group_concat(model ORDER BY model ASC),'') as width_225,
FROM cars
LEFT JOIN tires ON cars.carid=tires.carid
WHERE carid='10'
I hope that my explanation is clear. This sentence doesn't work properly, and I don't know if it's because of a bad syntax or that I simply can't use group_concat this way.
Well, thanks for reading and I'll be waiting for your answers. Thanks in advance!
Try
SELECT name,
GROUP_CONCAT(CASE width WHEN 205 THEN model ELSE NULL END CASE AS model ORDER BY model) as width_205,
GROUP_CONCAT(CASE width WHEN 225 THEN model ELSE NULL END CASE AS model ORDER BY model) as width_225,
FROM cars
LEFT JOIN tires ON cars.carid=tires.carid
WHERE carid='10'
GROUP BY name
to get what you want. I don't have MySQL handy to test. You may have to fool with (or even give up) the internal ORDER BY.
Alternatively, you can get back two rows (and not have to change your SQL if a third size is ever added) with:
SELECT name, width, GROUP_CONCAT(model ORDER BY model) AS Models
FROM cars
LEFT JOIN tires ON cars.carid=tires.carid
WHERE carid='10'
GROUP BY name, width
Both solutions are great! Maybe the second is more elegant and finally it's the one I'll use, but I'll take note also from the first because it's more concrete and can be better for some situations.
For the first method to work I had to delete "CASE AS model" after the "END" to prevent MySQL errors:
SELECT name,
GROUP_CONCAT(CASE width WHEN 205 THEN model ELSE NULL END ORDER BY model) as width_205,
GROUP_CONCAT(CASE width WHEN 225 THEN model ELSE NULL END ORDER BY model) as width_225,
FROM cars
LEFT JOIN tires ON cars.carid=tires.carid
WHERE carid='10'
GROUP BY name
I also have added a DISTINCT before the CASE inside group_concat, to avoid repetitions.
For the second method, I only have added an "ORDER BY width ASC" at the end of the sentence. For anyone else that may need this method, don't forget that mysql_fetch_(whatever) already takes the first result that contains the first case of the group_concat.
Thanks a lot! CU

Soccer SQL Query Home- and Roadteam Issue

In a soccer environment I want to display the current standings. Meaning: points and goals per team. The relevant tables look similar to the following (simplified).
Match Table
uid (PK) hometeamid roadteamid
------------------------------------------------------------------
Result Table
uid (PK) hometeamscore roadteamscore resulttype (45min, 90min, ..)
-------------------------------------------------------------------
Team Table
uid (PK) name shortname icon
------------------------------------------------------------------
Now I don't get my head around it, how to write the standings in one query. What I managed was to write a query, which returns the "homegames"-standings only. I guess that's the easy part. Anyway here is how it looks:
SELECT ht.name,
Count(*) As matches,
SUM(res.hometeamscore) AS goals,
SUM(res.roadteamscore) AS opponentgoals,
SUM(res.hometeamscore - res.roadteamscore) AS goalDifference,
SUM(res.hometeamscore > res.roadteamscore) * 3 + SUM(res.hometeamscore = res.roadteamscore) As Points
FROM league_league l
JOIN league_gameday gd
ON gd.leagueid = l.uid
JOIN league_match m
ON m.gamedayid = gd.uid
JOIN league_result res
ON res.matchid = m.uid
AND res.resulttype = 2
JOIN league_team ht
ON m.hometeamid = ht.uid
Where l.uid = 1
Group By ht.uid
Order By points DESC, goalDifference DESC
Any idea how to modify this, that it will return home- and roadgames would be big time appreciated.
Many thanks,
Robin
Create views. If your data does not change often and you need performance, create one or more pre-computed tables.
Views in MySQL are juste pseudo-tables that are dynamically computed from a SELECT query. Using the SQL in your question, you can create a view of the teams results at home: CREATE VIEW homegames AS SELECT ...
Then do the same for road games. Then it will be easy to synthesize both views in a third one (you just need to sum up the columns).
Views have at least one flaw: they are slow. A view built on views is like using complex subqueries, and MySQL is quite bad at this. I don't think it's a problem for you as you're probably dealing with hundreds of games at most. But if you find these views to be too slow to query, and provided you don't use any kind of cache that could mitigate this, then use simple tables instead of views. Of course, you'll need to keep them in sync. You can TRUNCATE and INSERT INTO homegames SELECT ... each time you have a new game, or you can be smarter and just UPDATE the tables. Both are right, depending on your needs.
Could you not abstract this out into a stored procedure or stored function to call rather than constructing such a big-ass complicated query?