Lookup table with best match query - sql

To lookup a country for a phone number prefix I running the following query:
SELECT country_id
FROM phonenumber_prefix
WHERE '<myphonnumber>' LIKE prefix ||'%'
ORDER BY LENGTH(calling_prefix) DESC
LIMIT 1
To query phone numbers from a table I run a query like:
SELECT phonenumber
FROM phonenumbers
Now I want to combine those query into one, to get countries for all phone numbers. I know that I could put the first query into a function e.g. getCountry() and then query
SELECT phonenumber, getCountry(phonenumber)
FROM phonenumbers
But is there also a way to to do this with joins in one query, I'm using postgresql 9.2?

You can do this with a correlated subquery:
SELECT phonenumber,
(SELECT country_id
FROM phonenumber_prefix pp
WHERE pn.phonenumber LIKE prefix ||'%'
ORDER BY LENGTH(calling_prefix) DESC
LIMIT 1
) as country_id
FROM phonenumbers pn;

This will give you list of numbers with corresponding country ids for the longest matching prefix:
SELECT * FROM (
SELECT
p.phonenumber, pc.country_id,
ROW_NUMBER() OVER (PARTITION BY phonenumber ORDER BY LENGTH(pc.prefix) DESC) rn
FROM
phonenumber_prefix pc INNER JOIN
phonenumbera p ON p.phonenumber LIKE pc.prefix || '%' ) t
WHERE t.rn = 1

You can do it using a join as so:
select phonenumber, countryId
from phonenumbers
inner join phonenumber_prefix on phonenumber like prefix||'%'
Be aware that this will be quite inefficient though as it won't be able to use indexes for the join.
You can also do it the other way around (by extracting the correct number of digits from the start of the phone number). That will improve things a bit as it will be able to use an index to look up the prefix, but it will still need to split each phonenumber.

Related

Count() how many times a name shows up in a table with the rest of info

