Use another query with the same result - mysql

I have query:
SELECT Name, Surname, Telephone, PersonID FROM Client WHERE Telephone =
(SELECT MAX(Telephone) FROM Client) OR PersonID = (SELECT MAX(PersonID)
FROM Client);
With result:
| Name | Surname | Telephone | PersonID |
| Tyler | Henry | 998778781 | 38568215856 |
| Brooke | Thornton | 617196573 | 99412132661 |
What other query will give me the same result? I have to compare two queries in terms of optimization.

You can use a UNION optimization:
SELECT Name, Surname, Telephone, PersonID
FROM Client
WHERE Telephone = (SELECT MAX(Telephone) FROM Client)
UNION
SELECT Name, Surname, Telephone, PersonID
FROM Client
WHERE PersonID = (SELECT MAX(PersonID) FROM Client)
If a subset of selected columns is UNIQUE, the query will return the same result.
Given indexes on (Telephone) and (PersonID) the query should also be much faster on a big table. The reason is that MySQL is not able to use more than one index per table and (sub)query. Splitting the query in two subqueries will allow the engine to use both indexes.

You can get the max Telephone and max PersonID in a single query and then join it to the table:
SELECT c.Name, c.Surname, c.Telephone, c.PersonID
FROM Client c INNER JOIN (
SELECT MAX(Telephone) Telephone, MAX(PersonID) PesronID FROM Client
)m ON m.Telephone = c.Telephone OR m.PersonID = c.PersonID
or with NOT EXISTS:
SELECT Name, Surname, Telephone, PersonID FROM Client c
WHERE
NOT EXISTS (
SELECT 1 FROM Client
WHERE Telephone > c.Telephone
)
OR
NOT EXISTS (
SELECT 1 FROM Client
WHERE PersonID > c.PersonID
)

It depends on what indexes you have. Without INDEX(Telephone) and INDEX(PersonID) most of the answers will do table scan(s).
Here's another contender:
( SELECT Name, Surname, Telephone, PersonID
FROM Client
ORDER BY Telephone DESC LIMIT 1 )
UNION DISTINCT
( SELECT Name, Surname, Telephone, PersonID
FROM Client
ORDER BY PersonID DESC LIMIT 1 )
This may be faster because it does not have subqueries. However, it may give different results if two have the same Telephone or PersonID.
I suggest you run all the formulations against your data to see which is best.
If you want to discuss things further, please provide SHOW CREATE TABLE client and EXPLAIN SELECT ... -- both will give clues of what will/won't run faster.

Related

MySQL - joining a table to itself / sub queries

I'm asking for help on an assessment question I recently got wrong, I've tried a number of solutions and think I kind of know what I'm trying to do, but can't seem to figure out the syntax.
I have a table that looks like the below but with more records.
MyTable
ID Name DivisionID ManagerID Salary
123 John Smith 100 789 40000
456 Harold Johnson 101 null 60000
789 Vicky Brown 100 null 80000
and have to select the row of the person with the 3rd highest salary, which I had no problem with. However, I also need to return, instead of ManagerID, the Manager Name, which needs to be looked up from the same table.
I've tried the following solution which seems to be a bit inelegant and has to have the same query hard-coded within it, so not ideal for scaling or general use:
SELECT
table.ID,
Name,
DivisionID,
(SELECT
Name FROM table WHERE id=(
SELECT ManagerID FROM table ORDER BY Salary DESC LIMIT 2,1)
) AS ManagerName,
Salary
FROM table
ORDER BY Salary DESC LIMIT 2,1;
I think there may be some way of doing this with subqueries, e.g. first selecting a separate table within the query of just manager id and name, and then selecting from this - but I just can't seem to get the syntax right or get my head around it. I think it might also be possible with table aliases where I select two different results from the same table under different aliases and then join the two, but again just can't figure out how to do this. Below is what I've tried to do with aliases
SELECT
a.ID,
a.Name,
a.DivisionID,
b.Name AS ManagerName
a.Salary
FROM table a
INNER JOIN table b ON a.ManagerID=b.ID
ORDER BY Salary DESC LIMIT 2,1;
First of all, when asked to return the nth greatest/least value, you must ask back what to do in case of ties. They want the person with third highest salary, so with salaries 1000, 1000, 900, 900, 800, 800, 700, 600, 500, I'd suppose you want to return the persons that earn 800, because that is the third highest salary. If you just order the persons by salary, skip two and take the third, then you pick one of the persons with a salary of 900 arbitrarily, and 900 is not even the third highest, but the second highest salary.
In order to get the manager, simply join the table again. You should use an outer join for the case that an employee with the third highest salary is a manager themselves.
The straight-forward solution is to rank the rows with DENSE_RANK:
select *
from
(
select t.*, dense_rank() over (order by salary desc) as rnk
from mytable t
) employee
left join mytable manager on manager.id = employee.managerid
where employee.rnk = 3;
MySQL supports DENSE_RANK since version 8. In older versions you must look up the same table again. Select the distinct salaries and use your limit/offset clause on those saleries.
select *
from mytable employee
left join mytable manager on manager.id = employee.managerid
where employee.salary =
(
select distinct salary
from mytable
order by salary desc
limit 2, 1
);
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=6b17e369fcd4f99ddc6c268de15f08a1
Maka a subquersy form you code for the thrd and join selj join the table
This works also in MySQL 5.7 and earlier
CREATE TABLE MyTable (
`ID` INTEGER,
`Name` VARCHAR(14),
`DivisionID` INTEGER,
`ManagerID` VARCHAR(4),
`Salary` INTEGER
);
INSERT INTO MyTable
(`ID`, `Name`, `DivisionID`, `ManagerID`, `Salary`)
VALUES
('123', 'John Smith', '100', '789', '40000'),
('456', 'Harold Johnson', '101', 'null', '60000'),
('789', 'Vicky Brown', '100', 'null', '80000');
SELECT m1.ID, m1.Name, m1. DivisionID,m2. Name, m1.Salary
FROM (SELECT `ID`, `Name`, `DivisionID`, `ManagerID`, `Salary` FROM MyTable ORDER BY Salary DESC LIMIT 2,1) m1 JOIN MyTable m2 on m1.ManagerID = m2.ID
ID | Name | DivisionID | Name | Salary
--: | :--------- | ---------: | :---------- | -----:
123 | John Smith | 100 | Vicky Brown | 40000
db<>fiddle here

