Prefix best match MySQL - mysql

That's my scenario.
I have 4 tables: records, providers, routing_domain, domains.
domains: id, name (something like 'example.com')
providers: id, name (something like 'TLC')
records: phone_number (varchar), provider_id (foreign key to
providers)
routing_domain: provider_id (foreign key to providers), domain_id
(foreign key to domains) and prefix (varchar).
Example of tables:
mysql> select id,name from domains;
+----+-----------------------+
| id | name |
+----+-----------------------+
| 1 | e164.arpa |
| 3 | example.com |
| 0 | localhost.localdomain |
| 4 | luigi.it |
| 2 | tim.it |
+----+-----------------------+
mysql> select id,name from providers where id in (9,10);
+----+----------+
| id | name |
+----+----------+
| 9 | TIM |
| 10 | VODAFONE |
+----+----------+
mysql> select * from routing_domain;
+----+--------+-----------+-------------+
| id | prefix | domain_id | provider_id |
+----+--------+-----------+-------------+
| 3 | 3932 | 4 | 9 |
| 1 | 39320 | 2 | 9 |
| 2 | 39321 | 3 | 10 |
+----+--------+-----------+-------------+
Now,
given a phone_number '39320xxxxxxx' with provider_id 9, i need to get
domain_id=2;
given a phone_number '39321xxxxxxx' with provider_id 9, i need to get
domain_id=4;
So, given a certain phone_number '3932xxxxxxxx' with provider_id=9, i need to do some bestmatch searching. Starting to search prefix with 6 chars, if not match, try with 5 chars and so on, until 3 chars (393).
I managed to get the correct domain from phone_number searching only from a prefix to 5 chars.
Something like:
select * FROM records r
left join routing_domain rd on rd.prefix like SUBSTRING(r.phone_number,1,5) and r.provider_id = rd.provider_id
left join providers p on p.id = rd.provider_id
left join domains d on d.id = rd.domain_id
where r.name = 'xxxxxxxxxxxx';
Any advices to do this bestmatch ?
Thank you so much!
Update
I tried with this:
select * FROM records r
left join routing_domain rd on on r.phone_number like concat(rd.prefix, '%') and r.provider_id = rd.provider_id
left join providers p on p.id = rd.provider_id
left join domains d on d.id = rd.domain_id
where r.name = 'xxxxxxxxxxxx';
Now, if i search for '39325xxxxxxx', there is a match with prefix '3932',
but if i search for '39320xxxxxxx', both prefixes will match and the search returns 2 rows.

One option is to have a sub-query that gives you the longest prefix matching provider_id and prefix. Something like this:
select domain_id from routing_domain
where
provider_id = 9
and '39321xxxxxxx' like concat(prefix, '%')
and length(prefix) =
( select max(length(prefix))
from routing_domain
where
provider_id = 9
and '39321xxxxxxx' like concat(prefix, '%')
)
See my fiddle here.

http://sqlfiddle.com/#!9/2e36df/10
SELECT r.*,
MAX(IF(rd.prefix = LEFT(r.phone_number,5),rd.prefix,
IF(rd.prefix = LEFT(r.phone_number,4),rd.prefix,
IF(rd.prefix = LEFT(r.phone_number,3),rd.prefix,''))))
FROM records r
LEFT JOIN routing_domain rd
ON r.provider_id = rd.provider_id
GROUP BY r.id
And to make it closer to your attempt:
http://sqlfiddle.com/#!9/2e36df/17
SELECT t.*, p.*, d.*
FROM (
SELECT r.*,
MAX(IF(rd.prefix = LEFT(r.phone_number,5),rd.id,
IF(rd.prefix = LEFT(r.phone_number,4),rd.id,
IF(rd.prefix = LEFT(r.phone_number,3),rd.id,'')))) as rd_id
FROM records r
LEFT JOIN routing_domain rd
ON r.provider_id = rd.provider_id
#WHERE r.phone_number = '393xxxxxxxxxx'
GROUP BY r.id
) t
LEFT JOIN routing_domain rd
ON t.rd_id = rd.id
LEFT JOIN providers p
ON p.id = rd.provider_id
LEFT JOIN domains d
ON d.id = rd.domain_id

Related

How to join 5 tables with IF conditions

