SQL - Inner Join and Columns to Rows - mysql

Using MySQL and I have two tables similar to the following. Both actually have more rows and more columns but for ease of reading, I've only put a few
wp_ahm_files
ID | Field_A | Field_B | Field_C
--------------------------------
69 | ABC | DEF | GHI
wp_ahm_filemeta
pID in this table refers to the ID of the table above
ID | pID | Name | Value
---------------------------------
25 | 69 | Version | 12345
26 | 69 | Expiry | 29/08/1981
How do I bring back a resultset such as
ID | Field_A | Field_B | Field_C | Version | Expiry
-------------------------------------------------------
69 | ABC | DEF | GHI | 12345 | 29/08/1981

You should be able to JOIN to your wp_ahm_filemeta twice to get the result:
select f.id,
f.field_a,
f.field_b,
f.field_c,
m1.value version,
m2.value expiry
from wp_ahm_files f
left join wp_ahm_filemeta m1
on f.id = m1.pid
and m1.name = 'Version'
left join wp_ahm_filemeta m2
on f.id = m2.pid
and m2.name = 'Expiry';
See SQL Fiddle with Demo. The key is to place a filter on the JOIN condition to return the rows with the specific name value you want.
You could also convert the rows of data into columns by using an aggregate function with a CASE expression:
select f.id,
f.field_a,
f.field_b,
f.field_c,
max(case when m.name = 'Version' then m.value end) version,
max(case when m.name = 'Expiry' then m.value end) expiry
from wp_ahm_files f
left join wp_ahm_filemeta m
on f.id = m.pid
group by f.id, f.field_a, f.field_b, f.field_c;
See SQL Fiddle with Demo

Something like
select a.*, version.value version, expiry.value expiry
from a join
( select * from b where name = 'Version' ) version
on version.table_a_id = a.id
join
( select * from b where name = 'Expiry' ) expiry
on expiry.table_a_id = a.id

Related

Prefix best match 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

Select each row of table except where the id is not the maximum value for a given foreign key

Given a table such as the following called form_letters:
+---------------+----+
| respondent_id | id |
+---------------+----+
| 3 | 1 |
| 7 | 2 |
| 7 | 3 |
+---------------+----+
How can I select each of these rows except the ones that do not have the maximum id value for a given respondent_id.
Example results:
+---------------+----+
| respondent_id | id |
+---------------+----+
| 3 | 1 |
| 7 | 3 |
+---------------+----+
Something like this should work;
SELECT respondent_id, MAX(id) as id FROM form_letters
group by respondent_id
MySQL fiddle:
http://sqlfiddle.com/#!2/5c4dc0/2
There are many ways of doing it. group by using max(), or using not exits and using left join
Here is using left join which is better in terms of performance on indexed columns
select
f1.*
from form_letters f1
left join form_letters f2 on f1.respondent_id = f2.respondent_id
and f1.id < f2.id
where f2.respondent_id is null
Using not exits
select f1.*
from form_letters f1
where not exists
(
select 1 from form_letters f2
where f1.respondent_id = f2.respondent_id
and f1.id < f2.id
)
Demo
Here's how I would do it. Get the max id in a sub query, then join it back to your original table. Next, limit to records where the ID does not equal the max id.
Edit: Opposite of this. limit to records where the ID = MaxID. Code changed below.
Select FL.Respondent_ID, FL.ID, A.Max_ID
From Form_Letters FL
left join (
select Respondent_ID, Max(ID) as Max_ID
from Form_Letters
group by Respondent_ID) A
on FL.Respondent_ID = A.Respondent_ID
where FL.ID = A.Max_ID

How to select data given several rows match?

