mysql - please advice with correct regexp syntax - mysql

I am looking for correct query in mysql db. Let's consider I have tables:
users:
id | user_name | desc
--------------------------
1 | john | tall
2 | john | fat
3 | maria | pretty
items:
id | item_name | color
--------------------------
1 | trousers | red
2 | shoes | blue
3 | shoes | red
My search engine searches database to select result with query:
SELECT i.item_name, i.color, u.user_name, u.desc
FROM users u, items i
WHERE u.id = i.id
AND item_name REGEXP $keywords
AND user_name REGEXP $keywords
Variable $keywords is like this:
$keywords = explode(' ', $_POST['keywords']);
$keywords = implode('|', $keywords);
Now when $keywords = 'john trousers' it works all fine - i get user with id = 1. It's ok. But when I set $keywords = 'john' it returns empty string. I know even why - there is no 'john' in item_name so my AND condition returns false. The question is:
What is correct regexp expression to return user = 1 with $keywords = "john trousers" and if $keyword = 'john' get two lines - with id = 1 and id = 2?
Thanks in advance.

You can reverse the comparison, using the column value as the search value (and use LIKE instead of REGEXP):
SELECT i.item_name, i.color, u.user_name, u.desc
FROM users u, items i
WHERE u.id = i.id
AND $keywords like concat('%', item_name, '%')
AND $keywords like concat('%', user_name, '%')
Note that although this is a neat approach, it only works when keywords contains the whole value for the column. To make a more sophisticated comparison, you'd need to play with splitting up the values etc (you would have to do your own investigation)

Related

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.

Combining SELECT Statements to connect Inventory SKU array/list to Product Page SKUs

Is this possible with one statement?
Multiple tables in my database.
Using this SELECT to retrieve an array of sub-sku's (aka product listing sku's)
SELECT SKU FROM MasterSKU WHERE '4048' IN (AltSKU, SKU_1, SKU_2, SKU_3);
Returns
+-------------------+
| SKU |
+-------------------+
| 4048 |
| 4048-SET-15 |
| 4699-4528-4048-EA |
+-------------------+
The actual SKU is 4048 and the result set is all the product listing sku's connected to this one sku.
These SKU's are located in a few other tables and generated by an eCommerce API.
This is my second SELECT statement to return where (what channel) the SKUs are located and the associated ItemID.
(SELECT 'store_1' as channel, SKU, ItemID FROM Listings_store1 WHERE SKU = '4048')
UNION
(SELECT 'store_2', SKU, ItemID FROM Listings_store2 WHERE SKU = '4048')
UNION
(SELECT 'store_3', SKU, ItemID FROM Listings_store3 WHERE SKU = '4048')
UNION
(SELECT 'store_4', SKU, ItemID FROM Listings_store4 WHERE SKU = '4048');
This results in
+----------+-------------------+---------------+
| channel | SKU | ItemID |
+----------+-------------------+---------------+
| store_1 | 4048 | 5654515256454 |
| store_1 | 4048-SET-15 | 5654515234536 |
| store_3 | 4699-4528-4048-EA | 5654515243553 |
+----------+-------------------+---------------+
How I'm doing it now is in PHP to turn the first SELECT statement into an array, and then looping the results to retrieve the results. Something like for ($i = 0; $i < $channelskucount; $i++) { $channeldetectquery = "(SELECT 'store_1' as source, SKU FROM Listings_store1 WHERE SKU = '$channelskuarray[$i]') . . .
Is it possible, or feasible, to merge these two queries? As the first select statement normally acts as an array I loop over with the second in PHP, I'm not sure where to begin.
End output something like
+--------------+----------+-------------------+---------------+
| InventorySKU | channel | SKU | ItemID |
+--------------+----------+-------------------+---------------+
| 4048 | store_1 | 4048 | 5654515256454 |
| 4048 | store_1 | 4048-SET-15 | 5654515234536 |
| 4048 | store_3 | 4699-4528-4048-EA | 5654515243553 |
+--------------+----------+-------------------+---------------+
Any tips/help really appreciated. I realize my first table isn't exactly normalized, but trying to finalize my first big database project so I've learned and am learning a lot along the way.
It is certainly possible [to do it in one query] if you do a minor, and much needed, restructuring of your data. In fact, your issue perfectly illuminates the problem with the current structure. The listings for the different stores shouldn't each have their own data table. There should be a 'store' table which has columns for an id key, store name, store description, address, etc. Then you would just have a single 'Listings' table with one additional column, the look-up (a foreign key relationship) to the id key of the 'store' table, followed by the rest of whatever is already there (sku, ItemID, etc.). Shouldn't be hard to do, and you'll only have to do it once. You could create the table and just brute force it:
INSERT into Listings (StoreID, SKU, ItemID,...)
SELECT 1, SKU, ItemID,... FROM Listings1
INSERT into Listings (StoreID, SKU, ItemID,...)
SELECT 2, SKU, ItemID,... FROM Listings2
...
Or do it the inexcusably lazy way for which I should be severely reprimanded for even mentioning (you should definitely double-check your column data types afterward and add indexes):
SELECT 1 as StoreID, *
INTO Listings
FROM Listings1
UNION
SELECT 2, *
UNION
SELECT 3, *
UNION
SELECT 4, *
...
You then simply JOIN this table to the master_sku and store tables... Like this:
DECLARE #InventorySKU as varchar(20)
SET #InventorySKU = '4084'
SELECT #InventorySKU as InventorySKU, s.StoreName as channel, l.SKU, l.ItemID
FROM Listings l
INNER JOIN MasterSKU ms on ms.SKU = l.SKU
INNER JOIN Stores s on s.ID = l.StoreID
WHERE
ms.AltSKU LIKE '%' + #InventorySKU + '%' OR
ms.SKU_1 LIKE '%' + #InventorySKU + '%' OR
ms.SKU_2 LIKE '%' + #InventorySKU + '%' OR
ms.SKU_3 LIKE '%' + #InventorySKU + '%'
I hope this helps.