I want to join 5 tables through book_id. I tried:
SELECT booked_room_info . * , booking_info . * , hotel_info.name as
hotelname,hotel_info.address as hoteladdress,hotel_info.contact as
hotelcontact , personal_info . *,room_registration.room_name as
roomname,room_registration.price as roomprice,room_registration.price as
roomprice,room_registration.description as
roomdescription,room_registration.image as roomimage
FROM booked_room_info
JOIN booking_info ON booking_info.booking_id = booked_room_info.booking_id
JOIN hotel_info ON hotel_info.id = booking_info.hotel_id
JOIN personal_info ON personal_info.booking_id = booked_room_info.booking_id
JOIN room_registration ON room_registration.hotel_id = booking_info.hotel_id
WHERE booked_room_info.booking_id= 1
But if i have 2 booking on booking_id = 1 but it fetched 10 result. I think I should make a if condition like. If (booked.room_type && book_info.hotel_id) is same then only fetch rows from room_registration but how can I aacomplish it.
booked_room_info
------
id | booking_id | room_type | check_in | check_out
1 | 1 | delux | 2015/1/2 | 2015/1/5
booking_info
---------
id | booking_id | hotel_id | user_id
1 | 1 | 2 | 1
hotel_info
----------
id | name | address | user_id
2 |palm hotel | newyork | 1
personal_info
-------------
id |full_name | address | nationality | booking_id
1 | sushil stha | new york | smth | 1
room_registration
-----------------
id | room_name | price | image | hotel_id | user_id
1 | delux | 1000 |room.jpg | 2 | 1
There is an error in your last join:
JOIN room_registration ON room_registration.hotel_id = booking_info.hotel_id
You are joining these tables using hotel_id column. There are probably 5 rows in room_registration table with hotel_id=2. So after joining you receive 2*5=10 rows instead of expected 2.
So you should join with this table in different way.
If you want to join every row with exacly one row from room_registration table then you have to specify more precise join condition.
To join room_registration table properly you may add room_registration_id column to booking_info table. Then you can join using:
JOIN room_registration ON room_registration.id = booking_info.room_registration_id
Use INNER JOIN's, removed the duplicate field.
SELECT
bri.*,
bi.*,
hi.name AS hotelname, hi.address as hoteladdress, hi.contact AS hotelcontact,
pi.*,
rr.room_name AS roomname, rr.price AS roomprice, rr.description AS roomdescription, rr.image AS roomimage
FROM booked_room_info bri
INNER JOIN booking_info bi ON bri.booking_id = bi.booking_id
INNER JOIN hotel_info hi ON bi.hotel_id = hi.id
INNER JOIN personal_info pi ON bri.booking_id = pi.booking_id
INNER JOIN room_registration rr ON bi.hotel_id = rr.hotel_id
WHERE bri.booking_id = 1

MySQL: How to query for a column and include information from related columns in the same table?

