Ordering values by custom criteria - mysql

I have two tables A and B table:
Table - A - represents basic information of persons
emp_id | email | name
----------------------------------------
1 | abc#gmail.com | john
2 | dbc#gmail.com | john1
3 | cbc#gmail.com | john2
4 | xbc#gmail.com | john3
5 | xac#gmail.com | john4
Table - B represents the locations handled by persons
john is handling Region and Zone
john1 is handling Area and Territory and so on...
Sequence of locationType is as follows : Region->Zone->Area->Territory
Regions is having higher priority then comes zone and so on..
id | emp_id | locationType
--------------------
1 | 1 | Region
2 | 2 | Area
3 | 3 | Area
4 | 4 | Territory
5 | 1 | Zone
6 | 2 | Territory
7 | 5 | Zone
8 | 5 | Area
I want to fetch those persons who are handling the higher locationType.
Suppose john is handling Region and zone so i want to display Region as Region is of higher priority and similarly john1 is handling Territory and Area so i want to display only Area as because Area is of higher priority
My Desired Output:
id | emp_id | name | locationType
----------------------------------------
1 | 1 | john | Region
5 | 5 | john4 | Zone
3 | 3 | john1 | Area
4 | 4 | john2 | Area
4 | 4 | john3 | Territory
What I am getting
id | emp_id | name | locationType
----------------------------------------
1 | 1 | john | Region
1 | 1 | john | Zone
5 | 5 | john4 | Zone
5 | 5 | john4 | Area
2 | 2 | john1 | Area
3 | 3 | john2 | Area
4 | 4 | john3 | Territory
4 | 4 | john3 | Territory

You can use field() to turn the locations into numbers. What you want is the minimum location based on this ordering.
You can obtain this information per employee using a correlated subquery:
select b.*
from b
where field(b.locationType, 'Region', 'Zone', 'Area', 'Territory') =
(select min(field(b2.locationType, 'Region', 'Zone', 'Area', 'Territory'))
from b b2
where b2.emp_id = b.emp_id
);
Adding the extra columns from a is just a matter of joining in the table.

use case when in order clause
order by (case locationType when 'Region' then 1
when 'Zone' then 2
when 'Area' then 3
when 'Territory' then 4
else 5 end )

To Resolve the Issue Pemanently just Follow the following steps. It will help you in data normalization also.
1 Create A new table that has LocaitonType name and id and Insert your location type as the order you want.
CREATE TABLE [dbo].[Table_C](
[LocationType_Id] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50) NULL )
Insert Into [Table_C] (name) values('Region')
Insert Into [Table_C] (name) values('Zone')
Insert Into [Table_C] (name) values('Area')
Insert Into [Table_C] (name) values('Territory')
2.Alter your table b LocationType column Data type to int.
Alter Table Table_B
Alter column locationType int not null
Now Insert id from Table_c to Table_B Locationtype Column.
and use the below query to get the desired output.
Select Table_B.id,Table_A.emp_id,Table_A.name,Table_C.name as locationType from Table_B inner join Table_A on Table_A.emp_id=Table_B.emp_id
inner join Table_C on Table_C.LocationType_Id=Table_B.locationType
order by Table_C.LocationType_Id

Related

MySQL - join multiple mapped tables and count records with different mapping conditions

