MySQL: Query for two values' presence - php

I have a table with fields id, text, and title. There may be multiple rows with the same title (title is not unique). Additionally, id is the primary key.
I need only to know if there are at least one row with title="A" that contains the string "aaa" in text, and additionally at least one row with title="B" that contains the string "bbb" in text.
This is what I tried:
mysql> SELECT (SELECT text FROM table WHERE title = 'A') as aText, (SELECT text FROM table WHERE title = 'B') as bText;
I had planned on parsing the values of aText and bText for the strings "aaa" and "bbb" respectfully with PHP. However, I have two issues:
1) Major issue: Due to title not being unique, the subqueries may return multiple rows. MySQL breaks on that happening with this error:
ERROR 1242 (21000): Subquery returns more than 1 row
2) Minor issue: The reason that I am parsing in PHP is to avoid using MySQL's LIKE operator. Would I be better off doing the parsing of the string right there in MySQL as such:
mysql> SELECT (SELECT text FROM table WHERE title = 'A' AND text LIKE '%aaa%') as aText, (SELECT text FROM table WHERE title = 'B' AND text LIKE '%bbb%') as bText;

SELECT (SELECT count(*) FROM table WHERE title='A') AS A_count,
(SELECT count(*) FROM table WHERE title='B') AS B_count
WHERE (A_count > 0) AND (B_count > 0)
this would return 1 row (with the counts) and no rows if either'A' or 'B' is not present.
A subquery returning data to a field in a parent query can only ever return a single value. You're basically replacing a fieldname with a query result, which means the query result has to behave the same as a normal table field would. Hence the "returns more than 1 row" error - you're returning all of the rows where 'A' or 'B' matches, which is not a single field - it's a column.

If you just wanted to know if those rows existed, you could do something like this:
SELECT COUNT(1)
FROM table
WHERE (title = 'A' AND text = '%aaa%')
OR (title = 'B' AND text = '%bbb%')
-- EDIT --
Based on your comment, you may want to try the following query instead:
SELECT COUNT(1), title
FROM table
WHERE (title = 'A' AND text = '%aaa%')
OR (title = 'B' AND text = '%bbb%')
GROUP BY title
Check for two rows, both with a COUNT(1) of greater than zero.

You can self-join:
SELECT t1.text as `atext`, t2.text as `btext`
table as t1, table as t2
WHERE t1.title = 'A' AND t1.text LIKE '%aaa%' AND t2.title = 'B' AND t2.text LIKE '%bbb%';
If you want to do your parsing in php just leave off the where statement, but if what you're doing in php is equivalent to mysql's like statement I don't know why you wouldn't do it in the query.

It is probably not good form to answer one's own question, but for the sake of the fine archives at the expense of my karma:
The query that I employed is:
SELECT count(*) FROM (
SELECT DISTINCT title FROM (
SELECT title, text FROM table WHERE title IN ('A', 'B')
)
AS filtered
WHERE (title='A' AND text LIKE '%aaa%')
OR (title='B' AND text LIKE '%bbb%')
)
AS allResults;
The innermost query gets everything with a good title, the next query gets distinct titles for the real results. This setup ensures that the LIKE clause will not run on the entire table. I then wrap all that in a count, and if if equals 2 then I know that each condition was met.
Thank you everyone who contributed, I gained quite some insight from your answers!

Related

How can I UPDATE a column in a record when that information is the result of a GROUP CONCAT in the same table?

