SQL Multiple values from same column with inner join - mysql

I am looking to join data from one table to another. The problem I have is in table two, the data I need is in the same column.
This is using wordpress/woocommerce DB so as you may be aware a lot of the user data is stored in the user meta table and you extract it by selecting the meta value where meta key = something
Rough data structure for user meta table
id | user_id | meta_key | meta_value
Now I select from table 1, inner join to table 2 and use the IN operator to select the meta values I want based on meta_key
select wp_xxxxxx_users.ID, wp_xxxxxx_users.user_email, wp_xxxxxxxxx.meta_value
from wp_xxxxxx_users
INNER JOIN wp_xxxxxx_usermeta on wp_xxxxxx_users.ID=wp_xxxxxx_usermeta.user_id
WHERE wp_xxxxxx_usermeta.meta_key IN ('last_name', 'first_name')
Now this returns all the data fine but in this format
id | user_email | meta_value
So what's wrong? Well I would like the meta_value columns to appear on the same row rather than multiple rows
I want the output to do the following
id | user_email | metavalue-first_name | metavalue-last_name
As it's currently outputting like so
1 | test#test.com | first name
1 | test#test.com | lastname name
2 | test1#test.com | first name
2 | test1#test.com | lastname name
I have a looked around relevant threads and seen people accomplishing this by Group By but I couldn't get it to work
Version is MySQL 5.7.27

You can do it if you group by ID, user_email and use conditional aggregation:
select
u.ID, u.user_email,
max(case m.meta_key when 'first_name' then m.meta_value end) first_name,
max(case m.meta_key when 'last_name' then m.meta_value end) last_name
from wp_xxxxxx_users u inner join wp_xxxxxx_usermeta m
on u.ID = m.user_id
where m.meta_key IN ('last_name', 'first_name')
group by u.ID, u.user_email

You didn't give any data.
So this tables
CREATE TABLE wp_xxxxxx_users (
ID int , user_email varchar(20));
INSERT INTO wp_xxxxxx_users (id,user_email) VALUES
(1,'test#mail.de'),
(2,'test1#mai.de'),
(3,'test2#mail.de');
CREATE TABLE wp_xxxxxx_usermeta
(`ID` int, `user_id` int, `meta_key` varchar(14), `meta_value` varchar(12))
;
INSERT INTO wp_xxxxxx_usermeta
(`ID`, `user_id`, `meta_key`, `meta_value`)
VALUES
(1, 1, 'last_name' , 'Dallas' ),
(2, 1, 'first_name' , 'Kermit' ),
(3, 2, 'last_name' , 'Huston' ),
(4, 2, 'first_name' , 'Piggy' ),
(5, 3, 'last_name' , 'Oklahoma' ),
(6, 3, 'first_name' , 'Beep' )
;
This Select statement
SELECT
wpu.ID
,wpu.user_email
, wpm.last_name
,wpm2.first_name
FROM wp_xxxxxx_users wpu
INNER JOIN
(SELECT meta_value as last_name, user_id FROM
wp_xxxxxx_usermeta Where meta_key = 'last_name') wpm
ON wpu.ID=wpm.user_id
INNER JOIN (SELECT meta_value as first_name, user_id from
wp_xxxxxx_usermeta WHERE meta_key = 'first_name') wpm2
ON wpu.ID=wpm2.user_id
;
or you can use
Select wpu.ID
,MAX(wpu.user_email),
MAX(CASE WHEN wpm.meta_key = 'last_name' THEN wpm.meta_value END) last_name,
MAX(CASE WHEN wpm.meta_key = 'first_name' THEN wpm.meta_value END) first_name
From wp_xxxxxx_users wpu inner join wp_xxxxxx_usermeta wpm
ON wpu.ID=wpm.user_id
WHERE wpm.meta_key in ('last_name','first_name')
GROUP by wpu.ID;
gives you following result
ID user_email last_name first_name
1 test#mail.de Dallas Kermit
2 test1#mai.de Huston Piggy
3 test2#mail.de Oklahoma Beep
You find here an example

