MySql - FIND_IN_SET check if all exist - php

I want to check in mysql if all given keys exists in set or not. like:
$comma_separted_user_ids = "20,2,9,8,31,1";
$query ="SELECT conversation_id FROM message
WHERE FIND_IN_SET($comma_separted_user_ids, user_ids) ";
// data of user_ids = "1,2,8,9,20,31";
I want to check if all user id exist in user_ids column or not, user_ids are not properly ordered.
Please suggest a solution, thanks.

While it is technically feasible:
$query =
'SELECT conversation_id FROM message'
. 'WHERE FIND_IN_SET('
. str_replace(
',',
', user_ids) AND FIND_IN_SET('
$comma_separted_user_ids
)
. ', user_ids)' ;
... you should never do this!
Instead, create a new table to model the many-to-many relationship that exists between your user and message entities (e.g. participant). This is basic normalisation.
Then the query becomes trivial and performant:
SELECT conversation_id FROM participant
WHERE user_id IN ($comma_separted_user_ids)
GROUP BY conversation_id
HAVING COUNT(user_id) = [number of items in $comma_separted_user_ids]

Since you don't know the ordering, I don't see a way around FIND_IN_SET. Like others said, it'd be far better to normalise your table structure.
But in the interest of providing an answer to the question, you'll need to create a list of FIND_IN_SET operators.
// A list of IDs.
$comma_separated_user_ids = "20,2,9,8,31,1";
// The TRUE string will make sure that the array
// always contains at least one item.
$where = array("TRUE");
// Iterate over the IDs and create strings such as
// "FIND_IN_SET(1, column_name_here)"
foreach(explode(",", $comma_separated_user_ids) as $id) {
$where[] = "FIND_IN_SET($id, user_ids)";
}
Then it's a simple matter of joining the strings together:
// Join everything together with AND (&&).
// Since "0" is considered FALSE, this works.
$where = implode(" && ", $where);
// Query for rows.
$query ="SELECT conversation_id FROM message WHERE ($where) ";
Don't use this if you don't need to. It won't scale very well.

You can do this:
SELECT conversation_id
FROM message
WHERE FIND_IN_SET($comma_separted_user_ids, user_ids) > 0
GROUP BY conversation_id
HAVING count(distinct user_id) = 1 + (length($comma_separted_user_id) - length(replace($comma_separted_user_id, ',', '')))
The having clause is counting the number of elements in the comma separated list.
If you are creating the SQL, you should consider using a table to store the values instead of a list. A join approach can take advantage of indexes, which find_in_set() cannot.

i guess you should write it like this :
$comma_separted_user_ids = "20,2,9,8,31,1";
$query ="SELECT conversation_id FROM message
WHERE user_id IN ($comma_separted_user_ids) ";

Related

SQL get an id from a column where ids separated by commas

I have a column named id with a bunch of ids separated by commas like this:
asdaxxdfd2,wwfsfdssdfsd6,sdfdsfdsed2,23445rr55
I need to match an id from the column to an existing $user_id
Trying this didn't do it:
"SELECT id FROM my_table WHERE id LIKE '%" . $user_id . "%'";
Not sure what else I can do.
Thank you.
"SELECT id FROM my_table WHERE concat(',', id, ',') LIKE '%," . $user_id . ",%';"
should do it.
But I also strongly recommend to normalize the schema.
Your question is a little bit ambiguous, I don't know if this solution is what you intends:
SELECT REGEXP_SUBSTR(id, '[^,]+', 1, 1) COLUMN_1,
REGEXP_SUBSTR(id, '[^,]+', 1, 2) COLUMN_2,
FROM my_table;
or just
SELECT id
FROM my_table
WHERE id LIKE '%wwfsfdssdfsd6%'
SELECT REGEXP_SUBSTR(id," . $user_id . ") UserID FROM my_table;
This will return the matching user id but again using a database with values stored in a single row is never a good idea.
SELECT * FROM tbl WHERE id REGEXP "[[:<:]]$user_id[[:>:]]";
Those match "word boundaries", thereby handling beginning and end of string.
But, without normalizing there is no way to make a query that runs faster than a full table scan.

Attempt to update one mySQL table with data from two other tables is failing