In MySQL, I have a table like this:
+-----------------------+
| Assets |
+-----------------------+
| Id | Name | RootId |
+----+---------+--------+
| 1 | Asset A | 1 |
+----+---------+--------+
| 2 | Asset B | 2 |
+----+---------+--------+
| 3 | Asset C | 3 |
+----+---------+--------+
| 4 | Asset D | 2 |
+----+---------+--------+
| 5 | Asset E | 3 |
+----+---------+--------+
| 6 | Asset F | 3 |
+----+---------+--------+
Not the greatest table structure, I know...but I'm stuck with it for now.
I am trying to write a single query that, given an Id value, will return a RootId and RootName ONLY if there are exactly two (2) rows with the same RootId. Otherwise those columns should be NULL.
So, using the table above, if given an Id of 4 the query should return:
+----------------------------------+
| Assets |
+----------------------------------+
| Id | Name | RootId | RootName |
+----+---------+--------+----------+
| 4 | Asset D | 2 | AssetB |
+----+---------+--------+----------+
But if given any other Id value, such as 5, it should return:
+----------------------------------+
| Assets |
+----------------------------------+
| Id | Name | RootId | RootName |
+----+---------+--------+----------+
| 5 | Asset E | null | null |
+----+---------+--------+----------+
Any help on this would be greatly appreciated. I think it will require a subquery with a COUNT and possibly a GROUP BY, but I'm really not sure how to articulate it...
Thanks in advance!
The following should implement this logic:
select id, name,
(case when cnt = 2 then rootid end) as rootid,
(case when cnt = 2 then ari.name end) as rootname
from assets a join
(select rootid, count(*) as cnt
from assets a
group by rootid
) ri
on a.rootid = ri.rootid left join
assets ari
on a.rootid = ari.id
where id = 4;
You can also do this as:
select a.id, a.name,
(case when a.cnt = 2 then a.rootid end) as rootid,
(case when a.cnt = 2 then ari.name end) as rootname
from (select a.*,
(select count(*) from assets a2 where a2.rootid = a.rootid) as cnt
from assets a
where id = 4
) a left join
assets ari
on a.rootid = ari.id;
Without the full aggregation, this will perform better.
Here is a SQL Fiddle illustrating them.
Something like this will return the specified resultset:
SELECT a.Id
, a.Name
, IF(q.cnt=2,q.RootId,NULL) AS RootId
, IF(q.cnt=2,q.RootName,NULL) AS RootName
FROM Assets a
JOIN ( SELECT COUNT(1) AS cnt
, r.RootId
, r.RootName
FROM Assets r
JOIN Assets s
ON s.RootId = r.RootId
WHERE r.Id = 4
GROUP BY r.RootId, r.RootName
) q
ON q.Id = a.Id
If its possible for RootId to be NULL, then you'd want to use a LEFT [OUTER] JOIN, and include a . If you want to consider a NULL value for RootId as matching another NULL value, then replace the equality comparator with the null-safe equality comparison operator, <=>
Adding those two tweaks gives a more robust solution:
SELECT a.Id
, a.Name
, IF(q.cnt=2,q.RootId,NULL) AS RootId
, IF(q.cnt=2,q.RootName,NULL) AS RootName
FROM Assets a
JOIN ( SELECT COUNT(1) AS cnt
, r.RootId
, r.RootName
FROM Assets r
LEFT -- lef outer join
JOIN Assets s
ON s.RootId <=> r.RootId -- nullsafe equality
WHERE r.Id = 4
GROUP BY r.RootId, r.RootName
) q
ON q.Id = a.Id

MySQL combine 3 queries in to 1

I'm trying to combine 3 MySQL queries in to one.
This is what I am using to grab my news entries (with pagination) :
SELECT A.sid,
A.title,
A.time,
A.bodytext,
A.author,
A.url
FROM news A
INNER JOIN
(SELECT sid
FROM news
WHERE approved=1
ORDER BY sid DESC LIMIT $start, $limit) B USING (sid)
Now, I've recently added a comments feature and would like to get the total amount of comments for each "sid"
The part that gets confusing for me is that I need to match "sid" (from news.news) with "page_id" (from comments.pages) to grab it's unique "id".
+----+---------+
| id | page_id |
+----+---------+
| 1 | 87 |
| 2 | 86 |
| 41 | 85 |
| 3 | 84 |
| 13 | 83 |
+----+---------+
Now with that unique "id", I need to query "comments.comments" and match it with "page_id" column and count(*) how many comments it has - WHERE is_approved = 1
SELECT page_id,is_approved,count(*) FROM comments WHERE page_id = $id and is_approved = 1;
+---------+-------------+----------+
| page_id | is_approved | count(*) |
+---------+-------------+----------+
| 1 | 1 | 2 |
+---------+-------------+----------+
Is this possible?
Edited my response to conform to the updated question:
SELECT A.sid, A.title, A.time, A.bodytext, A.author, A.url,
D.page_id, D.num_comments
FROM news.news A
INNER JOIN (SELECT sid
FROM news
WHERE approved=1
ORDER BY sid desc
LIMIT $start, $limit) B USING (sid)
LEFT JOIN comments.pages C ON A.sid = C.id
LEFT JOIN (SELECT page_id,is_approved,count(*) as num_comments
FROM comments.comments
WHERE is_approved = 1) D ON C.page_id = D.page_id

getting latest data in one to many relation table