I have a configuration table and a users table.
users:
| id | name |
|----|----------|
| 0 | Bob |
| 1 | Ted |
| 2 | Sam |
config:
| user_id | name | value |
|---------|----------|-------|
| 0 | a | 11 |
| 0 | b | 2 |
| 0 | c | 54 |
| 1 | a | 5 |
| 1 | b | 3 |
| 1 | c | 0 |
| 2 | a | 1 |
| 2 | b | 74 |
| 2 | c | 54 |
I normalized the configuration this way since the config will be of unknown amount, but I will have to query users based on this config, so it couldn't be stored in a serialized form.
My issue is how do I find users based on multiple rows? For instance:
Select all users with a > 4 and b < 5
This should return Bob and Ted.
Using groups:
SELECT u.name
FROM users u
JOIN config c
ON c.user_id = u.id
GROUP BY u.name
HAVING MAX(c.name = 'a' AND c.value > 4)
AND MAX(c.name = 'b' AND c.value < 5)
Using joins:
SELECT u.name
FROM users u
JOIN config a
ON a.user_id = u.id
AND a.name = 'a'
AND a.value > 4
JOIN config b
ON b.user_id = u.id
AND b.name = 'b'
AND b.value < 5
I prefer the JOIN method, as you can name each JOIN after the property and collect the conditions in the JOIN. You also don't have to worry about the GROUPs which makes it more flexible for aggregates.
A bonus over EXISTS is that you can easily access the properties of the config if you require further joins/calculations.
Try this:
SELECT us.name
FROM USERS us
WHERE EXISTS (SELECT name FROM CONFIG WHERE name='a' AND value>4 AND user_id=us.id)
AND EXISTS (SELECT name FROM CONFIG WHERE name='b' AND value<5 AND user_id=us.id)
Alternatively, you can use two joins:
SELECT us.name
FROM USERS us, CONFIG c1, CONFIG c2
WHERE us.id=c1.user_id
AND c1.name='a'
AND c1.value<4
AND us.id=c2.user_id
AND c2.name='b'
AND c2.value>5
select u.name from users as u inner join config as c on u.id = c.user_id where (c.name = "a" and c.value > 4) or (c.name = "b" and c.value < 5);
you will need two exists statements for this:
SELECT * FROM users u
WHERE EXISTS ( SELECT 1
FROM config
WHERE user_id = u.id
AND name = 'a' AND value > 4)
AND EXISTS ( SELECT 1
FROM config
WHERE user_id = u.id
AND name = 'b' AND value < 5)
the following query achieves the result, as does not rely joining between the tables in any way.
to include more of "name" you could add another OR clause inside the HAVING.
NOTE: This has been tested on the W3Schools WebSQL Interface
SELECT
value,
name,
user_id
FROM config
GROUP BY value, name, user_id
HAVING
(
(value > 4 AND name = 'a')
OR (value < 5 AND name = 'b')
)
After ditching the grouping (thanks Arth)
SELECT
value,
name,
user_id
FROM config
WHERE
(
(value > 4 AND name = 'a')
OR (value < 5 AND name = 'b')
)

combining 2 select statements with different number of columns, where 1st statement is already using JOIN

I'm trying to combine 2 select statements with different number of columns.
The 1st statement is this:
SELECT s.id, s.date_sent, m.sam_subject, m.sam_msg_id, (SELECT
COUNT(id) FROM tbl_something WHERE schedule_id = s.id) AS
total_recipients FROM tbl_something2 AS s INNER JOIN
tbl_something3 AS m ON s.message_id = m.sam_msg_id ORDER BY
s.date_sent DESC
The 2nd statement:
SELECT * FROM sms_something4 WHERE status = '0' ORDER BY id DESC
the table output for the 1st statement:
id date_sent sam_subject sam_msg_id total_recipients
1 1372880628 e-Newsletter 2 2
output for 2nd:
id | subject | sent | failed | date_sent | data_sent | data_failed | message | sam_uid from | select_members | status | from_no
11 | test | 2 | 0 | 1372881670 | 639176286411,639224588324 | | | | | | 0 | 0
any suggestions on how would i be able to combine these two statements?
my target output is
id | subject | sent | failed | date_sent | sam_subject | total_recipients | date_sent for email
sam_msg_id can be ignored.
Thank you.
here is basic that you need to have .. you might have to trouble shoot. add column as you need.
SELECT *
FROM (
SELECT s.id, s.date_sent, m.sam_subject, m.sam_msg_id, (SELECT COUNT(id)
FROM tbl_something
WHERE schedule_id = s.id) AS total_recipients
FROM tbl_something2 AS s
INNER JOIN tbl_something3 AS m
ON s.message_id = m.sam_msg_id
) as tbl
INNER JOIN (SELECT * FROM sms_something4 WHERE status = '0') as tbl2
ON tbl2.subject = tbl.sam_subject
and tbl.date_sent=tbl2.date_sent
and tbl.total_Recipients = tbl2.sent+ tbl2.failed
ORDER BY tbl.date_sent DESC
As AJP said you can just do this:
SELECT s.id, a.subject,a.sent, s.date_sent, m.sam_subject,
(SELECT COUNT(id) FROM tbl_something WHERE schedule_id = s.id) AS total_recipients
FROM tbl_something2 AS s
INNER JOIN tbl_something3 AS m ON s.message_id = m.sam_msg_id
INNER JOIN
(
SELECT * FROM sms_something4 WHERE status = '0' ORDER BY id DESC
) a on a.subject = m.sam_subject and a.date_sent = s.date_sent
ORDER BY
s.date_sent DESC