MySQL selecting missing rows

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

Mysql query from two tables as parent and child objects

I am creating an Angularjs app with two tables "Contact Group" and "Contact List". The contact list table items have corespondent id to items in the contact group.
What I want to achieve is a MySQL/Sqlite select query that will have each contact list items as a child object of its parent.
contact_group
id | title
------------
1 | friends
2 | mates
3 | family
contact_list
id | gID | name | number
--------------------------
1 | 1 | dave |0208500
2 | 1 | dan |4213839
3 | 1 | sans |5656434
4 | 2 | fola |1918982
5 | 3 | brian|2398343
6 | 5 | grace|0934232
Select query results examples (this is what i want to get)
id: 1
title: friends
child: {id:1, name:dave, number:0208500}, {id:2, name:dan, number:4213839},{id:3, name:sans, number:5656434}
I case there is another way of doing it, this what i want to achieve. I have created an accordion with title from the contact_group title. Under each accordion are the correspondent contacts from the contact_list.
I will be glad if anyone can help me. Thank you
In order to achieve this you will have to use MySql GROUP_CONCAT (documentation here: https://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat)
Using GROUP_CONCAT we will be able to join all matching rows from the contacts TB into a string in one column. With some more formatting of the text we glue together from the contacts TB and additional usage of CONCAT we will build the string in this column as a JSON string that you will then be able to work with on your application.
Here is the SQL:
SELECT
a.id,
a.title,
CONCAT(
'[',
GROUP_CONCAT(
CONCAT(
'{"id":',
b.id,
', "name":"',
b. `name`,
'", "number":"',
b.number,
'"}'
)
ORDER BY
b.`name`
), ']')
AS people
FROM
contact_group AS a
LEFT JOIN contact_list AS b ON a.id = b.gID
GROUP BY
a.id,
a.title
And this is the result we will get:
Don't forget to add Indexes and Foreign keys to your tables so query processing would be better.
SQLLITE Version:
in order to adjust the query to sqllite, there are 2 features that need to be changed.
SqlLite uses || operator instead of CONCAT and it does not support ORDER BY inside the GROUP_CONCAT. so for the SQLLITE DB the query will look like this:
SELECT
a.id,
a.title,
'[' ||
GROUP_CONCAT(
'{"id":' ||
b.id ||
', "name":"' ||
b. `name` ||
'", "number":"' ||
b.number ||
'"}'
) || ']'
AS people
FROM
contact_group AS a
LEFT JOIN contact_list AS b ON a.id = b.gID
GROUP BY
a.id,
a.title
MySQL Fiddle Demo
SqlLite Fiddle Demo
Try the below query,
select * from contact_group join contact_list on contact_group.id= contact_list.gid

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.