MySQL selecting missing rows - mysql

I have a table with user info:
user_id | meta_key | meta_value
-----------+----------+-------------
1 | name | John
1 | surname | Doe
2 | name | Luke
3 | name | Jane
I want to select rows with a certain key, given a list of IDs.
If I do:
SELECT meta_value FROM table WHERE meta_key = 'surname' AND user_id IN(1,2,3)
MySQL will only return the info for user 1, since the others do not have surname.
I would like to build a query to return exactly the number of rows as the IDs passed, with NULL (or an empty string) if that particular user has no surname in the table.
I have tried to use IFNULL(meta_value, "") and also using IF, but it does not seem to help.
Any suggestions? I am sure it's a trivial thing but I can't seem to get around this issue.
Here's a SQLfiddle showing the issue: http://sqlfiddle.com/#!9/86eef2/6
My expected output would be:
Doe
NULL
NULL

Try this query:
SELECT DISTINCT user_id,
(SELECT meta_value FROM mytable B WHERE B.user_id = mytable.user_id AND META_KEY = 'surname') AS 'surname_meta_value'
FROM mytable
WHERE user_id IN(1,2,3)
For study purpose, this could be a faster option, in most cases, based on rlb.usa solution:
SELECT user_id,
GROUP_CONCAT(
(CASE WHEN meta_key = "surname"
THEN meta_value
ELSE ''
END) SEPARATOR '')
AS 'surname_meta_value'
FROM mytable WHERE user_id IN(1,2,3)
GROUP BY user_id

Related

Mysql: where after having

I have the following posts table:
+-------------------+
| name | type |
+---------+---------+
| admin-1 | type1 |
| user-1 | type1 |
+-------------------+
I want to group the posts by type and return them if the group has more than 1 element, and then filter this result based on the name.
This is the query is tried:
select count(*), type from posts where name like 'admin-%' group by type having count(*) > 1
However in my query, where is executed "before" the group by clause, so I don't get type1 in my result set.
How could I achieve what I want ?
Thanks
Try nested queries
SELECT * FROM posts
WHERE type IN (
SELECT type FROM posts GROUP BY type HAVING COUNT(*) > 1
) AND name LIKE 'admin-%'
You can do it with EXISTS:
select p.*
from posts p
where p.name like 'admin-%'
and exists (select 1 from posts where type = p.type and name <> p.name)
If you expect only 1 row for each type in the results, then you can do it with conditional aggregation:
select type,
max(case when name like 'admin-%' then name end) name
from posts
group by type
having count(*) > 1 and sum(name like 'admin-%') > 0

Can this query, which groups users by amount of comments posted, be simplified?

Two tables are used in this query, and all that matters in the result is the number of users which have or haven't posted any comments so far. The table user of course has the column id, which is the foreign key in the table comment, identified by the column user_id.
The first super-simple query groups users by whether or not they have any comments so far. It outputs two rows (a row with the user count who have comments, and a row with the user count who have no comments), with two columns (number of users, and whether or not they have posted any comments).
SELECT
COUNT(id) AS user_count,
IF( id IN ( SELECT user_id FROM `comment` ), 1, 0) AS has_comment
FROM `user`
GROUP BY has_comment
An example of how the output would look like here:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | 0 |
| 140 | 1 |
+------------+-------------+
Now here comes my question. I want slightly more information here, by grouping these users into 3 groups instead:
Users that have posted no comments
Users that have posted fewer than 10 comments
Users that have posted 10 or more comments
And the best query that I know how to write for this purpose is as follows, which works, but unfortunately runs 4 subqueries and has 2 derived tables:
SELECT
COUNT(id) AS user_count,
CASE
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count >= 10 ) AS a) THEN '10 or more'
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count < 10 ) AS b) THEN 'less than 10'
ELSE 'none'
END AS has_comment
FROM `user`
GROUP BY has_comment
An example of the output here would be something like:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | none |
| 130 | less than 10|
| 100 | 10 or more |
+------------+-------------+
This second query; can it be written more simply and efficiently, and still produce the same kind of result? (potentially maybe even be expanded into more of these kinds of "groups")
You can use two levels of aggregation:
select
count(*) no_users,
case
when no_comments = 0 then 'none'
when no_comments < 10 then 'less than 10'
else '10 or more'
end has_comment
from (
select
u.id,
(select count(*) from comments c where c.user_id = u.id) no_comments
from users u
) t
group by has_comment
order by no_comments
The subquery counts how many comments each user has (you could also express this with a left join and aggregation); then, the outer query classifies and count the users per number of comments.