Mysql to select rows group by with order by another column

I am trying to select the rows from a table by 'group by' and ignoring the first row got by sorting the data by date. The sorting should be done by a date field, to ignore the newest entry and returning the old ones for the group.
The table looks like
+----+------------+-------------+-----------+
| id | updated on | group_name | list_name |
+----+------------+----------------+--------+
| 1 | 2013-04-03 | g1 | l1 |
| 2 | 2013-03-21 | g2 | l1 |
| 3 | 2013-02-26 | g2 | l1 |
| 4 | 2013-02-21 | g1 | l1 |
| 5 | 2013-02-20 | g1 | l1 |
| 6 | 2013-01-09 | g2 | l2 |
| 7 | 2013-01-10 | g2 | l2 |
| 8 | 2012-12-11 | g1 | l1 |
+----+------------+-------------+-----------+
http://www.sqlfiddle.com/#!2/cec99/1
So, basically, I just want to return ids (3,4,5,6,8) as those are the oldest in the group_name and list_name. Ignoring the latest entry and returning the old ones by grouping it based on group_name and list_name
I am not able to write sql for this problem. I know order by will not work with group by. Please help me in figuring out a solution.
Thanks
And also, is there a way to do this without using subqueries?
Something like the following to get only the rows that are the minimum date for a specific row:
select a.ID, a.updated_on, a.group_name, list_name
from data a
where
a.updated_on <
(
select max(updated_on)
from data
group by group_name having group_name = a.group_name
);
SQL Fiddle: http://www.sqlfiddle.com/#!2/00d43/10
Update (based on your reqs)
select a.ID, a.updated_on, a.group_name, list_name
from data a
where
a.updated_on <
(
select max(updated_on)
from data
group by group_name, list_name having group_name = a.group_name
and list_name = a.list_name
);
See: http://www.sqlfiddle.com/#!2/cec99/3
Update (To not use Correlated Subquery but Simple subquery)
Decided correlated subquery is too slow based on: Subqueries vs joins
So I changed to joining with a aliased temporary table based on nested query.
select a.ID, a.updated_on, a.group_name, a.list_name
from data a,
(
select group_name, list_name , max(updated_on) as MAX_DATE
from data
group by group_name, list_name
) as MAXDATE
where
a.list_name = MAXDATE.list_name AND
a.group_name = MAXDATE.group_name AND
a.updated_on < MAXDATE.MAX_DATE
;
SQL Fiddle: http://www.sqlfiddle.com/#!2/5df64/8
You could try using the following query (yes, it has a nested join, but maybe it helps).
SELECT ID FROM
(select d1.ID FROM data d1 LEFT JOIN
data d2 ON (d1.group_name = d2.group_name AND d1.list_name=d2.list_name AND
d1.updated_on > d2.updated_on) WHERE d2.ID IS NULL) data_tmp;
CORRECTION:
SELECT DISTINCT(ID) FROM
(select d1.* FROM data d1 LEFT JOIN
data d2 ON (d1.group_name = d2.group_name AND d1.list_name=d2.list_name AND
d1.updated_on < d2.updated_on) WHERE d2.ID IS NOT NULL) date_tmp;
SELECT DISTINCT y.id
FROM data x
JOIN data y
ON y.group_name = x.group_name
AND y.list_name = x.list_name
AND y.updated_on < x.updated_on;