Order column by certain criteria in mysql - mysql

I need to be able to order my output data in a proper way using MySQL.
I'm using ORDER BY so far and everything was working correctly until now.
Let's say I have something like this:
Table VEHICLES:
ID | Name | MainGroup | SubGroup
1 | A | Vehicle | Truck
2 | B | Vehicle | Car
3 | C | Vehicle | Car
4 | D | Vehicle | Truck
5 | E | Vehicle | Truck
6 | F | Vehicle | Motorbike
I was using this:
SELECT * FROM Vehicles WHERE MainGroup=Vehicle ORDER BY Subgroup;
When I get selections they are not sorted in a way that I want, because now I want to be able to determine the way of the selected ones. Let's say that I want an output like this Car, Truck, Motorbike or some other way around. How to achieve this?
Is this doable using an order by?

Use field():
SELECT *
FROM Vehicles
WHERE MainGroup=Vehicle
ORDER BY field(Subgroup, 'Car', 'Truck', 'Motorbike');

Related

MySQL - How to order duplicate rows in a key value pair table based on multiple columns?

So I have the following key/value pair table, where users submit data through a form and each question on the form is added to the table here as an individual row. Submission_id identifies each form submission.
+----+---------------+--------------+--------+
| id | submission_id | key | value |
+----+---------------+--------------+--------+
| 1 | 10 | manufacturer | Apple |
| 2 | 10 | model | 5s |
| 3 | 10 | firstname | Paul |
| 4 | 15 | manufacturer | Apple |
| 5 | 15 | model | 5s |
| 6 | 15 | firstname | Paul |
| 7 | 20 | manufacturer | Apple |
| 8 | 20 | model | 5s |
| 9 | 20 | firstname | Andrew |
+----+---------------+--------------+--------+
From the data above you can see that the submissions with id of 10 and 15 both have the same values (just different submission id). This is basically because a user has submitted the same form twice and so is a duplicate.
Im trying to find a way to order these table where the any duplicate submissions appear together in order. Given the above table I am trying to build a query that gives me the result as below:
+---------------+
| submission_id |
+---------------+
| 10 |
| 15 |
| 20 |
+---------------+
So I want to check to see if a submission where the manufacturer, model and firstname keys have the same value. If it does then these get the submission id and place them adjacently in the result. In the actual table there are other keys, but I only want to match duplicates based on these 3 keys (manufacturer, model, firstname).
I’ve been going back and forth to the drawing board quite some time now and have tried looking for some possible solutions but cannot get something reliable.
That's not a key value table. It's usually called an Entity-Attribute-Value table/relation/pattern.
Looking at the problem, it would be trivial if the table were laid out in conventional 1st + 2nd Normal form - you just do a join on the values, group by those and take a count....
SELECT manufacturer, model, firstname, COUNT(DISTINCT submission_id)
FROM atable
GROUP BY manufacturer, model, firstname
HAVING COUNT(DISTINCT submission_id)>1;
Or a join....
SELECT a.manufacturer, a.model, a.firstname
, a.submission_id, b.submission_id
FROM atable a
JOIN atable b
ON a.manufacturer=b.manufacturer
AND a.model=b.model
AND a.firstname=b.firstname
WHERE a.submission_id<b.submission_id
;
Or using sorting and comparing adjacent rows....
SELECT *
FROM
(
SELECT #prev.submission_id AS prev_submission_id
, #prev.manufacturer AS prev_manufacturer
, #prev.model AS prev_model
, #prev.firstname AS pref_firstname
, a.submission_id
, a.manufacturer
, a.model
, set #prev.submission_id:=a.submission_id as currsid
, set #prev.manufacturer:=a.manufacturer as currman
, set #prev.model:=a.model as currmodel
, set #prev.firstname=a.forstname as currname
FROM atable
ORDER BY manufacturer, model, firstname, submission_id
)
WHERE prev_manufacturer=manufacturer
AND prev_model=model
AND prev_firstname=firstname
AND prev_submission_id<>submission_id;
So the solution is to simply make your data look like a normal relation....
SELECT ilv.values
, COUNT(ilv.submission_id)
, GROUP_CONCAT(ilv.submission_id)
FROM
(SELECT a.submission_id
, GROUP_CONCAT(CONCAT(a.key, '=',a.value)) AS values
FROM atable a
GROUP BY a.submission_id
) ilv
GROUP BY ilv.values
HAVING COUNT(ilv.submission_id)>1;
Hopefully the join and sequence based solutions should now be obvious.

SQL ORDER BY using a value in another table using an equation grouped by duplicates