I have read in various websites about the count() function but I still cannot make this work.
I made a small table with (id, name, last name, age) and I need to retrieve all columns plus a new one. In this new column I want to display how many times a name shows up or repeats itself in the table.
I have made test and can retrieve but only COLUMN NAME with the count column, but I haven't been able to retrieve all data from the table.
Currently I have this
select a.n_showsup, p.*
from [test1].[dbo].[person] p,
(select count(*) n_showsup
from [test1].[dbo].[person])a
This gives me all data on output but on the column n_showsup it gives me just the number of rows, now I know this is because I'm missing a GROUP BY but then when I write group by NAME it shows me a lot of records. This is an example of what I need:
You can use window functions, if you RDBMS supports them:
select t.*, count(*) over(partition by name) n_showsup
from mytable t
Alternatively, you can join the table with an aggregation query that counts the number of occurences of each name:
select t.*, x.n_showsup
from mytable t
inner join (select name, count(*) n_showsup from mytable group by name) x
on x.name = t.name
While the window function approach (#GMB's answer) is the right way to go, thinking through this from a subquery approach (like you were headed towards) would look something like:
select p.*, a.n_showsup
from [test1].[dbo].[person] p
INNER JOIN (
select name, count(*) n_showsup
from [test1].[dbo].[person]
GROUP BY name
) a ON p.name = a.name
This is VERY close to what you had, the difference is that we are grouping that subquery by name (so we get a count by name) and we can use that in the join criteria which we do with the ON clause on that INNER JOIN.
You should really never ever use a comma in your FROM clause. Instead use a JOIN.

Selecting a data from table while fetching the MAX value of a column

QUERY:
I have a table consisting columns like UserId and FirstName
What i want to do is:
I want to fetch the FirstName while getting the max if UserId
My novice attempt:
select FirstName from tblUsers where MAX(UserId)=#UserId
Which is showing the error of:
An aggregate may not appear in the WHERE clause unless it is in a
subquery contained in a HAVING clause or a select list, and the column
being aggregated is an outer reference.
A query without #UserId is also welcome
Try to use HAVING in following:
select FirstName
from tblUsers
group by FirstName
having MAX(UserId)=#UserId
UPDATE 1
If you want to get only 1 FirstName which have highest UserId you can use TOP 1 in following:
select top 1 FirstName
from tblUsers
order by UserId desc
UPDATE 2
I'm not recommending to use this solution, but If you want (for any reason) to use MAX(UserId) you can do something like:
select FirstName
from tblUsers
group by FirstName
having max(UserId ) = (select max(UserId) from tblUsers)
select FirstName from tblUsers group by FirstName having MAX(UserId)=#UserId
HAVING is like a where clause for aggregate expressions.
But that doesn't seem to make sense to me. You'll only get a row if #UserId is the maximum user Id in the table, so I don't quite understand what you want.
Aggregations are not supported in where clauses,since below is the logical querying phases
from
where
group by
having
select
order by
top
So in your case,SQL goes to your table and in where clause you asking result of aggregation which is unknown at that point of time,since SQL knows only table columns in this stage... since we use group by for aggregations,next operator having access to result of group by is having,so you have to refer any aggregations in having or using a derived table or a cte
Agree with some others that you should try to be more explicit about what you want exactly. After reading your second comment on Stanislovas' answer I think this is what you want:
SELECT FirstName
FROM tblUsers
WHERE UserId =
(SELECT MAX(UserId)
FROM tblUsers
)
The subquery first selects the maximum UserId value, which is then used as value to select the corresponding FirstName.

using group by operators in sql

i have two columns - email id and customer id, where an email id can be associated with multiple customer ids. Now, I need to list only those email ids (along with their corresponding customer ids) which are having a count of more than 1 customer id. I tried using grouping sets, rollup and cube operators, however, am not getting the desired result.
Any help or pointers would be appreciated.
SELECT emailid
FROM
( SELECT emailid, count(custid)
FROM table
Group by emailid
Having count(custid) > 1
)
I think this will get you what you want, if I am understanding you question correctly
select emailid, customerid from tablename where emailid in
(
select emailid from tablename group by emailid having count(emailid) > 1
)
Sounds like you would need to use HAVING
e.g
SELECT email_id, COUNT(customer_id)
From sometable
GROUP BY email_id
HAVING COUNT(customer_id) > 1
HAVING allows you to filter following the grouping of a particular column.
WITH email_ids AS (
SELECT email_id, COUNT(customer_id) customer_count
FROM Table
GROUP BY email_id
HAVING count(customer_id) > 1
)
SELECT t.email_id, t.customer_id
FROM Table t
INNER JOIN email_ids ei
ON ei.email_id = t.email_id
If you need a comma separated list of all of their customer id's returned with the single email id, you could use GROUP_CONCAT for that.
This would find all email_id's with at least 1 customer_id, and give you a comma separated list of all customer_ids for that email_id:
SELECT email_id, GROUP_CONCAT(customer_id)
FROM your_table
GROUP BY email_id
HAVING count(customer_id) > 1;
Assuming email_id #1 was assigned to customer_ids 1, 2, & 3, your output would look like:
email_id | customer_id
1 | 1,2,3
I didn't realize you were using MS SQL, there's a thread here about simulating GROUP_CONCAT in MS SQL: Simulating group_concat MySQL function in Microsoft SQL Server 2005?
SELECT t1.email, t1.customer
FROM table t1
INNER JOIN (
SELECT email, COUNT(customer)
FROM table
GROUP BY email
HAVING COUNT(customer)>1
) t2 on t1.email = t2.email
This should get you what your looking for.
Basically, as other ppl have stated, you can filter group by results with HAVING. But since you want the customerids afterwards, join the entire select back to your original table to get your results. Could probably be done prettier but this is easy to understand.
SELECT
email_id,
STUFF((SELECT ',' + CONVERT(VARCHAR,customer_id) FROM cust_email_table T1 WHERE T1.email_id = T2.email_id
FOR
XML PATH('')
),1,1,'') AS customer_ids
FROM
cust_email_table T2
GROUP BY email_id
HAVING COUNT(*) > 1
this would give you a single row per email id and comma seperated list of customer id's.

How to find max value and its associated field values in SQL?

Say I have a list of student names and their marks. I want to find out the highest mark and the student, how can I write one select statement to do that?
Assuming you mean marks rather than remarks, use:
select name, mark
from students
where mark = (
select max(mark)
from students
)
This will generally result in a fairly efficient query. The subquery should be executed once only (unless your DBMS is brain-dead) and the result fed into the second query. You may want to ensure that you have an index on the mark column.
If you don't want to use a subquery:
SELECT name, remark
FROM students
ORDER BY remark DESC
LIMIT 1
select name, remarks
from student
where remarks =(select max(remarks) from student)
If you are using a database that supports windowing,
SELECT name, mark FROM
(SELECT name, mark, rank() AS rk
FROM student_marks OVER (ORDER BY mark DESC)
) AS subqry
WHERE subqry.rk=1;
This probably does not run as fast as the mark=(SELECT MAX(mark)... style query, but it would be worth checking out.
In SQL Server:
SELECT TOP 1 WITH TIES *
FROM Students
ORDER BY Mark DESC
This will return all the students that have the highest mark, whether there is just one of them or more than one. If you want only one row, drop the WITH TIES specifier. (But the actual row is not guaranteed to be always the same then.)
You can create view and join it with original table:
V1
select id , Max(columName)
from t1
group by id
select * from t1
where t1.id = V1.id and t1.columName = V1.columName
this is right if you need Max Values with related info
I recently had a need for something "kind of similar" to this post and wanted to share a technique. Say you have an Order and OrderDetail table, and you want to return info from the Order table along with the product name associated with the highest priced detail row. Here's a way to pull that off without subtables, RANK, etc.. The key is to create and aggregate that combined the key and value from the detailed table and then just max on that and substring out the value you want.
create table CustOrder(ID int)
create table CustOrderDetail(OrderID int, Price money, ProdName varchar(20))
insert into CustOrder(ID) values(1)
insert into CustOrderDetail(OrderID,Price,ProdName) values(1,10,'AAA')
insert into CustOrderDetail(OrderID,Price,ProdName) values(1,50,'BBB')
insert into CustOrderDetail(OrderID,Price,ProdName) values(1,10,'CCC')
select
o.ID,
JoinAggregate=max(convert(varchar,od.price)+'*'+od.prodName),
maxProd=
SUBSTRING(
max(convert(varchar,od.price)+'*'+od.prodName)
,CHARINDEX('*',max(convert(varchar,od.price)+'*'+convert(varchar,od.prodName))
)+1,9999)
from
CustOrder o
inner join CustOrderDetail od on od.orderID = o.ID
group by
o.ID

Sql - Use the biggest value to select other data

I've a big doubt on the way to retrieve the biggest value of a table and use it in another query.
Consider this :
CREATE TABLE people
(
peopleID int NOT NULL,
cityID int NOT NULL
)
The following request gives me the number of people per city
SELECT peopleID, COUNT(*)
FROM people
GROUP BY cityID
Suppose I want the people list of the biggest city, I would write this request like :
SELECT people.peopleID, people.cityID
FROM people,
(
SELECT cityID, COUNT(*) AS "people_count"
FROM people
GROUP BY cityID
) g
WHERE people.cityID = g.cityID
HAVING people_count = MIN(people_count)
but doesn't work, what's the best way to achieve this request?
Thanks :)
This technique should work in most databases:
SELECT peopleID
FROM people
WHERE cityID =
(
SELECT cityID
FROM people
GROUP BY cityID
ORDER BY COUNT(*) DESC
LIMIT 1
)
LIMIT 1 is not standard SQL (the standard states that you should use FETCH FIRST 1 ROWS ONLY). See here for a list of how to fetch only the first row in a variety of databases:
Select (SQL) - Result limits
Edit: I misunderstood your question. I thought you meant what is a sensible way to perform this query that can easily be modified to work in almost any SQL database. But it turns out what you actually want to know is how to write the query so that it will work using exactly the same syntax in all databases. Even random databases that no-one uses that don't even properly support the SQL standard. In which case you can try this but I'm sure you can find a database where even this doesn't work:
SELECT peopleID, cityID
FROM people
WHERE cityID = (
SELECT MAX(cityID)
FROM (
SELECT cityID
FROM people
GROUP BY cityID
HAVING COUNT(*) =
(
SELECT MAX(cnt) FROM
(
SELECT cityID, COUNT(*) AS cnt
FROM people
GROUP BY cityID
) T1
)
) T2
)