It's the 3rd day I'm trying to write a MySQL query. Did lots of search, but it still doesn't work as expected. I'll try to simplify tables as much as possible
System has tkr_restaurants table:
restaurant_id | restaurant_name
1 | AA
2 | BB
3 | CC
Each restaurant has a division assigned (tkr_divisions table):
division_id | restaurant_id | division_name
1 | 1 | AA-1
2 | 1 | AA-2
3 | 2 | BB-1
Then there are meals in tkr_meals_to_restaurants_divisions table, where each meal can be assigned (mapped) to whole restaurant(s) and/or specific division(s). If meal is mapped to restaurant, all restaurant's divisions should see it. If meal is mapped to division(s), only specific division(s) should see it.
meal_id | mapped_restaurant_id | mapped_division_id
1 | 1 | NULL
2 | NULL | 1
3 | NULL | 2
I need to display a list of restaurants and number of meals mapped to it depending on user permissions.
Example 1: if user has permissions to access whole restaurant_id 1 and restaurant_3 (and no specific divisions), then list should be:
AA | 3
CC | 0
(because user can access meals mapped to restaurant 1 + all its division, and restaurant 3 + all its divisions (even if restaurant 3 has no divisions/meals mapped))
Example 2: if user has permissions to access only division_id 1, then list should be:
AA | 1
(because user can only access meals mapped to division 1).
The closest query I could get is:
Example 1:
SELECT *,
(SELECT COUNT(DISTINCT meal_id)
FROM
tkr_meals_to_restaurants_divisions
WHERE
tkr_meals_to_restaurants_divisions.mapped_restaurant_id=tkr_restaurants.restaurant_id
OR tkr_meals_to_restaurants_divisions.mapped_division_id=tkr_divisions.division_id)AS total_meals
FROM
tkr_restaurants
LEFT JOIN
tkr_divisions
ON tkr_restaurants.restaurant_id=tkr_divisions.restaurant_id
WHERE
tkr_restaurants.restaurant_id IN (1, 3)
OR tkr_restaurants.restaurant_id IN (
SELECT restaurant_id
FROM tkr_divisions
WHERE division_id IN (NULL)
)
GROUP BY
tkr_restaurants.restaurant_id
ORDER BY
tkr_restaurants.restaurant_name
However, result was:
AA | 2
CC | 0
I believe I'm greatly over-complicating this query, but all the simpler queries I wrote produced even more inaccurate results.
What about this query:
SELECT
FROM tkr_restaurants AS a
JOIN tkr_divisions AS b
ON a.restaurant_id = b.restaurant_id
LEFT OUTER JOIN tkr_meals_to_restaurants_divisions AS c
ON (c.mapped_restaurant_id = a.restaurant_id OR c.mapped_division_id = b.division_id)
As a Base four your further work. It combine all information into one table. If you add e.g. this:
WHERE a.restaurant_id IN (1, 3)
the result will be
| restaurant_id | restaurant_name | division_id | restaurant_id | division_name | meal_id | mapped_restaurant_id | mapped_division_id |
|---------------|-----------------|-------------|---------------|---------------|---------|----------------------|--------------------|
| 1 | AA | 1 | 1 | AA-1 | 1 | 1 | (null) |
| 1 | AA | 2 | 1 | AA-2 | 1 | 1 | (null) |
| 1 | AA | 1 | 1 | AA-1 | 2 | (null) | 1 |
| 1 | AA | 2 | 1 | AA-2 | 3 | (null) | 2 |
just count the distinct meal ids with COUNT(DISTINCT c.meal_id) and take the restaurant name to get AA: 3 for your example 2
I used a sqlfiddle: http://sqlfiddle.com/#!9/fa2b78/18/0
[EDIT]
Change JOIN tkr_divisions AS b to LEFT OUTER JOIN tkr_divisions AS b
Change SELECT * to SELECT a.restaurant_name, COUNT(DISTINCT c.meal_id)
Add a GROUP BY a.restaurant_name at the end.
Update the SQL Fiddle (new link)

Select the most current records from multiple identical rows in the MySQL database

I am working on a product sample inventory system where I track the movement of the products. The status of each product can have a status of "IN" or "OUT" or "REMOVED". Each row of the table represents a new entry, where ID, status and date are unique. Each product also has a serial number.
I need help with a SQL query that will return all products that are currently "OUT". If I simply just select SELECT * FROM table WHERE status = "IN", it will return all products that ever had status IN.
Every time product comes in and out, I duplicate the last row of that specific product and change the status and update the date and it will get a new ID automatically.
Here is the table that I have:
id | serial_number | product | color | date | status
------------------------------------------------------------
1 | K0T4N | XYZ | silver | 2016-07-01 | IN
2 | X56Z7 | ABC | silver | 2016-07-01 | IN
3 | 96T4F | PQR | silver | 2016-07-01 | IN
4 | K0T4N | XYZ | silver | 2016-07-02 | OUT
5 | 96T4F | PQR | silver | 2016-07-03 | OUT
6 | F0P22 | DEF | silver | 2016-07-04 | OUT
7 | X56Z7 | ABC | silver | 2016-07-05 | OUT
8 | F0P22 | DEF | silver | 2016-07-06 | IN
9 | K0T4N | XYZ | silver | 2016-07-07 | IN
10 | X56Z7 | ABC | silver | 2016-07-08 | IN
11 | X56Z7 | ABC | silver | 2016-07-09 | REMOVED
12 | K0T4N | XYZ | silver | 2016-07-10 | OUT
13 | 96T4F | PQR | silver | 2016-07-11 | IN
14 | F0P22 | DEF | silver | 2016-07-12 | OUT
This query will give you all the latest records for each serial_number
SELECT a.* FROM your_table a
LEFT JOIN your_table b ON a.serial_number = b.serial_number AND a.id < b.id
WHERE b.serial_number IS NULL
Below query will give your expected result
SELECT a.* FROM your_table a
LEFT JOIN your_table b ON a.serial_number = b.serial_number AND a.id < b.id
WHERE b.serial_number IS NULL AND a.status LIKE 'OUT'
There are two good ways to do this. Which way is best,in terms of performance, can depend on various factors, so try both.
SELECT
t1.*
FROM table t
LEFT OUTER JOIN table later_t
ON later_t.serial_number = t.serial_number
AND later_t.date > t.date
WHERE later_t.id IS NULL
AND t.status = "OUT"
Which column you check from later_t for IS NULL does not matter, so long as that column is declared NOT NULL in the table definition.
The other logically equivalent method is:
SELECT
t.*
FROM table t
INNER JOIN (
SELECT
serial_number,
MAX(date) AS date
FROM table
GROUP BY serial_number
) latest_t
ON later_t.serial_number = t.serial_number
AND latest_t.date = t.date
WHERE t.status = "OUT"
For each of these queries, I strongly suggest the following index:
ALTER TABLE table
ADD INDEX `LatestSerialStatus` (serial_number,date)
I use this type of query a lot in my own work, and have the above index as the primary key on tables. Query performance is extremely fast in such cases, for these type of queries.
See also the documentation on this query type.