I have been given a movie database and I am trying to order by user rating held in a separate table to my movie information.
My problem is that my database does NOT have an overall rating of a movie, but only singular user ratings attached to that movie. But I want to order it by the overall average rating
For example:
SELECT Movies.title, Movies.movie_id, Movies.poster, Ratings.rating
FROM Movies INNER JOIN Ratings
ON Movies.movie_id = Ratings.movie_id
WHERE genre LIKE '%action%' AND (origin = 'american')
ORDER BY Ratings.rating DESC;
*(Rating is only selected to showcase to you the values, I do not need to use them outside this query)
This works out to display
+-------------------------+----------+------------------------------------+--------+
| title | movie_id | poster | rating |
+-------------------------+----------+------------------------------------+--------+
| The Baytown Outlaws | 2 | posters/The Baytown Outlaws.jpg | 5 |
| A Dark Truth | 8 | posters/A Dark Truth.jpg | 4 |
| A Dark Truth | 8 | posters/A Dark Truth.jpg | 3 |
| American Made | 14 | posters/American Made.jpg | 3 |
| Avengers: Age of Ultron | 4 | posters/Avengers Age of Ultron.jpg | 3 |
| Romeo Must Die | 1 | posters/Romeo Must Die.jpg | 3 |
| Avengers: Age of Ultron | 4 | posters/Avengers Age of Ultron.jpg | 2 |
| Fast & Furious 6 | 3 | posters/Fast & Furious 6.jpg | 2 |
| Olympus Has Fallen | 9 | posters/Olympus Has Fallen.jpg | 1 |
+-------------------------+----------+------------------------------------+--------+
Now from this I want to have my query select groups based on movie title, add up the ratings of that group, average that value, and then order titles by that average and excluding duplicate title names from the return
So my ideal query would return:
+-------------------------+----------+------------------------------------+
| title | movie_id | poster |
+-------------------------+----------+------------------------------------+
| The Baytown Outlaws | 2 | posters/The Baytown Outlaws.jpg |
| A Dark Truth | 8 | posters/A Dark Truth.jpg |
| American Made | 14 | posters/American Made.jpg |
| Romeo Must Die | 1 | posters/Romeo Must Die.jpg |
| Avengers: Age of Ultron | 4 | posters/Avengers Age of Ultron.jpg |
| Fast & Furious 6 | 3 | posters/Fast & Furious 6.jpg |
| Olympus Has Fallen | 9 | posters/Olympus Has Fallen.jpg |
+-------------------------+----------+------------------------------------+
So this returns my movie info ordered by average rating then excluding duplicate titles
Baytown Outlaws has 1 rating at 5 -> overall 5
Dark Truth has 2 ratings at 4 and 3 -> overall rating of 3.5 but only displays one row of movie info
American Made has 1 rating at 3 -> overall 3
etc.
I am having a lot of trouble figuring out this exact query, or if it is even possible. Any help or keyword suggestion would be useful as I am somewhat new to SQL and don't know all of its strengths. If not possible, I would also appreciate an answer saying so, so that I can go ahead and rework the database system to instead follow a better system of saving the overall rating within the Movies table.
A combination of COUNT(*) and SUM(rating) group by movie_id (or title).
Something like this:
SELECT Movies.title, Movies.movie_id, Movies.poster, SUM(Ratings.rating)/COUNT(*) AS avg_rating
FROM Movies INNER JOIN Ratings
ON Movies.movie_id = Ratings.movie_id
WHERE genre LIKE '%action%' AND (origin = 'american')
GROUP BY Movies.movie_id ORDER BY avg_rating DESC;
It can be done with the AVG function and a group by on multiple columns.
The tricky part of using AVG in that case is that when using such a function, every element in the SELECT part must either be in the GROUP or be an aggregate function. That being said, If you make groups by a combination of ID and Movie title, you would obtain the same result (in this case) as if you grouped by ID only.
You can use that to your advantage to add these columns in your SELECT section while using the AVG aggregate function.
In your first table, every time you look at the A Dark Truth movie, it comes with the same id, 8. Every time you look at the value Avengers: Age of Ultron, it comes with the same id, 4.
I suggest taking a few minutes and drawing a Venn diagram of the problem to get a good grasp of it as it seems this is material of a class.
I made a fiddle to demonstrate it for you. You can play around with it and add your initial join and where to complete it, I did a slight variation of the initial model, the create table is also in the fiddle.
SELECT Movie_id, Title, Poster, AVG(Rating)
FROM MoviesRatings
GROUP BY Movie_id, Title, Poster
ORDER BY AVG(Rating) DESC

count groupings of multiple columns