SQL count occurrence by column

After some research I haven't found what I need and I thought I'll ask here. I'm currently trying to develop a advanced search mode for a application and I'm stuck with my task. Maybe you can help me. So imagine I have the following table:
ID | Name | Surname
1 | John | Mim
2 | Johnny | Crazy
3 | Mike | Something
4 | Milk | Milk
5 | Peter | IDontknow
6 | Mitch | SomeName
Then in my frontend, there's one input field. The input of that field will go trough the query in that way:
SELECT name, surname FROM people WHERE name LIKE 'input%' OR surname LIKE 'input%'
Now lets say my input is "Mi", so I'll have 3 columns match in the "name" column, and 2 in the surname. And that's what I'm looking for.
A count which ouputs the following:
Column | Count
Name | 3
Surname | 2
Is there a way to achieve this in only one query?
What I've tried so far:
I actually created the table above on my localhost in my database and tried different queries. Tried with SELECT count(name), count(surname), but that would output 3 for both counts. So I'm not even sure if that's possible in only one query.
use union all
SELECT 'name' as col, count(name) as cnt FROM people WHERE name LIKE 'input%'
union all
SELECT 'surname', count(surname) FROM people WHERE surname LIKE 'input%'
make customize group using case when
SELECT (case when name LIKE 'input%' then 'name'
else 'surname' end) as Column, count(*) as cnt
FROM people WHERE name LIKE 'input%' OR surname LIKE 'input%'
group by Column
Try this:
SELECT "Name" as Column, count(*) as Count FROM people WHERE name LIKE 'mi%'
UNION
SELECT "Surname" as Column, count(*) as Count FROM people WHERE surname LIKE 'mi%'
In Mysql booleans are evaluated as 1 or 0, so you can do this:
select 'Name' Column, sum(name LIKE 'input%') Count from people
union all
select 'Surname', sum(surname LIKE 'input%') from people
For Mysql 8.0+ you can avoid the double scan of the table with a CTE:
with cte as (
select
sum(name LIKE 'input%') namecounter,
sum(surname LIKE 'input%') surnamecounter
from people
)
select 'Name' Column, namecounter Count from cte
union all
select 'Surname', surnamecounter from cte
The solution without UNION[ ALL] of the people table:
SELECT
CASE cj.x WHEN 1 THEN 'Name' ELSE 'Surname' END AS `Column`,
CASE cj.x
WHEN 1 THEN COUNT(CASE WHEN Name LIKE concat(#input, '%') THEN 1 end)
ELSE COUNT(CASE WHEN Surname LIKE concat(#input, '%') THEN 1 END)
END `Count`
FROM people CROSS JOIN (SELECT 1 AS x UNION ALL SELECT 2) AS cj
WHERE Name LIKE concat(#input, '%') OR Surname LIKE concat(#input, '%')
GROUP BY cj.x;
Output for the Mi input:
| Column | Count |
+---------+-------+
| Name | 3 |
| Surname | 2 |
Test it online with SQL Fiddle.

Order MySql query using

I, I would like to make a query and sorting my members in a special way... Could someone help ?
Here's the problem.
I would like to select members in my table using a special sort order.
The profile fields values are stored in a table wp_bp_xprofile_data like this :
id | field_id | user_id | value
--------+----------+---------+----------
For example, I have 3 fields
NICKNAME (field_id = 1)
FIRSTNAME (field_id = 2)
LASTNAME (field_id = 3)
The table rows will look like this :
id | field_id | user_id | value
--------+----------+---------+----------
2544 1 100 fib
2545 2 100 john
2546 3 100 arenzich
2547 1 200 dog
2548 2 200 rick
2549 3 200 zarenburg
2550 1 300 fox
2551 2 300 frank
2552 3 300 arenzich
I've got this query to sort them using one field, for example to sort them by nickname alphabetically :
SELECT *
FROM wp_bp_xprofile_data u WHERE u.field_id = 1 ORDER BY u.value ASC
So they will be sorted like this : dog(200),fib(100), then fox(300).
Now, I would like to sort them not one but several fields (firstname and lastname; to differenciate people with same lastname) so that the query returns the users in this order :
frank arenzich (300), john arenzich (100), frank arenzich (200).
Any idea for doing this ?
Thanks A LOT !!!
This will probably need to be done by first pivoting this into a proper table by column, and then ordering that on multiple columns.
Note: this will not produce output in the same format as your original table, but is arguably a lot more flexible and useful as it combines all information about each user_id into a single row.
/* A column-wise pivot of NICKNAME, FIRSTNAME, LASTNAME */
SELECT
user_id,
MAX(CASE WHEN field_id = 1 THEN value ELSE null END) AS NICKNAME,
MAX(CASE WHEN field_id = 2 THEN value ELSE null END) AS FIRSTNAME,
MAX(CASE WHEN field_id = 3 THEN value ELSE null END) AS LASTNAME
FROM wp_bp_xprofile_data
GROUP BY user_id
/* Include the HAVING if you only want those who have both first & last names specified */
HAVING
FIRSTNAME IS NOT NULL
AND LASTNAME IS NOT NULL
/* Pivoted columns can then be treated in the ORDER BY */
ORDER BY
FIRSTNAME,
LASTNAME
Here is a demonstration...
It looks like this is a Wordpress table, so you may not be in a position to change its structure. But if you do have the option of modifying it, I would recommend changing the structure to resemble the pivot's output to begin with.

MySQL: Finding repeated names in my User table

I want to find all users whose name appears at least twice in my User table. 'email' is a unique field, but the combination of 'firstName' and 'lastName' is not necessarily unique.
So far I have come up with the following query, which is very slow, and I am not even sure it is correct. Please let me know a better way to rewrite this.
SELECT CONCAT(u2.firstName, u2.lastName) AS fullName
FROM cpnc_User u2
WHERE CONCAT(u2.firstName, u2.lastName) IN (
SELECT CONCAT(u2.firstName, u2.lastName) AS fullNm
FROM cpnc_User u1
GROUP BY fullNm
HAVING COUNT(*) > 1
)
Also, note that the above returns the list of names that appear at least twice (I think so, anyway), but what I really want is the complete list of all user 'id' fields for these names. So each name, since it appears at least twice, will be associated with at least two primary key 'id' fields.
Thanks for any help!
Jonah
SELECT u.*
FROM cpnc_User u JOIN
(
SELECT firstName, lastName
FROM cpnc_User
GROUP BY firstName, lastName
HAVING COUNT(*) > 1
) X on X.firstName = u.firstName AND x.lastName = u.lastName
ORDER BY u.firstName, u.lastName
There is no need to make up a concatenated field, just use the 2 fields separately
SELECT u.id, u.firstName, u.lastName
FROM cpnc_User u, (
SELECT uc.firstName, uc.lastName
FROM cpnc_User uc
GROUP BY uc.firstName, uc.lastName
HAVING count(*) > 1
) u2
WHERE (
u.firstName = u2.firstName
AND u.lastName = u2.lastName
)
To experiment I created a simple table with two columns a user id, and a name. I inserted a bunch of records, including some duplicates. Then ran this query:
SELECT
count(id) AS count,
group_concat(id) as IDs
FROM
test
GROUP BY
`name`
ORDER BY
count DESC
It should give you results like this:
+-------+----------+
| count | IDs |
+-------+----------+
| 4 | 7,15,4,1 |
| 2 | 2,8 |
| 2 | 6,13 |
| 2 | 14,9 |
| 1 | 11 |
| 1 | 10 |
| 1 | 3 |
| 1 | 5 |
| 1 | 17 |
| 1 | 12 |
| 1 | 16 |
+-------+----------+
You'll need to filter out the later results using something else.
SELECT u.id
, CONCAT(u.firstName, ' ', u.lastName) AS fullname
FROM cpnc_User u
JOIN
( SELECT min(id) AS minid
, firstName
, lastName
FROM cpnc_User
GROUP BY firstName, lastName
HAVING COUNT(*) > 1
) AS grp
ON u.firstName = grp.firstName
AND u.lastName = grp.lastName
ORDER BY grp.minid
, u.id
The ORDER BY grp.minid ensures that users with same first and last name stay grouped together in the output.
OK, you are doing a concatenation, then doing a compare on this, which essentially means that the DB is going to have to do something to every single row of the database.
How about a slightly different approach, you are holding surname and first name separately. So first select all those instances where surname appears > 1 time in your database. Now this has cut your population down dramatically.
Now you can do a compare on the first name to find out where the matches are.