I cannot determine if this should be something nested or a JOIN.
Each record has three values from the column value across from their name in the column variable.
I have a successful GROUP CONCAT that combines them into a single text string.
BUT, I need to UPDATE/INSERT the concat value into another variable=>value pair.
Such as this. I want each person to have "cityStateZip" in the value for `cust_abc'.
I believe it's "INSERT" and not update since none of the records have anything in cust_abc yet. But, I'm not quite sure if it shouldn't be UPDATE.
id_member
variable
value
1234
cust_abc
"should show citystatezip"
1234
cust_a
city
1234
cust_b
state
1234
cust_c
zip
I can't get past the error of having the target table being the same in the SELECT FROM.
I was attempting things like:
INSERT INTO smgqg_themes.value (my group concat) WHEN `variable` = "cust_abc"
This is the group concat that works fine to make the string:
SELECT GROUP_CONCAT
( `value`
order by case variable
when 'cust_a' then 1
when 'cust_b' then 2 else 3 end
SEPARATOR '')
output
FROM smfqg_themes
WHERE `id_member` IN (1234, 1235, 1236, etc)
AND `variable` IN ('cust_a', 'cust_b', 'cust_c')
You can INSERT into a the same table you SELECT from in the same SQL statement. I do this all the time.
The syntax is:
INSERT INTO <tablename> (<columns...>)
SELECT ...;
The SELECT must return the same columns that you name in the INSERT clause, in the same order.
If you want an existing row to be updated, but still insert a row if one is missing with the 'cust_abc' varible, then you can use INSERT...ON DUPLICATE KEY UPDATE:
INSERT INTO smgqg_themes (id_member, variable, value)
SELECT id_member, 'cust_abc',
GROUP_CONCAT(`value`
ORDER BY CASE variable
WHEN 'cust_a' THEN 1
WHEN 'cust_b' THEN 2 ELSE 3 END
SEPARATOR '') as v
FROM smgqg_themes
WHERE `id_member` IN (1234, 1235, 1236)
AND `variable` IN ('cust_a', 'cust_b', 'cust_c')
GROUP BY `id_member`
ON DUPLICATE KEY UPDATE value = VALUES(value);
Demo: https://dbfiddle.uk/OPJQ1ZXF

How to display duplicated results only in MYSQLI?

I want to display only the duplicated results in a PHP page, not their count and there is a condition where certain field can't be empty. What is the mysqli query for displaying each duplicated result and not grouping them so it displays all the duplicates in 1 row rather than displaying each duplicate in a single row?
This is for a reporting panel on full PHP website. I have tried some queries like SELECT col1,col2 FROM table Where col3!='' GROUP BY col2 HAVING COUNT(col1) > 1; But this will display the duplicated results in 1 row, if I have 3 duplicates with same col2 the query returns only one due to the GROUP BY col2 clause, I tried GROUP BY col2,col1 but now HAVING COUNT(col1) > 1 can never be true since it is impossible to have same value of col1.
I expect the output of the query to display each duplicated result in a row and not group them in 1 row only. In other words to display the same 3 duplicated results having same col2 but different other columns values and not display only their 1 result.
All results:
Query I tried:
Try this
SELECT * FROM usersl Where hash!='' AND user_fullName IN (SELECT user_fullName FROM usersl HAVING COUNT(user_fullName) > 1);
This will give you result as you want
I think you should add one more column in group by clause and that column should be Primary ID column then you will got your desired result.
SELECT col1,col2 FROM table Where col3!='' GROUP BY col2,pk_id;
Try this. I think this helps you.
SELECT u1.* FROM usersl AS u1
INNER JOIN (SELECT user_fullName FROM usersl WHERE hash != '' GROUP BY (user_fullName) HAVING COUNT(user_fullName) > 1) AS u2 ON u1.user_fullName = u2.user_fullName
WHERE u1.hash!=''
First find all ID's of these fields:
$ids = $db->queryColumn(
"SELECT GROUP_CONCAT(id SEPARATOR ',') FROM table WHERE col3 != '' GROUP BY col2 HAVING COUNT(col1) > 1"
);
Then make array of ID's:
$idsToFind = [];
foreach ($ids as $idString) {
array_push($idsToFind, array_walk('trim', explode(',', $idString)));
}
Now do another query where you find elements by ID:
$db->queryAll(
'SELECT col1, col2 FROM table WHERE id IN :idArray',
$this->escapeArray($idsToFind)
);

Combine results of multiple SQL queries (UNION not possible because column names are different)

I want to make a notifications page which shows notifications about a variety of things, like new followers, new likes, new comments etc. I want to display a list that shows all of these things in chronological order.
My tables look like this:
COMMENT
1 comment__id
2 comment__user_id
3 comment__snap__id
4 comment__text
5 comment_add_time
LIKE
1 like__id
2 like__user__id
3 like__snap__id
4 like__like_time
FOLLOW
1 follow__id
2 follower__user__id
3 followed__user__id
4 follow__follow_time
5 follow__request_status
I would load the followers of a user with a query like this:
try {
$select_followers_query = '
SELECT follow.follower__user__id, follow.followed__user__id, follow.follow__request_status, user.user__id, user.user__username, user.user__profile_picture, user.privacy
FROM follow
JOIN user ON(follow.follower__user__id = user.user__id)
WHERE followed__user__id = :followed__user__id';
$prep_select_followers = $conn->prepare($select_followers_query);
$prep_select_followers->bindParam(':followed__user__id', $get_user__id, PDO::PARAM_INT);
$prep_select_followers->execute();
$followers_result = $prep_select_followers->fetchAll();
$followers_count = count($followers_result);
}
catch(PDOException $e) {
$conn = null;
echo $error;
}
Next, I get the results like this:
foreach($followers_result AS $followers_row) {
$follower_user_id = $followers_row['follower__user__id'];
// the rest of the variables will come here...
}
I will have separate SQL queries like the one above which each load something. The example above loads the followers, another query will load the comments etc. I want to display the results of all of these queries and display them in chronological order, like this:
#user_1 liked your photo
#user_4 started following you
#user_2 commented on your photo
etc...
How can I achieve this? SQL UNION requires the tables to have the same number of columns and the selected columns must have the same name. I don't have all that. Moreover, every kind of result (follower, comment or like) will have different markups. A follower notification will have a follow button, a comment notification will have a button that redirects to the photo that was liked etc.
SQL UNION requires the tables to have the same number of columns and the selected columns must have the same name.
No, it doesn't. Here table "a" has two columns, integer and varchar.
create table a (
a_id integer,
a_desc varchar(10)
);
insert into a values (1, 'aaaaaaaa'), (2, 'bbbbbbbb');
Table "b" has three columns, varchar, date, and char.
create table b (
b_id varchar(10),
created_date date,
unused char(1)
);
insert into b values ('xyz', '2014-01-01', 'x'), ('tuv', '2014-01-13', 'x');
The SQL union operator only requires that the SELECT clauses (not tables) have the same number of columns, and that they be of compatible data types. You can usually cast incompatible types to something more useful.
-- SELECT clause has three columns, but table "a" has only two.
-- The cast is for illustration; MySQL can union an integer with a
-- varchar without a cast.
--
select cast(a_id as char) as col_1, a_desc as col_2, null as col_3
from a
union all
-- Note that these columns don't have the same names as the columns
-- above.
select b_id, null, created_date
from b;
You can use a single column for date and varchar, but it's usually not a good idea. (Mixing dates with something that's clearly not a date is usually not a good idea.)
select cast(a_id as char) as col_1, a_desc as col_2
from a
union all
select b_id, created_date
from b;
You can use UNION but you'll need to use 'AS' to give columns the same name.
You'll also need to add a line like this to each select:
, 'comment' as Type FROM comment
and:
, 'follow' as Type FROM follow

MySQL sort by number of occurrences

I am doing a search in two text fields called Subject and Text for a specific keyword. To do this I use the LIKE statement. I have encountered a problem when trying to sort the results by the number of occurrences.
my search query looks like this:
SELECT * FROM Table WHERE (Text LIKE '%Keyword%' OR Subject LIKE '%Keyword%')
I tried to add a count() statement and sort it by the number of occurrences, but the count() statement just keep returning the number of rows in my table.
Here is the query with count statement:
SELECT *, COUNT(Text LIKE '%Keyword%') AS cnt FROM News WHERE (Text LIKE '%Keyword%' OR Subject LIKE '%Keyword%') ORDER BY cnt
What im looking for is something that returns the number of matches on the Subject and Text columns on each row, and then order the result after the highest amount of occurrences of the keyword on each row.
Below query can give you the no.of occurrences of string appears in both columns i.e text and subject and will sort results by the criteria but this will not be a good solution performance wise its better to sort the results in your application code level
SELECT *,
(LENGTH(`Text`) - LENGTH(REPLACE(`Text`, 'Keyword', ''))) / LENGTH('Keyword')
+
(LENGTH(`Subject`) - LENGTH(REPLACE(`Subject`, 'Keyword', ''))) / LENGTH('Keyword') `occurences`
FROM
`Table`
WHERE (Text LIKE '%Keyword%' OR Subject LIKE '%Keyword%')
ORDER BY `occurences` DESC
Fiddle Demo
Suggested by #lserni a more cleaner way of calculation of occurrences
SELECT *,
(LENGTH(`Text`) - LENGTH(REPLACE(`Text`, 'test', ''))) / LENGTH('test') `appears_in_text`,
(LENGTH(`Subject`) - LENGTH(REPLACE(`Subject`, 'test', ''))) / LENGTH('test') `appears_in_subject`,
(LENGTH(CONCAT(`Text`,' ',`Subject`)) - LENGTH(REPLACE(CONCAT(`Text`,' ',`Subject`), 'test', ''))) / LENGTH('test') `occurences`
FROM
`Table1`
WHERE (TEXT LIKE '%test%' OR SUBJECT LIKE '%test%')
ORDER BY `occurences` DESC
Fiddle Demo 2
You want SUM instead. Count will count how many records have non-null values, which means ALL matches and NON-matches will be counted.
SELECT *, SUM(Text LIKE '%Keyword') AS total_matches
...
ORDER BY total_matches
SUM() will count up how many boolean true results the LIKE produces, which will be typecast to integers, so you get a result like 1+1+1+0+1 = 4, instead of the 5 non-nulls count.
// escape $keyword for mysql
$keyword = strtolower('Keyword');
// now build the query
$query = <<<SQL
SELECT *,
((LENGTH(`Subject`) - LENGTH(REPLACE(LOWER(`Subject`), '{$keyword}', ''))) / LENGTH('{$keyword}')) AS `CountInSubject`,
((LENGTH(`Text`) - LENGTH(REPLACE(LOWER(`Text`), '{$keyword}', ''))) / LENGTH('{$keyword}')) AS `CountInText`
FROM `News`
WHERE (`Text` LIKE '%{$keyword}%' OR `Subject` LIKE '%{$keyword}%')
ORDER BY (`CountInSubject` + `CountInText`) DESC;
SQL;
Returns number of occurrences in each field and sorts by that.
The 'keyword' needs to be lower cased for this to work. I don't think it's really fast, performance wise as it needs to lower-case fields and there's no case-insensitive search in MySQL afaik.
You could index each news item (subject and text) by words and store in another table with news_id and occurrence count and then match against that.

How to select a column value as a column name and group the results as a row

How do I select a column value as a column name and group the results as a row.
I have a table as such:
id articleId label value
1 1 title Example title
2 1 description This is the description
3 1 author Me
4 2 title Example of another type of article
5 2 description Short description
6 2 author Someone else
Is it possible to select all of the rows and use the label as the column name and the value as the value of that column name and then group them by the article name.
So how I would like to have it returned:
articleId title description author
1 Example title This is the.. Me
2 Example of an.. Short descr.. Someone else
I'm using this for a CMS where the user can define the fields for an article so we don't have to customize the table's. This is why i'm not making the tables as the I would like to have it returned. I am also aware that I can just as easily convert the result to this in php.
-- edit --
Can this be done without knowing what labels are added? In this example im using title, description and author. But it could very well be something totally different like title, shortDescription, availableTo, techInformation, etc.. The idea is that the article's are customizable for the user without needing to change the database and query's
I figured I'd better post as an answer, even if not what OP would like to hear. What you are asking to do is to populate a query with a variable number of columns based on the distinct values within column label, all associated with articleID. Taking your specific example, the following would be the resultant query that I would most likely go to in this instance (though the example from #Devart is equally valid)
SELECT
t.id,
t.articleId,
t1.value AS title,
t2.value AS description,
t3.value AS author
FROM `tableName` t
LEFT JOIN `tablename` t1
ON t1.article_id = t.article_id AND t1.label = 'title'
LEFT JOIN `tablename` t2
ON t2.article_id = t.article_id AND t2.label = 'description'
LEFT JOIN `tablename` t3
ON t3.article_id = t.article_id AND t3.label = 'author'
Now expanding this to account for up to n labels, we get the following query (metacode included, this query will NOT execute verbatim)
SELECT DISTINCT label FROM `tableName`;
SELECT
t.id,
t.articleId
// for (i=1;i<= number of distinct labels) {
,t[i].value AS [value[i]]
// }
FROM `tableName` t
// for (i=1;i<= number of distinct labels) {
LEFT JOIN `tablename` t[i]
ON t[i].article_id = t.article_id AND t[i].label = [value[i]]
// }
;
So what you can do is one of the following.
SELECT t.* FROM tablename t and then have PHP process it as required
SELECT DISTINCT label FROM tablename and have PHP build the second query with the many LEFT JOINs (or MAX / GROUP BY logic if preferred)
Create a Stored Procedure to do the same as #2. This would most likely be more efficient than #2 however may be less efficient overall than #1.
You can use pivote table trick -
SELECT
articleId,
MAX(IF(label = 'title', value, NULL)) AS title,
MAX(IF(label = 'description', value, NULL)) AS description,
MAX(IF(label = 'author', value, NULL)) AS author
FROM
table
GROUP BY
articleId
Try below :
select t1.articleId,t1.title,t1.description,t1.author
from tablename as t1
left join (select max(articleId) as articleId
from tablename
group by articleId ) as t2
on t1.articleId=tsm.articleId where [.....]

Categories