I have a table of tickets to multiple dates of shows shows. basically, it looks like this...
+----+---------------+--------------+-----------+
| ID | ticket_holder | ticket_buyer | show_date |
+----+---------------+--------------+-----------+
ticket_holder and ticket_buyer are both user ids
If I wanted to count the total number of tickets that one ticket holder has, I could group by that holder and count the rows, but I want more stats than that.
I want to know a user's total bought tickets, how many they hold and how many shows they've bought tickets for.
+------+---------+--------+-------+
| USER | HOLDING | BOUGHT | DATES |
+------+---------+--------+-------+
| 1 | 12 | 24 | 7 |
+------+---------+--------+-------+
| 2 | 3 | 4 | 2 |
+------+---------+--------+-------+
| 3 | 1 | 2 | 1 |
+------+---------+--------+-------+
is it possible to put all this in a query, or do i need to do php stuff to make it happen?
I would do it in multiple queries. You can't group by either ticket_holder or ticket_buyer like you want, in a single query. If you try GROUP BY ticket_holder, ticket_buyer then it will group by both columns, which is not what you want.
SELECT ticket_holder, COUNT(*) AS tickets_held
FROM `a table of tickets` GROUP BY ticket_holder;
SELECT ticket_buyer, COUNT(*) as tickets_bought
FROM `a table of tickets` GROUP BY ticket_buyer;
SELECT ticket_buyer, COUNT(DISTINCT show_date) AS shows_bought
FROM `a table of tickets` GROUP BY ticket_buyer;
Not every task has to be accomplished in a single query! It's part of the design of SQL that it should be used by some application language, and you're expected to handle formatting and display in the application.

SQL: many-to-many relationship and the 'ALL' clause

I have a table products and a table locations which are linked together in a many-to-many relationship with a table products_locations. Now a client can select a set of products, and I want to run a query that selects only the locations, where ALL of the selected products are available.
This seemed pretty straight forward at first, but I see myself being quite baffled by how to achieve this. I initially thought I could get all the correct location-ids with something like
SELECT location_id
FROM products_locations
WHERE product_id = ALL [the user selected product ids]
But on second thought that does not appear to make sense either (the structure of products_locations is quite simply [product_id, location_id].
Any suggestion on how to structure such a query would be appreciated. I feel like I am overlooking something basic..
EDIT: I am using mysql syntax/dialect
Quick sample: Given the following tables
| products | | locations | | products_locations |
| id | name | | id | name | | product_id | location_id |
|------------| |-----------| |--------------------------|
| 1 | prod1 | | 1 | locA | | 1 | 2 |
| 2 | prod2 | | 2 | locB | | 2 | 1 |
| 3 | prod3 | |-----------| | 2 | 2 |
|------------| | 3 | 1 |
|--------------------------|
If a user selects products 1 and 2, the query should return only location 2. If the user selects products 2 and 3, the query should return location 1. For 1, 2, and 3, no location would be valid, and for product 2, both locations would be valid.
I figured out a query that achieves what I need. Though it is not as clean as I had hoped, it seems to be a robust approach to what I'm trying to query:
SELECT t.location_id
FROM (SELECT location_id, COUNT(*) as n_hits
FROM products_locations
WHERE product_id IN [the user selected products]
GROUP BY location_id) t
WHERE n_hits = [the number of user selected products];
Explanation:
I create a temporary table t which contains every location_id that has at least one matching product in the user's selection, together with the number of times that location matches a product in the user's selection. This is achieved by grouping the query by location_id.
I select the location_id(s) from that temporary table t, where the number of hits is equal to the number of products the user had selected. If that number is lower, I know that at least one product did not match that location.

MYSQL (WHERE - IF - SELECT/IN statement)

I'm trying to set my code up where I have a Control Panel for my Sales Reps.
From my Control Panel, I am able to control which customers a Rep calls based on the item the customer bought.
i.e. If I assign "Cars" in my Control Panel for Sales Rep 01, he would only call customers that bought toys in the "Cars" category. And all different toys in my "Cars" category are stored in a separate file named "Vehicles".
My files are as follows:
customer (File)
+--------+-----------+--------+
| name | phone | toy |
+--------+-----------+--------+
| Gail | 777-1234 | Truck |
| June | 777-1235 | Doll |
| Mary | 777-1236 | Racer |
| Bill | 777-1237 | Ball |
| Jon | 777-1238 | Jeep |
+--------+-----------+--------+
control_panel (File)
+----------+--------+
| user | desc |
+----------+--------+
| sales_01 | Cars |
+----------+--------|
vehicles (File)
+---------+
+ item |
+---------+
| Truck |
| Racer |
| Jeep |
+---------+
In trying to test this code out, I have this portion of my code working.
select
c.name , c.phone
FROM
customer c
WHERE
c.toy IN (
SELECT
v.item
FROM
vehicles v
)
Now I'm trying to condition my WHERE statement so that only if I choose "Cars" in my Control Panel screen for User "sales_01", then customers who bought Cars will only show on the Call Screen for User "sales_01".
This is one example of some of the code I've been testing but cant get to work correctly.
SELECT
c.name , c.phone
FROM
customer c , control_panel p
WHERE
(IF p.desc = "Cars"
THEN (c.toy
IN (SELECT
v.item
FROM
vehicles v)
)
END)
Any help is appreciated. Thx.
To start with, I suggest you refactor your schema by adding an field category to your vehicles table. This will allow a proper relationship between your control_panel and customer. In that case you could just do SELECT c.name, c.phone FROM customer c LEFT OUTER JOIN vehicles v ON c.toy = v.items WHERE v.category = 'Cars'. I would have loved to add other suggestions but I feel you want something that could work right now. Plus, I do not have much time to spare presently. Hope this helps.