Related

MySQL Include in any case a column even if it's also part of JOIN clause

this is my query, apart Wordpress prefix, it's just a matter of MySQL.
Scroll to WHERE CLAUSE when you will find all the options.
basically give me back all the results wether $searchString is either in the post_title, OR in the postmeta field called “operations_long_bio" OR “operations_short_bio"
this is working quite as expected but I would also like to have in the results, in any case ALWAYS the content of the column whose meta_key value is “operations_short_bio"
SELECT
posts.post_title AS name, posts.ID as ID,
postmeta.meta_value AS description
FROM `{$wpdb->base_prefix}posts` AS posts
LEFT JOIN `{$wpdb->base_prefix}postmeta` AS postmeta
ON posts.ID=postmeta.post_id
WHERE
posts.post_type = 'post'
AND
posts.post_status = 'publish'
AND
(
(
postmeta.meta_key
IN('operations_long_bio', 'operations_short_bio')
AND postmeta.meta_value LIKE '%$searchStr%'
)
OR ( posts.post_title LIKE '%$searchStr%' )
)
GROUP BY posts.ID
");
I tried forcing:
SELECT
posts.post_title AS name, posts.ID as ID,
postmeta.meta_value AS description
/***********/
CASE postmeta.meta_key='operations_short_bio' THEN postmeta.meta_value AS short_description END
...
but throws an error.
don't really actually know how to ask "give me ALSO the field postmeta.meta_value AS short_description WHEN it's postmeta.meta_key='operations_short_bio' :(
any hint?
tyvm!!
The Syntax is correctly in this form
CASE WHEN postmeta.meta_key='operations_short_bio' THEN postmeta.meta_value END AS short_description
And here is an example from your query, iha to adept it a litte, as this is not php
CREATE TABLE posts (ID int,post_title varchar(100), post_type varchar(10),post_status varchar(10))
CREATE tABLE postmeta (post_id int,meta_key varchar(100),meta_value varchar(100))
SELECT CASE WHEN postmeta.meta_key='operations_short_bio' THEN postmeta.meta_value END AS short_description
FROM postmeta
| short_description |
| :---------------- |
SELECT
posts.post_title AS name, posts.ID as ID,
postmeta.meta_value AS description,
MAX(CASE WHEN postmeta.meta_key='operations_short_bio' THEN postmeta.meta_value END) AS short_description
FROM `posts` AS posts
LEFT JOIN `postmeta` AS postmeta
ON posts.ID=postmeta.post_id
WHERE
posts.post_type = 'post'
AND
posts.post_status = 'publish'
AND
(
(
postmeta.meta_key
IN('operations_long_bio', 'operations_short_bio')
AND postmeta.meta_value LIKE '%$searchStr%'
)
OR ( posts.post_title LIKE '%$searchStr%' )
)
GROUP BY posts.ID,posts.post_title,postmeta.meta_value
name | ID | description | short_description
:--- | -: | :---------- | :----------------
db<>fiddle here

COnditional JOIN query in MySQL

I have two mysql tables
user:
|--------------------------------|
| id | name | type | ruser_type |
|--------------------------------|
| 1 | Admin | a | |
| 2 | | r | c |
|--------------------------------|
customer
|-------------------------|
| id | name | user_id |
|-------------------------|
| 1 | Sam | 2 |
|-------------------------|
If user.type is 'a' or 's', then its admin user whose name is in user table.
If user.type is 'r' and ruser_type is 'c', then its regular user which has a relation in customer table where customer.user_id = user.id
I want a query which would run a conditional join.
If user.type is 'a' or 's', then name would be fetched from user table.
If user.type is 'r' and and ruser_type is 'c', then name would be fetched from customer table with the JOIN condition customer.user_id = user.id.
For this, I have written a query like this:-
SELECT users.fname as adminFname, customers.fname as customerFname, users.type FROM users
LEFT JOIN customers ON (customers.user_id = users.id AND
(
(users.type = 'r' AND users.ruser_type = 'c')
OR users.type = 'a'
OR users.type = 's'
)
)
WHERE users.id = 1
Is there any possibility to optimize the query more?
Also, how can I write this query using Laravel eloquent?
FWIW, I find this marginally easier to read...
SELECT u.fname adminFname
, c.fname customerFname
, u.type
FROM users u
LEFT
JOIN customers c
ON c.user_id = u.id
WHERE u.id = 1
AND (
(u.type = 'r' AND u.ruser_type = 'c')
OR (u.type IN('a','s'))
)
I have written two sql query hope this will help you
SELECT CASE CU.type WHEN 'a' OR 's' THEN CU.name END AS name,
CASE WHEN CU.type = 'r' AND CU.ruser_type = 'c' THEN CR.name END AS cust_name, CU.type
FROM
user AS CU
LEFT JOIN customer AS CR ON CR.user_id = CU.id
In this you'll get result like this,
name cust_name type
Sam r
Admin a
and i have wrote another query like this,
SELECT CASE WHEN CU.type = 'a' OR 's' THEN CU.name ELSE CR.name END AS name, CU.type
FROM
user AS CU
LEFT JOIN customer AS CR ON CR.user_id = CU.id
In this you'll get result like this
name type
Sam r
Admin a
DB File Link
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=b02d931b68a4f70d8b4a84144c60a572
This will give you required result for all users:
SELECT u.fname adminFname
, c.fname customerFname
, u.type
FROM users u
LEFT JOIN customers c
ON (u.type = 'r' AND u.ruser_type = 'c' AND c.user_id = u.id)
Add where condition as required.
You can even simplify it further to get common firstName column in output:
SELECT COALESCE(u.fname, c.fname) firstName, u.type
FROM users u
LEFT JOIN customers c
ON (u.type = 'r' AND u.ruser_type = 'c' AND c.user_id = u.id)

MySQL UPDATE query using two tables

Using the following tables:
post_meta
-----
meta_id post_id key value
-----
22 4546 Advantages old value
23 4546 Article number 123
posts
-----
id status
-----
4546 pending
4547 publish
4548 publish
I am trying to write an UPDATE query to change 'old value' to 'new value', for posts that are set 'pending' which id corresponds to post_meta's post_id that has 'Article number' set to '123'.
I have no idea how to proceed ... any ideas?
If your intent is to change the value of the row with meta_key = 'Advantages' from 'old value' to 'new value' for the rows with meta_key = 'Article' and value = '123' for which there exists a corresponding post_id in posts with status = 'Pending' then I think the query you want is this:
update post_meta
join (
select pm.meta_id
from post_meta pm
join posts p on pm.post_id = p.id
where p.status = 'pending'
and pm.`key` = 'Advantages' -- or pm.value = 'old value'
and exists (
select 1
from post_meta
where post_id = pm.post_id
and `key` = 'Article number' and value = '123')
) t on post_meta.meta_id = t.meta_id
set value = 'new value';
Sample SQL Fiddle
With your sample data this would leave the post_meta table looking like this:
| meta_id | post_id | key | value |
|---------|---------|----------------|-----------|
| 22 | 4546 | Advantages | new value |
| 23 | 4546 | Article number | 123 |
Updated query
UPDATE post_meta pm, (
SELECT pm2.meta_id
FROM posts p
INNER JOIN post_meta pm1 ON p.id=pm1.post_id
INNER JOIN post_meta pm2 ON p.id=pm2.post_id
WHERE
pm1.key='Article Number' AND pm1.value = 123
AND pm2.key='Advantages' AND pm2.value = 'old value'
AND p.status = 'pending'
) p
SET pm.value = 'new value'
WHERE pm.meta_id=p.meta_id

Consolidate many queries into one

I'm looking to achieve my goals as described below using one single query, as opposed to multiple queries as I currently have to use.
The problem I am having is that data from the wp_usermeta table is stored as a meta_key/meta_value in 2x columns, as opposed to each type of data having it's own column. While the reasoning for this makes sense, it does mean I'm stumped at this point.
The database in question is for a WordPerss site, should anyone wish to replicate this.
My goals
For each user who has at least one image/video post I need to grab the following details -
+-----------------------+---------------+------------------------------------------------+
| Description | Table | Column |
+-----------------------+---------------+------------------------------------------------+
| User ID | `wp_users` | `ID` |
| Display Name | `wp_users` | `display_name` |
| First Name | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'first_name' |
| Description | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'description' |
| Facebook profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'facebook' |
| Google+ profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'google_plus' |
| Twitter profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'twitter' |
+-----------------------+---------------+------------------------------------------------+
My current solution
First I select the ID and display name of all users who have at least 1 image/video post (this is one single query) -
SELECT DISTINCT `wp_users`.`ID`, `wp_users`.`display_name`
FROM `wp_posts`
INNER JOIN `wp_users`
WHERE `wp_posts`.`post_type` = "attachment"
AND `wp_posts`.`post_status` = "inherit"
AND `wp_posts`.`post_author` = `wp_users`.`ID`
AND (
`wp_posts`.`post_mime_type` LIKE "image%"
OR `wp_posts`.`post_mime_type` LIKE "video%"
)
Next, I have to loop through each result from the first query and select the first name, description and social media links for each (this is one example, for user_id = 2) -
SELECT `wp_usermeta`.`meta_key`, `wp_usermeta`.`meta_value`
FROM `wp_usermeta`
WHERE `wp_usermeta`.`user_id` = 2
AND (
`wp_usermeta`.`meta_key` = 'first_name'
OR `wp_usermeta`.`meta_key` = 'description'
OR `wp_usermeta`.`meta_key` = 'facebook'
OR `wp_usermeta`.`meta_key` = 'google_plus'
OR `wp_usermeta`.`meta_key` = 'twitter'
);
As part of the loop where the second query is run I also have to insert those results into the results from the first. All of this not only means extra code, but leads to a longer execution time.
My full code can be found here - http://pastebin.com/P2jv3WTt
Is this goal achievable with MySQL, or is it simply not something it is able to do? Thanks.
What I've tried
I have tried to join to the wp_usermeta table as follows, outputting the results as a named column, but there is an issue - the only results are for users who have an entry for every single meta_key in the wp_usermeta table, but some don't (no Twitter profile link, for example).
SELECT DISTINCT
u.`ID`,
u.`display_name`,
m1.`meta_value` AS first_name,
m2.`meta_value` AS description,
m3.`meta_value` AS facebook,
m4.`meta_value` AS google_plus,
m5.`meta_value` AS twitter
FROM `wp_users` u
JOIN `wp_posts` p
JOIN `wp_usermeta` m1 ON (m1.user_id = u.id AND m1.meta_key = 'first_name')
JOIN `wp_usermeta` m2 ON (m2.user_id = u.id AND m2.meta_key = 'description')
JOIN `wp_usermeta` m3 ON (m3.user_id = u.id AND m3.meta_key = 'facebook')
JOIN `wp_usermeta` m4 ON (m4.user_id = u.id AND m4.meta_key = 'google_plus')
JOIN `wp_usermeta` m5 ON (m5.user_id = u.id AND m5.meta_key = 'twitter')
WHERE p.`post_type` = "attachment"
AND p.`post_status` = "inherit"
AND p.`post_author` = u.`ID`
AND (
p.`post_mime_type` LIKE "image%"
OR p.`post_mime_type` LIKE "video%"
)
ORDER BY RAND()
Most straightforward solution would be like this. Simply do a join for each meta-value you need.
By using LEFT OUTER JOIN instead of INNER JOIN to join the meta-data, you will still keep users for which some of these meta-data don't exist.
SELECT DISTINCT u.ID, u.DisplayName,
fn.meta_value AS firstname, fb.meta_value AS facebook, (etc.)
FROM wp_Users u
INNER JOIN wp_posts p ON p.post_author = u.ID
LEFT OUTER JOIN wp_usermeta fn ON fn.UserId = u.ID AND fn.meta_key = 'first_name'
LEFT OUTER JOIN wp_usermeta fb ON fb.UserId = u.ID AND fb.meta_key = 'facebook'
--- (etc. for each meta column you need to join)
WHERE p.post_type = 'attachment'
AND p.post_status = 'inherit'
AND (p.post_mime_type LIKE 'image%'
OR p.post_mime_type LIKE 'video%')
I would go with something like this:
SELECT DISTINCT
`wp_users`.`ID`,
`wp_users`.`display_name`,
`wp_usermeta`.`meta_key`,
`wp_usermeta`.`meta_value`
FROM
`wp_posts`
INNER JOIN
`wp_users` ON (`wp_posts`.`post_author` = `wp_users`.`ID`)
LEFT JOIN
`wp_usermeta` ON (`wp_usermeta`.`user_id` = `wp_users`.`ID`)
WHERE
`wp_posts`.`post_type` = 'attachment'
AND `wp_posts`.`post_status` = 'inherit'
AND (`wp_posts`.`post_mime_type` LIKE 'image%'
OR `wp_posts`.`post_mime_type` LIKE 'video%')
AND `wp_usermeta`.`meta_key`
IN ('first_name', 'description', 'facebook', 'google_plus', 'twitter');
LEFT JOIN must return you posts and users even if there are no meta associated with users.
are you looking for something like this?
SELECT meta.user_id, meta.meta_key, meta.meta_value
FROM wp_usermeta meta JOIN wp_posts posts
ON posts.post_author = meta.user_id
WHERE posts.post_type = "attachment"
AND posts.post_status = "inherit"
AND posts.post_mime_type RLIKE "^(image|video)"
AND meta_key IN ('first_name', 'description','facebook','google_plus','twitter');
Keep in mind that wp_posts.post_author and wp_usermeta.user_id should be indexes, otherwise this query will be slow.
Better yet if you have a joined index on wp_usermeta: user_key (user_id, meta_key). You can add this with the following command:
ALTER TABLE wp_usermeta ADD KEY user_key(user_id, meta_key);
PS If you also need display_name from the wp_users table, then you have to join it too:
SELECT meta.user_id, users.display_name, meta.meta_key, meta.meta_value
FROM wp_usermeta meta JOIN wp_posts posts
ON posts.post_author = meta.user_id
JOIN wp_users users
ON users.id = meta.user_id
WHERE posts.post_type = "attachment"
AND posts.post_status = "inherit"
AND posts.post_mime_type RLIKE "^(image|video)"
AND meta_key IN ('first_name', 'description','facebook','google_plus','twitter');

MySQL: SELECT value WHERE subquery returns a wildcarded value

Confusing title...my apologies.
What I've got is a table with two related rows. I need to get the value of a column in one row based on the value of a column in another row.
Given the following postmeta table:
+----------+------------+---------------------------------------------------+--------------------+
| meta_id | post_id | meta_key | meta_value |
+----------+------------+---------------------------------------------------+--------------------+
| 6917 | 661 | member_categories_0_member_categories_name | 11 |
+----------+------------+---------------------------------------------------+--------------------+
| 6918 | 661 | member_categories_0_member_categories_description | First description |
+----------+------------+---------------------------------------------------+--------------------+
| 6919 | 661 | member_categories_1_member_categories_name | 12 |
+----------+------------+---------------------------------------------------+--------------------+
| 6920 | 661 | member_categories_1_member_categories_description | Second description |
+----------+------------+---------------------------------------------------+--------------------+
I need to get the meta_value category description based on the meta_value category ID and the post_id. For instance, if my category ID is 11 and my post_id is 661, I should get "First description".
I thought about using a subquery to get the meta_key corresponding with a meta_value of '11', but I don't know how to find the description based on the counter inside 'member_categories_x_member_categories_name'.
Unfortunately, I don't have control over the name of the meta_key. I got as far as this simple query, which returns 'member_categories_0_member_categories_name'. How do I use that value to find 'First description'?
SELECT pm.meta_key
FROM postmeta pm
WHERE pm.meta_value = "11"
AND pm.post_id = 661
Here's the SQL for the table:
CREATE TABLE `postmeta` (
`meta_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`post_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
`meta_key` VARCHAR(255) NULL DEFAULT NULL,
`meta_value` LONGTEXT NULL,
PRIMARY KEY (`meta_id`),
INDEX `post_id` (`post_id`),
INDEX `meta_key` (`meta_key`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=30814;
INSERT INTO `postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES (6917, 661, 'member_categories_0_member_categories_name', '11');
INSERT INTO `postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES (6918, 661, 'member_categories_0_member_categories_description', 'First description');
INSERT INTO `postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES (6919, 661, 'member_categories_1_member_categories_name', '12');
INSERT INTO `postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES (6920, 661, 'member_categories_1_member_categories_description', 'Second description');
This is ugly, but then, that table is so far from normalized that any answer short of scanning every row is going to be something like this:
SELECT pm2.meta_value
FROM postmeta pm1
JOIN postmeta pm2
ON pm1.post_id = pm2.post_id
AND SUBSTRING(pm1.meta_key,1,LENGTH(pm1.meta_key)-5) = SUBSTRING(pm2.meta_key,1,LENGTH(pm2.meta_key)-12)
AND pm1.meta_key like '%_name'
AND pm2.meta_key like '%_description'
WHERE pm1.meta_value = 11
AND pm1.post_id = 661
The idea is to join the table to itself, linking rows that have the same post_id, and whose meta_key is 'similar' -- it needs to be exactly the same, except that one ends with _name and one ends with _description.
I am not sure if I have fully understood what you want.
This gives the full "table", if you want to add a "where" clause you can add it at the end but for best performance, add inside the subqueries.
select
n.*, d.meta_value as meta_value_descrip
from
(
select left(meta_key, 34) as xjoin, p.*
from postmeta p where right(meta_key, 4) = 'name'
) as n
left join -- or inner join
(
select left(meta_key, 34) as xjoin, p.*
from postmeta p where right(meta_key, 11) = 'description'
) as d
on n.post_id = d.post_id and n.xjoin = d.xjoin
This's raw, but quicker than extract the number from the string,
SELECT pm2.meta_key, pm2.meta_value
FROM postmeta pm, postmeta pm2
WHERE pm.meta_value = "11"
AND pm.post_id = 661
and pm.meta_value = pm2.meta_value
AND pm.post_id = pm2.post_id
and substring(pm.meta_key,30) = substring(pm2.meta_key,30)
I apologizes, I made some mistakes, follows the correct one:
SELECT pm2.meta_value
FROM postmeta pm, postmeta pm2
WHERE pm.meta_value = "11"
AND pm.post_id = 661
and pm.meta_id <> pm2.meta_id
AND pm.post_id = pm2.post_id
and substring(pm.meta_key,19,20) = substring(pm2.meta_key,19,20)
As I told this's raw but uses (IMHO) less CPU from DB and made a better uses of indexes. Of course this is safe only for 20 digits.
of course if you have more than 2 meta_key (name and description) you should add:
AND pm2.meta_key like '%_description'
AND pm.meta_key like '%_name'
This index is unuseful (meta_key is subject of elaboration):
INDEX `meta_key` (`meta_key`)
better add this one:
INDEX `idx_post_id_meta_value` (`post_id`,`meta_value`)