I am trying to update one table which holds the inputted data from my employees. The table has three columns: entry id, field id, and value. I am wanting to make sure there is a row for every field in the this table according to each entry. As such I have the written the following php/sql script. The thought process was get two arrays (one containing the entry ids and the other the field ids) and check the input table to see if there was an existing row for every field and entry. If not then use an inert into command to add a row with the value 0, but this command is failing. My guess is a timeout error due to the intensity of this function. Any suggestions?
$db = JFactory::getDBO();
$field_query = 'SELECT id FROM `jos_directory_field`';
$db->setQuery( $field_query );
$fields = $db->loadObjectList();
$entry_query = 'SELECT id FROM `jos_directory_entry`';
$db->setQuery( $entry_query );
$entries = $db->loadObjectList();
for($e=0;$e count($entries);$e++) {
for($i=0;$i count($fields);$i++) {
$insert_query = 'INSERT INTO `jos_directory_enf` (entry_id,field_id,field_value)';
$insert_query .= 'SELECT '.$entries[$e]->id.','.$fields[$i]->id.',0';
$insert_query .= 'FROM dual WHERE NOT EXISTS (SELECT * FROM `jos_directory_enf`';
$insert_query .= 'WHERE entry_id = '.$entries[$e]->id.' and field_id = '.$fields[$i]->id.')';
$db->setQuery( $insert_query );
$db->query();
}
}
It's too bad Joomla doesn't appear to support prepared statements, as they can help with performance for repeated queries.
However, in this case there's a better option. You should be able to replace the PHP code with a single SQL statement, making use of the IGNORE clause for INSERT statements. First, make sure you have a unique index on the entry_id and field_id columns of jos_directory_enf. Then the SQL statement would be something like:
INSERT IGNORE INTO `jos_directory_enf` (entry_id,field_id,field_value)
SELECT e.id, f.id, 0
FROM jos_directory_entries AS e
JOIN jos_directory_field AS f
;
.

What is the query statement to write in order to solve the followin database problem?