MySQL - SELECT Most occuring value (with several same occurrences)

I have been looking for a way to do a MySQL select to get all the most occuring values. All the solutions i have found where with using LIMIT 1, but this doesn't help if there are more than one value that occur the same amount of times. E.g:
customer
ID
FirstName
1
Bob
2
Tom
3
Bob
4
Robert
5
Tom
6
Timothy
The Select for most occuring FirstNames should result in:
FirstName
Bob
Tom
Since both occur twice.
I have tried the following:
SELECT FirstName FROM (
SELECT FirstName, COUNT(FirstName) as counter FROM customer
GROUP BY FirstName
HAVING counter = MAX(counter)
) s
But this doesn't seem to work, i would really appreciate a nudge in the right direction.
There can be different ways for doing this
you can try
1st
SELECT FIRSTNAME
FROM customer
GROUP BY FirstName Having count(FirstName) = (
SELECT COUNT(FirstName) FROM customer GROUP BY FirstName ORDER BY 1
DESC LIMIT 1);
2nd
with cte as
(
SELECT COUNT(FirstName) MaxCounter
FROM customer
Group By FirstName ORDER BY COUNT(FirstName) DESC LIMIT 1
)
SELECT c.FirstName
From customer c
Group BY FirstName
HAVING COUNT(FirstName) = (SELECT MaxCounter FROM cte)

How to display Unique record number using sql?

I have student table
id | name | zip
1 | abc | 1234
2 | xyz | 4321
3 | cde | 1234
And i want to only display unique zip code which is 4321. I don't want to display the 1 and 3 record number. So, how can i display the unique record only?
Thanks In Advance.
The following query will give you all zip codes which don't appear in duplicate:
SELECT zip
FROM yourTable
GROUP BY zip
HAVING COUNT(*) = 1
If you want to also get the full records then you can use the above as a subquery to filter the original table:
SELECT *
FROM yourTable t1
INNER JOIN
(
SELECT zip
FROM yourTable
GROUP BY zip
HAVING COUNT(*) = 1
) t2
ON t1.zip = t2.zip
or this:
SELECT *
FROM yourTable
WHERE zip IN
(
SELECT zip
FROM yourTable
GROUP BY zip
HAVING COUNT(*) = 1
)
This will also work
select
id, name, zip
from
(select
id, name, zip, count(zip) as countZip
from
student
group by zip) as subq
where
countZip = '1'
There is a DISTINCT modifier that forces a select to show only one value for a column that may appear several times. In your case it would be:
SELECT DISTINCT ZIP
FROM ...

SQL Query Two Tables at once and Loop Result as JSON objects