i would like to fetch data from table a,b,c but order by most recent data of table response
table casework has this structure ( simplified):
casework_id | problem | user_id
------------+-----------+-------
1 | Problem1 | 1
2 | Problem2 | 2
3 | Problem3 | 1
4 | Problem4 | 3
table user has this structure ( simplified):
user_id | name
--------+-----------------
1 | peter
2 | Sam
3 | Tom
4 | Steve
table response has this structure ( simplified):
response_id | response | casework_id | created
------------+-----------+--------------+-------
1 | responce1 | 1 | 2012-10-14 11:28:31
2 | responce2 | 1 | 2012-9-10 11:28:31
3 | responce3 | 1 | 2012-9-2 11:28:31
4 | responce4 | 3 | 2012-8-3 11:28:31
4 | responce5 | 3 | 2012-8-2 11:28:31
I am looking the query to fetch data order by latest responce and group by casework_id
I. e. required out put is
casework_id | problem | name | responce | created
------------+-----------+-------+-----------+---------
1 | Problem1 | peter | responce1 | 2012-10-14 11:28:31
2 | Problem2 | Sam | Null | Null
3 | Problem3 | peter | responce4 | 2012-8-3 11:28:31
4 | Problem4 | Tom | Null | Null
I would be most grateful if one of you kind people could point me in the right direction.
You can use the following:
select c.casework_id,
c.problem,
u.name,
r2.response,
r1.created
from casework c
left join user u
on c.user_id = u.user_id
left join
(
select max(created) created, casework_id
from response r
group by casework_id
) r1
on c.casework_id = r1.casework_id
left join response r2
on r1.created = r2.created
and r1.casework_id = r2.casework_id
See SQL Fiddle with Demo
If you want to include both the user that created the casework and then who responsed, then you will want to join on the user table twice:
select c.casework_id,
c.problem,
u1.name CreatedByName,
r2.response,
r1.created,
u2.name ReponseName
from casework c
left join user u1
on c.user_id = u1.user_id
left join
(
select max(created) created, casework_id
from response r
group by casework_id
) r1
on c.casework_id = r1.casework_id
left join response r2
on r1.created = r2.created
and r1.casework_id = r2.casework_id
left join user u2
on r2.user_id = u2.user_id
See SQL Fiddle with demo
I have not tested it, but it might give you an idea
select c.casework_id, c.problem,
(select name from user u where u.user_id = c.user_id ),
(select r.reponse from response r where r.casework_id = c.casework_id ORDER BY r.created DESC LIMIT 1),
(select r.created from response r where r.casework_id = c.casework_id ORDER BY r.created DESC LIMIT 1),
from casework c
SELECT responce.casework_id, problem, name, responce, created
FROM responce
JOIN
(SELECT casework_id, problem, name
FROM casework JOIN user
ON casework.userid=user.userid) AS A
ON responce.casework_id=A.casework_id
ORDER BY responce, responce.casework_id
Try this
select c.caseword_id, c.problem, u.name, response.response, responce.created from asework c inner join user u on u.user_id = c.user_id left outer join select casework_id from response having max(created) group by casework_id) responsedata on responsedata.casework_id = c.casework_id

SQL conditional join challenge

I have 2 tables stores and shares in a mysql database. I am trying to avoid an IN clause. Please see below for more details
select id, user_id from stores where user_id =7;
+----+---------+
| id | user_id |
+----+---------+
| 36 | 7 |
| 37 | 7 |
select stores_id,share_id from shares where share_id=7;
+-----------+----------+
| stores_id | share_id |
+-----------+----------+
| 15 | 7 |
| 38 | 7 |
Now I run this
SELECT stores.id
FROM stores
WHERE user_id = 7
UNION
(SELECT stores.id
FROM stores
WHERE id IN (SELECT stores_id
FROM shares
WHERE share_id = 7));
To get the below result:
+----+
| id |
+----+
| 36 |
| 37 |
| 15 |
| 38 |
+----+
QUESTION
How can I rewrite the query so that I don't use the IN key word.?
You can use either EXISTS:
WHERE EXISTS
( SELECT 1
FROM shares
WHERE share_id = 7
AND stores_id = stores.id
)
or JOIN:
JOIN shares
ON shares.stores_id = stores.id
AND shares.share_id = 7
(Note that the JOIN potentially returns multiple copies of some stores, but because UNION implies SELECT DISTINCT, that won't actually affect your final result-set.)
This can help to you:
select stores.id from stores where user_id = 7
UNION
select s1.id from stores s1
inner join shares s2
on s2.share_id = 7
and s1.id = s2.stores_id;
If all you need is the ID, this will do just fine...
SELECT stores.id
FROM stores
WHERE user_id = 7
UNION
SELECT stores_id as id
FROM shares
WHERE share_id = 7
But if you need some data from the other columns in the stores table, INNER JOIN or EXISTS will be your best bet.
A left join should accomplish what you are looking for:
select distinct stores.id
from stores
left join shares on stores.id = shares.stores_id
where stores.user_id = 7
or shares.share_id = 7