Getting all results from 3 tables without repetition

I have 3 tables: sports, venues and instructors, they all have an id and a name. Instead of having 3 separate SELECT * FROM table I want to fetch the results with a single query so that the result is something like:
+----------+------------+----------+------------+---------------+-----------------+
| sport_id | sport_name | venue_id | venue_name | instructor_id | instructor_name |
+----------+------------+----------+------------+---------------+-----------------+
| 1 | Sport1 | 1 | Venue1 | 1 | Instructor1 |
| 2 | Sport2 | 2 | Venue2 | 2 | Instructor2 |
| 3 | Sport3 | | | 3 | Instructor3 |
| 4 | Sport4 | | | | |
+----------+------------+----------+------------+---------------+-----------------+
I tried
SELECT * FROM sports, venues, instructors
However there are at ton of repetitions.
EDIT: The 3 tables are not related to each other in any way.
Although I see limited use for getting all the values in one query, you could alway use UNION ALL to get all the values;
SELECT sport_id id, sport_name name, 'sport' type FROM sports
UNION ALL
SELECT venue_id id, venue_name name, 'venue' type FROM venues
UNION ALL
SELECT instructor_id id, instructor_name name, 'instructor' type FROM instructors
This will give output in the format;
id name type
-------------------------------
1 sport1 sport
2 sport2 sport
3 sport3 sport
4 sport4 sport
1 venue1 venue
2 venue2 venue
1 instructor1 instructor
2 instructor2 instructor
3 instructor3 instructor

MySQL Advanced Ranks Query

I have a table that looks like this:
map uid time name
'first' 1 5.0 'Jon'
'first' 3 4.9 'Robin'
'second' 1 2.0 'Jon'
'first' 2 5.3 'Max'
'second' 3 2.1 'Robin'
I am currently selecting the values using this:
SELECT records.* FROM `records` WHERE `uid` = '3' ORDER BY `records`.`time` ASC
Now obviously, I have multiple uids for different maps. How would I find the rank of every user out of total ranks? I know I can find total ranks of the map by using COUNT(DISTINCT map). However, I am having issues selecting a specific user and their rank in the map. Any help would be appreciated!
EDIT:
Desired output when selecting uid 3 is as follows:
map uid time name position totalposition (totalposition would be COUNT(DISTINCT map))
'first' 3 4.9 'Robin' 2 3
'second' 3 2.1 'Robin' 2 2
Use the following query : -
mysql> set #pos = 0; select records.*, #pos:=#pos+1 as position from records order by time desc;
Output :
+--------+------+------+-------+--------------+
| map | uid | time | name | position |
+--------+------+------+-------+--------------+
| first | 2 | 5.30 | Max | 1 |
| first | 1 | 5.00 | jon | 2 |
| first | 3 | 4.90 | Robin | 3 |
| second | 3 | 2.10 | Robin | 4 |
| second | 1 | 2.00 | Jon | 5 |
+--------+------+------+-------+--------------+
And now, to recieve position of a particular :
mysql> set #pos = 0; select * from (select records.*, #pos:=#pos+1 as position
mysql> from records order by time desc) as t where uid = 3;
Output :
+--------+------+------+-------+----------+
| map | uid | time | name | position |
+--------+------+------+-------+----------+
| first | 3 | 4.90 | Robin | 3 |
| second | 3 | 2.10 | Robin | 4 |
+--------+------+------+-------+----------+

How to display the string values of a table refering to Two foreign ID keys on the main table

So I have this database
TABLE: rates
ID | FID | TID | RATE
---------------------------
1 | 1 | 2 | 0.3
2 | 1 | 3 | 1.2
3 | 1 | 4 | 4.5
4 | 2 | 1 | 1.3
5 | 2 | 3 | 3.3
6 | 2 | 4 | 4.4
TABLE: currencies
ID | Name | Symbol
---------------------
1 | Euro | E
2 | Pound | P
3 | Dollar | $
4 | CAD | C
So what I tried so far was
SELECT rates.*,
currencies.name,
currencies.symbol FROM RATES
JOIN CURRENCIES ON
(rates.fid = currencies.id)
Which worked but only for 1 column. I could not find a way to add more. Also I want to give a custom output name for each currency. So the final output should be:
ID | FromCurrency (FID) | ToCurrency (TID) | Rate
You need to do multiple joins, and as you are using the same table for both joins, give them an alias.
Something like this:
SELECT rates.ID,
a.name AS 'FromCurrency (FID)',
a.symbol AS 'FID Symbol',
b.name AS 'ToCurrency (TID)',
b.symbol AS 'TID Symbol',
rates.rate
FROM RATES
JOIN CURRENCIES AS a ON
(rates.fid = a.id)
JOIN CURRENCIES AS b ON
(rates.tid = b.id)
Here is a working example