I have the following 3 tables in the database.
Programs_Table
Program_ID (Primary Key)
Start_Date
End_Date
IsCompleted
IsGoalsMet
Program_type_ID
Programs_Type_Table(different types of programs, supports a dropdown list in the form)
Program_type_ID (Primary Key)
Program_name
Program_description
Client_Program_Table
Client_ID (primary key)
Program_ID (primary key)
What is the best way to find out how many clients are in a specific program (program type)?
Would the following SQL statement be the best way, or even plausible?
SELECT Client_ID FROM Client_Program_Table
INNER JOIN Programs_Table
ON Client_Program_Table.Program_ID = Programs_Table.Program_ID
WHERE Programs_Table.Program_type_ID = "x"
where "x" is the Program_type_ID of the specific program we're interested in.
OR is the following a better way?
$result = mysql_query("SELECT Program_ID FROM Programs_Table
WHERE Program_type_ID = 'x'");
$row = mysql_fetch_assoc($result);
$ProgramID = $row['Program_ID'];
$result = mysql_query("SELECT * FROM Client_Program_Table
WHERE Program_ID = '$ProgramID'");
mysql_num_rows($result) // returns how many rows of clients we pulled.
Thank you in advance, please excuse my inexperience and any mistakes that I've made.
Here is how you can do it:
<?php
// always initialize a variable
$number_of_clients = 0;
// escape the string which will go in an SQL query
// to protect yourself from SQL injection
$program_type_id = mysql_real_escape_string('x');
// build a query, which will count how many clients
// belong to that program and put the value on the temporary colum "num_clients"
$query = "SELECT COUNT(*) `num_clients` FROM `Client_Program_Table` `cpt`
INNER JOIN `Programs_Table` `pt`
ON `cpt`.`Program_ID` = `pt`.`Program_ID`
AND `pt`.`Program_type_ID` = '$program_type_id'";
// execute the query
$result = mysql_query($query);
// check if the query executed correctly
// and returned at least a record
if(is_resource($result) && mysql_num_rows($result) > 0){
// turn the query result into an associative array
$row = mysql_fetch_assoc($result);
// get the value of the "num_clients" temporary created column
// and typecast it to an intiger so you can always be safe to use it later on
$number_of_clients = (int) $row['num_clients'];
} else{
// query did not return a record, so we have no clients on that program
$number_of_clients = 0;
}
?>
If you want to know how many clients are involved in a program, you'd rather want to use COUNT( * ). MySQL (with MyISAM) and SQL Server have a fast way to retrieve the total number of lines. Using a SELECT(*), then mysql_num_rows leads to unnecessary memory ressources and computing time. To me, this is the fastest, though not the "cleanest" way to write the query you want:
SELECT
COUNT(*)
FROM
Client_Program_Table
WHERE
Program_ID IN
(
SELECT
Program_ID
FROM
Programs_Table
WHERE
Program_type_ID = 'azerty'
)
Why is that?
Using JOIN make queries more readable, but subqueries often prove to be computed faster.
This returns a count of the clients in a specific program type (x):
SELECT COUNT(cpt.Client_ID), cpt.Program_ID
FROM Client_Program_Table cpt
INNER JOIN Programs_Table pt ON cpt.Program_ID=pt.Program_ID
WHERE pt.Program_type_ID = "x"
GROUP BY cpt.Program_ID

GROUP BY give priority to in MySQL

I have the following query.
$query_assignments = "SELECT * FROM tb_scheduler_assignments
WHERE company_id = '".$company_id."' OR
dept_id = '".$dept_id."' OR
user_id = '".$user_id."' ORDER BY
due_date GROUP BY purchase_id";
What I'd like is a single query solution that would keep the results for user_id over dept_id and dept_id over company_id.
For example:
if the same purchase_id occurs for
rows that were gotten via dept_id and
user_id, then I only want the result
for the user_id;
if the same purchase_id occurs for
rows that were gotten via company_id
and user_id, then I only want the
result for the user_id
First, you're interpolating variables in your SQL, which suggests you might be vulnerable to SQL injection. Just to make sure. PHP should offer prepared statements, or some escaping function.
Second, your SQL statement won't compile because you're using GROUP BY a but selecting * which includes at least three more columns.
Third, it sounds like you're misunderstanding SQL in thinking that it might, in a query such as you're trying to formulate (without UNION ALL), retrieve duplicate rows, i.e. the same row multiple times because it matches multiple criteria. This is not so.
The "single query" solution that I was looking for doesn't seem to exist, or if it does, it would be way slower than just handling all the sorting in php.
So, I ran 3 separate queries, put each of them into arrays, and then in order to put them all into a final array with the hierarchy that I needed, I did the loops below to see if the purchaseID existed for the levels up the hierarchy. If it didn't, then I put it in to the array.
$finalArray = array();
foreach ($companyArray as $purchaseID => $companyData) {
if (empty($deptArray[$purchaseID]) && empty($userArray[$purchaseID])) {
$finalArray[] = $companyData;
}
}
foreach ($deptArray as $purchaseID => $deptData) {
if (empty($userArray[$purchaseID])) {
$finalArray[] = $deptData;
}
}
foreach ($userArray as $purchaseID => $userData) {
$finalArray[] = $userData;
}
Then I can sort that array however I want and loop through that to echo what I need to.
Not sure if that's the best way, but it worked well and is lightning fast for me.
$query_assignments = "SELECT *,
IF(user_id = {$user_id}, 30,
IF(dept_id = {$dept_id}, 20,
IF(company_id = {$company_id}, 10, 0)
)
) as priority
FROM tb_scheduler_assignments
WHERE company_id = {$company_id} OR
dept_id = {$dept_id} OR
user_id = {$user_id}
GROUP BY purchase_id
ORDER BY due_date, priority DESC";
You can make a virtual field with the if statement.
user_id: 30 pts
dept_id: 20 pts
company_id: 10 pts
else: 0 pts
WARNING: can not be Indexed!
Syntax FIX: GROUP BY and ORDER BY reordered

Summing a field from all tables in a database

I have a MySQL database called "bookfeather." It contains 56 tables. Each table has the following structure:
id site votes_up votes_down
The value for "site" is a book title. The value for "votes_up" is an integer. Sometimes a unique value for "site" appears in more than one table.
For each unique value "site" in the entire database, I would like to sum "votes_up" from all 56 tables. Then I would like to print the top 25 values for "site" ranked by total "votes_up".
How can I do this in PHP?
Thanks in advance,
John
You can do something like this (warning: Extremely poor SQL ahead)
select site, sum(votes_up) votes_up
from (
select site, votes_up from table_1
UNION
select site, votes_up from table_2
UNION
...
UNION
select site, votes_up from table_56
) group by site order by sum(votes_up) desc limit 25
But, as Dav asked, does your data have to be like this? There are much more efficient ways of storing this kind of data.
Edit: You just mentioned in a comment that you expect there to be more than 56 tables in the future -- I would look into MySQL limits on how many tables you can UNION before going forward with this kind of SQL.
Here's a PHP code snip that should get it done.
I have not tested it so it might have some typos and stuff, make sure you replace DB_NAME
$result = mysql_query("SHOW TABLES");
$tables = array();
while ($row = mysql_fetch_assoc($result)) {
$tables[] = '`'.$row["Tables_in_DB_NAME"].'`';
}
$subQuery = "SELECT site, votes_up FROM ".implode(" UNION ALL SELECT site, votes_up FROM ",$tables);
// Create one query that gets the data you need
$sqlStr = "SELECT site, sum(votes_up) sumVotesUp
FROM (
".$subQuery." ) subQuery
GROUP BY site ORDER BY sum(votes_up) DESC LIMIT 25";
$result = mysql_query($sqlStr);
$arr = array();
while ($row = mysql_fetch_assoc($result)) {
$arr[] = $row["site"]." - ".$row["sumVotesUp"];
}
print_r($arr)
The UNION part of Ian Clelland answer can be generated using a statement like the following. The table INFORMATION_SCHEMA.COLUMNS has a column TABLE_NAME to get all tables.
select * from information_schema.columns
where table_schema not like 'informat%'
and column_name like 'VOTES_UP'
Join all inner SELECT with UNION ALL instead of UNION. UNION is doing an implicit DISTINCT (on oracle).
The basic idea would be to iterate over all your tables (using a SQL SHOW TABLES statement or similar) in PHP, then for every table, iterate over the rows (SELECT site,votes_up FROM $table). Then, for every row, check the site against an array that you're building with sites as keys and votes up as values. If the site is already in the array, increment its votes appropriately; otherwise, add it.
Vaguely PHP-like pseudocode:
// Build an empty array for use later
$votes_array = empty_array();
// Get all the tables and iterate over them
$tables = query("SHOW TABLES");
for($table in $tables) {
$rows = query("SELECT site,votes_up FROM $table");
// Iterate over the rows in each table
for($row in $rows) {
$site = $row['site'];
$votes = $row['votes_up'];
// If the site is already in the array, increment votes; otherwise, add it
if(exists_in_array($site, $votes_array)) {
$votes_array[$site] += $votes;
} else {
insert_into_array($site => $votes);
}
}
}
// Get the sites and votes as lists, and print out the top 25
$sorted_sites = array_keys($votes_array);
$sorted_votes = array_values($votes_array);
for($i = 0; $i < 25; $i++) {
print "Site " . $sorted_sites[$i] . " has " . $sorted_votes[$i] . " votes";
}
"I allow users to add tables to the database." - I hope all your users are benevolent and trustworthy and capable. Do you worry about people dropping or truncating tables, creating incorrect new tables that break your code, or other things like that? What kind of security do you have when users can log right into your database and change the schema?
Here's a tutorial on relational database normalization. Maybe it'll help.
Just in case someone else that comes after you wants to find what this could have looked like, here's a single table that could do what you want:
create database bookfeather;
create user bookfeather identified by 'bookfeather';
grant all on bookfeather.* to 'bookfeather'#'%';
use bookfeather;
create table if not exists book
(
id int not null auto_increment,
title varchar(255) not null default '',
upvotes integer not null default 0,
downvotes integer not null default 0,
primary key(id),
unique(title)
);
You'd vote a title up or down with an UPDATE:
update book set upvotes = upvotes + 1 where id = ?
Adding a new book is as easy as adding another row:
insert into book(title) values('grails in action')
I'd strongly urge that you reconsider.

Categories