I'm not exactly sure what I am looking for here, so apologies if this has already been covered here, I'm not sure what I need to search for!
I have a MySQL database with a table called "Locations" which looks a bit like this
id | name | other parameters
1 | shop1 | blah
2 | shop2 | blah
etc
and a table of customer queries
id | customer | department
1 | john | shop2
2 | Joe | shop2
3 | James | shop1
4 | Sue | shop2
etc
I want to query this and return a JSON object that looks like this
{"location":"shop1","queryCount":"1"},{"location":"shop2","queryCount":"3"}
The location table can be added to with time, and obviously the customer queries will be to, so both need dynamic queries.
I tried this by getting a list of locations by a simple SELECT name from locations query, turning it into an array and then looping through that as follows:
For i = UBound(listofLocations) To 0 Step -1
locations.id = listofLocations(i)
locations.queryCount= RESULT OF: "SELECT COUNT(id) as recordCount from queries WHERE department=listofLocations(i)"
objectArray.Add(locations)
Next
This works, but it is inefficient calling the database through the loop, how do I avoid this?
Thanks
The inefficient is because you are using nested query,
you should use LEFT JOIN, and you just need single query
Here is the SQL:-
select l.name, count(*) as recordCount
from Locations as l
left join customer as c
on l.name = c.department
group by l.id;
And your schema is not very optimized.
Your schema for customer should be
id, customer, location_id <-- using name is redundant,
<-- which should represent in ID (location ID)
First, I must recommend that your "department" field be changed. If you change the spelling of a name in the locations table, your relationships break.
Instead, use the id from the location table, and set up a foreign key reference.
But that's an aside. Using your current structure, this is what I'd do in SQL...
SELECT
location.name,
COUNT(queries.id) AS count_of_queries
FROM
locations
LEFT JOIN
queries
ON queries.department = locations.name
GROUP BY
location.name
Using a LEFT JOIN ensures that you get EVERY location, even if there is no query for it.
Using COUNT(queries.id) instead of COUNT(*) gives 0 if there are no associated records in the queries table.
You can then loop through the result-set of one query, rather than looping multiple queries, and build your JSON string.
It is possible to build the string in SQL, but that's generally considered bad practice. Much better to keep you SQL about data, and you php/whatever about processing and presenting it.
Try this:
SELECT T1.name, (SELECT COUNT(*) FROM queries AS T2 WHERE T2.department = T1.name) AS number FROM locations AS T1;
I agree that your scheme is not proper, you should reference department by Id, not by name
I think you're just looking for "GROUP BY"
SELECT
department_id as location,
COUNT(id) as querycount
FROM
(table of customer queries)
GROUP BY
department_id
As for the database structure... If possible, I would change the tables a bit:
Location:
id | name | other parameters
Customer:
id | name | whatever
table_of_customer_queries:
id | customer_id | location_id
1 | 2 | 2
2 | 4 | 2
3 | 2 | 1
4 | 1 | 2
GROUP BY will only give you results for those departments that have queries. If you want all departments, the LEFT JOIN option mentioned earlier is the way to go.
First off, if your Locations table really stores Department information, rename it as such. Then, change Customer.department to be a fk reference to the Locations.id column (and renamed to department_id), not the name (so much less to get wrong). This may or may not give you a performance boost; however, keep in mind that the data in the database isn't really meant to be human readable - it's meant to be program readable.
In either case, the query can be written as so:
SELECT a.name, (SELECT COUNT(b.id)
FROM Customer as b
WHERE b.department_id = a.id) as count
FROM Location as a
If you don't change your schema, your answer is to do a group by clause that counts the number of queries to each location.
Creating tables like yours:
create table #locations (id integer, name varchar(20), other text)
create table #queries (id integer, customer varchar(20), department varchar(20))
insert into #locations (id, name, other) values (1, 'shop1', 'blah')
insert into #locations (id, name, other) values (2, 'shop2', 'blah')
insert into #queries (id, customer, department) values (1, 'john', 'shop2')
insert into #queries (id, customer, department) values (2, 'Joe', 'shop2')
insert into #queries (id, customer, department) values (3, 'James', 'shop1')
insert into #queries (id, customer, department) values (4, 'Sue', 'shop2')
Querying your data:
select
l.name as location,
count(q.id) as queryCount
from #locations as l
left join #queries as q on q.department = l.name
group by l.name
order by l.name
Results:
location queryCount
-------------------- -----------
shop1 1
shop2 3

How to get distinct record from mysql table?

I have a table student like this
id | name | zip
1 | abc | 1234
2 | xyz | 4321
3 | asd | 1234
I want to get all records but zip code should not be repeated. So In case of above table records, record No 1 and 2 should be fetched. Record No. 3 will not be fetched because it has a zip code which is already in record No. 1
SELECT DISTINCT fieldName FROM tableName;
The following query will only select distinct 'zip' field.
SELECT DISTINCT zip FROM student;
SELECT * FROM tableName GROUP BY fieldName;
The following query will select all fields along with distinct zip field.
SELECT * FROM student GROUP BY zip;
TRY
SELECT DISTINCT(zip),id,name FROM student;
OR
SELECT * FROM student GROUP BY zip;
Altough in MySQL you can get away with:
SELECT *
FROM student
GROUP BY zip
I would choose:
SELECT *
FROM student t
JOIN
( SELECT MIN(id) AS minid
FROM student
GROUP BY zip
) AS grp
ON grp.minid = t.id
Since presumably the other columns are of some interest....
SELECT y.*
FROM yourTable y,
(SELECT MIN(y2.id)
FROM yourTable y2
GROUP BY y2.zip) ilv
WHERE ilv.id=y.id;
(or you could use the max-concat trick)
update
Oracle have now removed the max concat trick from the linked page - but it is described elsewhere on the internet
Try Using
Select Distinct(zip),id,name group by zip;
Is there any problem if I use as this below?
select distinct zip,name,id from student;
select id, name, distinct(zip) from student;