I am using WordPress, and I need to create a function to check for typos in a custom table, by comparing the single values with a comparison table. The values to be checked are animal species names, and they stored as follows in table A
id | qualifying_species
----------------------
1 | Dugong dugon, Delphinus delphis
2 | Balaneoptera physalus, Tursiops truncatus, Stenella coeruleoalba
etc.
These values must be checked for typos by matching them with table B which contains a simple list of species name as a reference
id | species_name
----------------------
1 | Dugong dugon
2 | Delphinus delphis
3 | Balaneoptera physalus
4 | Tursiops truncatus
5 | Stenella coeruleoalba
Here's the code I prepared
function test(){
global $wpdb;
$query_species = $wpdb->get_results("SELECT qualifying_species FROM A", ARRAY_A);
foreach($query_species as $row_species)
{
$string = implode (";", $row_species);
$qualifying_species = explode(";", $string);
//echo '<pre>';
//print_r($qualifying_species);
//echo '</pre>';
foreach ($qualifying_species as $key => $value) {
//I get here the single species name
echo $value . '<br>';
//I compare the single name with the species list table
$wpdb->get_results("SELECT COUNT(species_name) as num_rows FROM B WHERE species_name = '$value'");
//if the species is written correctly, it will be counted as 1 with num_rows
//if the species is written wrongly, no match will be found and num_rows = 0
echo $wpdb->num_rows . '<br>';
}
}
}
The echo was to check the results of the function. The Mysql query works when I do it on PHPMyAdmin, but it seems that something is wrong with the PHP loop that I wrote. For each $value echoed I have a result of 1 echoed with $wpdb->num_rows even if $value presents typos or doesn't exist in table B
What am I doing wrong?
Possible solutoin for MySQL 5.7.
Create a procedure (must be performed only once):
CREATE PROCEDURE check_table_data ()
BEGIN
DECLARE sp_name VARCHAR(255);
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR SELECT species_name FROM tableB;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN cur;
CREATE TEMPORARY TABLE t_tableA SELECT * FROM tableA;
FETCH cur INTO sp_name;
REPEAT
UPDATE t_tableA SET qualifying_species = REPLACE(qualifying_species, sp_name, '');
FETCH cur INTO sp_name;
UNTIL done END REPEAT;
CLOSE cur;
SELECT id, qualifying_species wrong_species FROM t_tableA WHERE REPLACE(qualifying_species, ',', '') != '';
DROP TABLE t_tableA;
END
Now, when you need to check your data for unknown species and misprintings you simply execute one tiny query
CALL check_table_data;
which will return id for a row which have a species value not found in tableB, and this species itself.
fiddle
The code assumes that there is no species_name value which is a substring of another species_name value.
The procedure do the next: it makes the data copy then removes existent species from the values. If some species is wrong (is absent, contains misprint) it won't be removed. After all species processed the procedure selects all rows which are not empty (contain non-removed species).
You maybe could do this in the same query :
SELECT *
FROM table_a
INNER JOIN table_b ON FIND_IN_SET(species_name,replace(qualifying_species,';',','))
if you want to find non-existent values, use something like this:
SELECT *
FROM table_b
LEFT OUTER JOIN table_a ON FIND_IN_SET(species_name,replace(qualifying_species,';',','))
WHERE table_a.id IS null
Related
How can i perform a WHERE NOT IN Condition when the database column as well as the check condition have multiple values in it.
I want to set the status of user to 1, if he doesn't belong to the excludedUserGroups
User Status Table :- user_status
+---------+--------+
| user_id | status |
+=========+========+
| 1 | 1 |
+---------+--------+
User Table :- user
+---------+---------------+---------------------+
| user_id | user_group_id | secondary_group_ids |
+=========+===============+=====================+
| 1 | 4 | 2,8,9 |
+---------+---------------+---------------------+
Variables:-
$excludeUsergroups = "8,7,12"
$finalExcludeUsergroups = implode(",", $excludeUsergroups);
Query i want to perform is :-
Update user_status us
LEFT JOIN user u ON us.user = u.user
SET us.status = 1
WHERE (u.user_group_id NOT IN (". $finalExcludeUsergroups . ") AND u.secondary_group_ids NOT IN (" . $finalExcludeUsergroups . "))
Now the issue is that, both u.secondary_group_ids and $finalExcludedUsergroups have multiple values, its giving error on checks for that second NOT IN.
It performs checks for user_group_id as it contains only 1 value, but when it goes to secondary_groups_ids it fails
How can i perform the second NOT IN where it checks for each value of secondary_group_ids and checks it with $finalExcludedUsergroups
Update
I tested with FIND_IN_SET , however it doesn't work, as FIND_IN_SET checks for a string from the list of strings FIND_IN_SET(string, string_list)
But in my case i want to check a list of strings from the list of strings, which isn't possible with FIND_IN_LIST.
I'd use MySql NOT REGEXP Documentation and the regular expression /(^|\,)3($|\,)/
$sql = "UPDATE user_status us
LEFT JOIN user u ON us.user = u.user
SET us.status = 1
WHERE (u.user_group_id NOT REGEXP '(^|\,):id($|,)'
AND u.secondary_group_ids NOT REGEXP '(^|\,):id($|,)')";
set your :id parameter to $finalExcludeUsergroups,
I would show example of how but don't know if you're using mysqli, pdo, a framework, etc
Break down of the regular expression:
the group (^|\,) will match the start of the string or a comma
3 will match the id you're looking for (for the code I'd use a parameter here)
the group ($|\,) will match the end of a string or a comma
I was intrigued by your issue and wondered if a simple User Defined Function might help, so prepared the example below. If you create the function in your DB, you should be able to pass your two "comma separated value" strings to it inside your query to see if any groups are shared. As you'll notice, I've named it for the negative case (i.e. Disjoint) as I couldn't quickly think of a suitable short name to express the positive case (i.e. Sets_have_one_or_more_elements_in_common seems a bit long!).
Hopefully someone may find this useful at some point!
/*
MySQL Function to determine whether two comma-separated strings
supplied as parameters share any common elements.
Example Usage:
SELECT SETS_ARE_DISJOINT('8,7,12', '2,8,9'); --> False - shared entry = 8
SELECT SETS_ARE_DISJOINT('7,12', '2,8,9'); --> True - no shared entries
*/
CREATE FUNCTION SETS_ARE_DISJOINT(csv1 TEXT, csv2 TEXT)
RETURNS BOOLEAN
LANGUAGE SQL
BEGIN
DECLARE Occurrences INT;
DECLARE myValue TEXT;
SET Occurrences = LENGTH(TRIM(csv1)) - LENGTH(REPLACE(TRIM(csv1), ',', ''));
Loop1:
WHILE Occurrences > 0 DO
SET myValue = SUBSTRING_INDEX(csv1, ',', 1);
IF (myValue != '') THEN
IF FIND_IN_SET(myValue , csv2) > 0 THEN
RETURN FALSE;
END IF;
ELSE
LEAVE Loop1;
END IF;
SET Occurrences = LENGTH(TRIM(csv1)) - LENGTH(REPLACE(TRIM(csv1), ',', ''));
IF (Occurrences = 0) THEN
LEAVE Loop1;
END IF;
SET csv1 = SUBSTRING(csv1, LENGTH(SUBSTRING_INDEX(csv1, ',', 1)) + 2);
END WHILE;
RETURN TRUE;
END;
Ok, so i achieved what i wanted to do through a foreach loop, i looped through the $finalExcludeUsergroups and created an array of the FIND_IN_SET and than included it in the query .
foreach ($excludeUsergroups as $usergroup)
{
$statusJoiner[] = 'FIND_IN_SET(' . $db->quote($usergroup) .' , user.secondary_group_ids) = 0';
}
and than i joined them through AND condition
if (!empty($statusJoiner))
{
//Joiner for hit part
$Joiner = ' AND ';
$statusFinalJoiner = implode($Joiner,$statusJoiner);
}
and than the query goes like this :-
Update user_status us
LEFT JOIN user u ON us.user = u.user
SET us.status = 1
WHERE (u.user_group_id NOT IN (". $finalExcludeUsergroups . ") AND " . $statusFinalJoiner . ")
That's it, it worked out :)
My Problem: Getting query results from 2 db tables with PROPEL2 even when second table has no corresponding entries. If the second has corresponding entries than it is no problem.
I have 3 tables: Entry, Contingent and Favorit.
The schema is as follow:
Entry.id [PK]
Entry.contingent_id [FK]
Entry.expert_id
Contingent.id [PK]
Contingent.name
Favorit.id [PK]
Favorit.contingent_id [FK]
Favorit.expert_id
Favorit.pos
I want to get for a specified expert_id ($id) all entries from Entry with contingent-name and if exists the favorit.pos for this expert and contingent. I get the wanted with:
$result = EntryQuery::create()
->filterByExpertId($id)
->join('Entry.Contingent')
->withColumn('Contingent.name','_contingentName')
->join('Contingent.Favorit')
->where('Favorit.expert_id = ?', $id)
->find();
This works only if there exists such a favorit.pos . In some cases this element doesn’t exists (what is wanted from the system). In these cases I want to get the result too just with favorit.pos as empty, null or 0. But Propel doesn’t return me these records.
With MySQL I have no problem to get the desired result:
SELECT entry.* ,
(SELECT favorit.position
FROM contingent, favorit
WHERE
favorit.expert_id = entry.expert_id
AND entry.contingent_id = contingent.id
AND contingent.id = favorit.contingent_id
)
FROM `entry`
JOIN contingent
ON entry.contingent_id = contingent.id
WHERE
entry.expert_id=1;
Use Join left in code:
->join('Contingent.Favorit','selection conditon','left' )
This left work when empty database when condition is false
in condition like 'id'=$id
I am getting a db result as 3 rows (Please see image link below).
The sql statement used is
select tevmkt.ev_mkt_id, tevmkt.name, tevmkt.ev_id, tevoc.ev_oc_id,
tevoc.desc, tevoc.fb_result, tevoc.lp_num, tevoc.lp_den,
tev.start_time
from tevmkt, tev,tevoc
where tevmkt.name = '|Match Result|' and tev.ev_id=tevmkt.ev_id and
tevoc.ev_mkt_id=tevmkt.ev_mkt_id and tev.start_time>=Today;
I will like to use php to concatenate each of the 3 rows into string or maybe use SQL statement.
So, the first 3 rows will display as ;
632274|Match Result||Draw||Aldershot Town||Arsenal FC|
And the next 3 rows
637799|Match Result||Draw||Southend United||Oxford United|
You can use concat
Sample base on your query
select CONCAT(tevmkt.ev_mkt_id, tevmkt.name, tevmkt.ev_id, tevoc.ev_oc_id,
tevoc.desc, tevoc.fb_result, tevoc.lp_num, tevoc.lp_den,
tev.start_time)
from tevmkt, tev,tevoc
where tevmkt.name = '|Match Result|' and tev.ev_id=tevmkt.ev_id and
tevoc.ev_mkt_id=tevmkt.ev_mkt_id and tev.start_time>=Today;
For what I see your model looks something like this:
CREATE TABLE tevmkt(
ev_mkt_id INT,
ev_id INT,
name CHAR(25)
);
CREATE TABLE tev(
ev_id INT,
start_time DATETIME YEAR TO SECOND
);
CREATE TABLE tevoc(
ev_mkt_id INT,
desc CHAR(25),
fb_result CHAR(1),
lp_num SMALLINT,
lp_den SMALLINT
);
You join tevmkt with tev by ev_id in a 1-to-1 relation.
You filter the records on tevmkt using the name field and the records on tev using the start_time field.
Now, you join the tevmkt with the tevoc by ev_mkt_id in a one to many relation, for what I see 1-to-3.
Your goal is to have a 1-to-1 also. Looking at you example I see three rows for each event and I conclude that they are:
1 row for home team, where fb_result is H;
1 row for away team, where fb_result is A;
1 row for result, I'm not sure on this one so I will handle it with care;
If this is the case, you can get your result set straigth using the next joins and filters:
SELECT
tevmkt.ev_mkt_id,
tevmkt.name,
(
TRIM(tevoc_result.desc) ||
TRIM(tevoc_away.desc) ||
TRIM(tevoc_home.desc)
) AS desc
FROM tevmkt
JOIN tev ON
tev.ev_id = tevmkt.ev_id
JOIN tevoc tevoc_result ON
tevoc_result.ev_mkt_id = tevmkt.ev_mkt_id
JOIN tevoc tevoc_away ON
tevoc_away.ev_mkt_id = tevmkt.ev_mkt_id
JOIN tevoc tevoc_home ON
tevoc_home.ev_mkt_id = tevmkt.ev_mkt_id
WHERE
tevmkt.name = '|Match Result|'
AND tev.start_time >= TODAY
AND tevoc_result.fb_result NOT IN ('H', 'A')
AND tevoc_away.fb_result = 'A'
AND tevoc_home.fb_result = 'H'
;
Now i have the table like
--------------------------
merge_id | merge_combo |
--------------------------
A1 | 25,9 |
A2 | 25,21 |
A3 | 2,5,15 |
A4 | 2,9,5 |
A5 | 12,19,2 |
A6 | 2,1 |
--------------------------
for example now if i pass the value 9, then it should check the merge_combo and delete the rows which are having 9 (A1,A4 this two having 9)
You can do this with find_in_set():
delete from tbl
where find_in_set(9, merge_combo) > 0;
The bigger issue is that you are storing numbers in a string for a list. SQL has this great data structure for storing lists -- it is called a table. In this case, you want a junction table, with one row per merge_id and combo item.
DELETE from your_table where FIND_IN_SET(9, merge_combo) or merge_combo IN(9)
You can use the following qry
DELETE FROM tbl WHERE merge_combo LIKE '%,9,%' OR merge_combo LIKE '9,%' OR merge_combo LIKE '%,9' OR merge_combo = '9';
You may use Find_In_Set
delete from your_table where FIND_IN_SET(#val, merge_combo)
FIND_IN_SET(str,strlist)
Returns a value in the range of 1 to N if the string str is in the
string list strlist consisting of N substrings. A string list is a
string composed of substrings separated by “,” characters. If the
first argument is a constant string and the second is a column of type
SET, the FIND_IN_SET() function is optimized to use bit arithmetic.
Returns 0 if str is not in strlist or if strlist is the empty string.
Returns NULL if either argument is NULL. This function does not work
properly if the first argument contains a comma (“,”) character.
Use Below query
DELETE FROM tbl WHERE FIND_IN_SET("9",merge_combo)
You should not create a complex SQL query with LIKE.If you have a bad db design your app will be bad too.
Instead normalize your database that comes up even against the 1NF. Try to break merge_combo to merge_comboA and merge_comboB and merge_comboC if you know that you will have to untill 3 combo values or for the best implementation create a separate table with composite keys (this way you will avoid Null values)
PS : use the queries proposed only if you can't change your database (e.g. an academic exercise to test your knoweledge on queries)
Connection:
$mysqli = new mysqli ('localhost', 'admin_user', 'Paradise1191', 'admin_db');
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$mysqli->set_charset("utf8");
Select & Delete:
$var = "Your Result";
$array = array();
$query = "SELECT * FROM `TABLE` WHERE 1";
if ($result = $mysqli->query($query)) {
while ($row = $result->fetch_array())
{
$this_id = $row['merge_id'];
$var1 = $row['merge_combo'];
$array = explode(",",$var1 );
if (in_array($var, $people)) {
$query = "DELETE FROM `TABLE` WHERE `merge_id` = '$this_id'";
$mysqli->query($query);
}
}
$result->close();
}
you should try this following query:
DELETE FROM merge WHERE merge_combo LIKE '% 9 %';
A user can input it's preferences to find other users.
Now based on that input, I'd like to get the top 10 best matches to the preferences.
What I thought is:
1) Create a select statement that resolves users preferences
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid = you"))
$stmt->bind_result($ownsex);
2) Create a select statement that checks all users except for yourself
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid <> you"))
$stmt->bind_result($othersex);
3) Match select statement 1 with select statement 2
while ($stmt->fetch()) {
$match = 0;
if ($ownsex == $othersex) {
$match = $match + 10;
}
// check next preference
4) Start with a variable with value 0, if preference matches -> variable + 10%
Problem is, I can do this for all members, but how can I then select the top 10???
I think I need to do this in the SQL statement, but I have no idea how...
Ofcourse this is one just one preference and a super simple version of my code, but you'll get the idea. There are like 15 preference settings.
// EDIT //
I would also like to see how much the match rating is on screen!
Well, it was a good question from the start so I upvoted it and then wasted about 1 hour to produce the following :)
Data
I have used a DB named test and table named t for our experiment here.
Below you can find a screenshot showing this table's structure (3 int columns, 1 char(1) column) and complete data
As you can see, everything is rather simple - we have a 4 columns, with id serving as primary key, and a few records (rows).
What we want to achieve
We want to be able to select a limited set of rows from this table based upon some complex criteria, involving comparison of several column's values against needed parameters.
Solution
I've decided to create a function for this. SQL statement follows:
use test;
drop function if exists calcMatch;
delimiter //
create function calcMatch (recordId int, neededQty int, neededSex char(1)) returns int
begin
declare selectedQty int;
declare selectedSex char(1);
declare matchValue int;
set matchValue = 0;
select qty, sex into selectedQty, selectedSex from t where id = recordId;
if selectedQty = neededQty then
set matchValue = matchValue + 10;
end if;
if selectedSex = neededSex then
set matchValue = matchValue + 10;
end if;
return matchValue;
end//
delimiter ;
Minor explanation
Function calculates how well one particular record matches the specified set of parameters, returning an int value as a result. The bigger the value - the better the match.
Function accepts 3 parameters:
recordId - id of the record for which we need to calculate the result(match value)
neededQty - needed quantity. if the record's qty matches it, the result will be increased
neededSex - needed sex value, if the record's sex matches it, the result will be increased
Function selects via id specified record from the table, initializes the resulting match value with 0, then makes a comparison of each required columns against needed value. In case of successful comparison the return value is increased by 10.
Live test
So, hopefully this solves your problem. Feel free to use this for your own project, add needed parameters to function and compare them against needed columns in your table.
Cheers!
Use the limit and offset in query:
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 10 offset 0
This will give the 10 users data of top most.
You can set a limit in your query like this:
SELECT sex FROM ledenvoorkeuren WHERE userid <> yourid AND sex <> yourpreferredsex limit 0, 10
Where the '0' is the offset, and the '10' your limit
More info here
you may try this
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 0, 10 order by YOUR_PREFERENCE