mysql like query exclude numbers - php

I have a small problem with a php mysql query, I am looking for help.
I have a family tree table, where I am storing for each person his/her ancestors id separated by a comma. like so
id ancestors
10 1,3,4,5
So the person of id 10 is fathered by id 5 who is fathered by id 4 who is fathered by 3 etc...
Now I wish to select all the people who have id x in their ancestors, so the query will be something like:
select * from people where ancestors like '%x%'
Now this would work fine except, if id x is lets say 2, and a record has an ancestor id 32, this like query will retrieve 32 because 32 contains 2. And if I use '%,x,%' (include commas) the query will ignore the records whose ancestor x is on either edge(left or right) of the column. It will also ignore the records whose x is the only ancestor since no commas are present.
So in short, I need a like query that looks up an expression that either is surrounded by commas or not surrounded by anything. Or a query that gets the regular expression provided that no numbers are around. And I need it as efficient as possible (I suck at writing regular expressions)
Thank you.
Edit: Okay guys, help me come up with a better schema.

You are not storing your data in a proper way. Anyway, if you still want to use this schema you should use FIND_IN_SET instead of LIKE to avoid undesired results.
SELECT *
FROM mytable
WHERE FIND_IN_SET(2, ancestors) <> 0

You should consider redesigning your database structure. Add new table "ancestors" to database with columns:
id id_person ancestor
1 10 1
2 10 3
3 10 4
After -- use JOIN query with "WHERE IN" to choose right rows.

You're having this issue because of wrong design of database.First DBMS based db's aren't meant for this kind of data,graph based db's are more likely to fit for this kind of solution.
if it contain small amount of data you could use mysql but still the design is still wrong,if you only care about their 'father' then just add a column to person (or what ever you call it) table. if its null - has no father/unknown otherwise - contains (int) of his parent.
In case you need more then just 'father' relationship you could use a pivot table to contain two persons relationship but thats not a simple task to do.

There are a few established ways of storing hierarchical data in RDBMS. I've found this slideshow to be very helpful in the past:
Models for Hierarchical Design
Since the data deals with ancestry - and therefore you wouldn't expect it to change that often - a closure table could fit the bill.
Whatever model you choose, be sure to look around and see if someone else has already implemented it.

You could store your values as a JSON Array
id | ancestors
10 | {"1","3","4","5"}
and then query as follows:
$query = 'select * from people where ancestors like \'%"x"%\'';
Better is of course using a mapping table for your many-to-many relation

You can do this with regexp:
SELECT * FROM mytable WHERE name REGEXP ',?(x),?'
where x is your searched value

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,ancestors VARCHAR(250) NOT NULL
);
INSERT INTO my_table VALUES(10,',1,3,4,5');
SELECT *
FROM my_table
WHERE CONCAT(ancestors,',') LIKE '%,5,%';
+----+-----------+
| id | ancestors |
+----+-----------+
| 10 | ,1,3,4,5 |
+----+-----------+
SELECT *
FROM my_table
WHERE CONCAT(ancestors,',') LIKE '%,4,%';
+----+-----------+
| id | ancestors |
+----+-----------+
| 10 | ,1,3,4,5 |
+----+-----------+

Related

What is the best way to match a field with multiple IDs stored in mySQL

I have a table with a column called "owners", this contains the IDS for users that are connected with that particular record.
I currently delimit the data with ",". So for example
ID | Name | Owners
1 | Bob | 1,4,5
When doing a select on this I was intending to use the following SQL:
select * from table where owner='$profile' or owner like '%,$profile%' or owner like '%$profile,%'
but now I realise this is flawed (searching for 5 would match 5, 15, 25, even 50).
What would be the correct way to do this?
#Amarnasan is correct: Don't store multiple values in a single field separated with commas!
In Bill Karwin's SQL Antipatterns book this is called the Jaywalking antipattern.
The correct way is to create a an intersection table which joins owners to the first table. You would have multiple rows in the intersection table representing the multiple owners for each record:
Record_ID | Owners_ID
1 | 1
1 | 4
1 | 5
Your query would then look something like:
select * from table
join intersection_table
where intersection_table.record_id = table.id
and intersection_table.owners_id = '$profile'
I wouldn't be storing data this way (it's not 'sound' database design).
But if you insist on doing so, you could make sure your other numbers don't satisfy this condition by explicitly including the commas yourself:
SELECT * FROM table WHERE ',' + owner + ',' LIKE '%,5,%'
Again, I would not use this method for storing data.. but in the case that you can't change it or require a patch before refactoring I would use the following:
SELECT *
FROM table
WHERE FIND_IN_SET(:profile, Owners) > 0
See FIND_IN_SET docs
Use find_in_set
SELECT * FROM table where find_in_set($profile,owner)
FIND_IN_SET is a great workaround only if you don't have time to normalize the values and split them out into other tables.
Careful though, This method is very sensitive and will look for the entire value between each comma. So if you store numbers with spaces like "1, 2, 3", then the values that will be returned will be "1", " 2", and " 3", not "1","2","3" (notice the first group has spaces).

MySQL SELECT SUM(Column) and SELECT * Cardinality violation: 1241 Operand should contain 1 column(s)

Trying to write statement where in single statement select all (*) and sum one column from the same database and the same table, depending on conditions.
Wrote such statement (based on this Multiple select statements in Single query)
SELECT ( SELECT SUM(Amount) FROM 2_1_journal), ( SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? )
I understand that SELECT SUM(Amount) FROM 2_1_journal will sum all values in column Amount (not based on codition).
But at first want to understand what is correct statement
With above statement get error SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s)
Can not understand error message. From advice here MySQL - Operand should contain 1 column(s) understand that subquery SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? must select only one column?
Tried to change statement to this SELECT ( SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? ), ( SELECT SUM(Amount) FROM 2_1_journal), but get the same error...
What would be correct statement?
SELECT *, (SELECT SUM(Amount) FROM 2_1_journal)
FROM 2_1_journal
WHERE TransactionPartnerName = ?
This selects sums up Amount from the entire table and "appends" all rows where TransactionPartnerName is the parameter you bind in the client code.
If you want to limit the sum to the same criteria as the rows you select, just include it:
SELECT *, (SELECT SUM(Amount) FROM 2_1_journal WHERE TransactionPartnerName = ?)
FROM 2_1_journal
WHERE TransactionPartnerName = ?
A whole different thing: table names like 2_1_journal are strong indicators of a broken database design. If you can redo it, you should look into how to normalize the database properly. It is most likely pay back many times over.
With regard to normalization (added later):
Since the current design uses keys in table names (such as the 2 and 1 in 2_1_journal), I'll quickly illustrate how I think you can vastly improve that design. Lets say that the table 2_1_journal has the following data (I'm just guessing here because the tables haven't been described anywhere yet):
title | posted | content
------+------------+-----------------
Yes! | 2013-01-01 | This is just a test
2nd | 2013-01-02 | Another test
This stuff belongs to user 2 in company 1. But hey! If you look at the rows, the fact that this data belongs to user 2 in company 1 is nowhere to be found.
The problem is that this design violates one of the most basic principles of database design: don't use keys in object (here: table) names. A clear indication that something is very wrong is if you have to create new tables if something new is added. In this case, adding a new user or a new company requires adding new tables.
This issue is easilly fixed. Create one table named journal. Next, use the same columns, but add another two:
company | user | title | posted | content
--------+------+-------+------------+-----------------
1 | 2 | Yes! | 2013-01-01 | This is just a test
1 | 2 | 2nd | 2013-01-02 | Another test
Doing it like this means:
You never add or modify tables unless the application changes.
Doing joins across companies or users (and anything else that used to be part of the table naming scheme is now possible with a single, fairly simple select statement).
Enforcing integrity is easy - if you upgrade the application and want to change the tables, the changes doesn't have to be repeated for each company and user. More importantly, this lowers the risk of having the application get out of sync with the tables in the database (such as adding the field comments to all x_y_journal tables, but forgetting 5313_4324_journal causing the application to break only when user 5313 logs in. This is the kind of problem you don't want to deal with.
I am not writing this because it is a matter of personal taste. Databases are just designed to handle tables that are laid out as I describe above. The design where you use object keys as part of table names has a host of other problems associated with it that are very hard to deal with.

Use search results (Like %search%), to match id's in another table in the database

I have two tables in the database, parts, and products.
I have a column in the products table with strings of ids (comma separated). Those ids match ids of the parts table.
**parts**
ID | description (I'm searching this part)
-------------------------------
1 | some text here
2 | some different text here
3 | ect...
**products**
ID | parts-list
--------------------------------
1 | 1,2,3
2 | 2,3
3 | 1,2
I'm really struggling with the SQL query on this one.
I've done the 1st part, got the id's from the parts table
SELECT * FROM parts WHERE description LIKE '%{$search}%'
The biggest problem is the comma separated structure of the the description column.
Obviously, I could do it in PHP, create an array of the the results from the parts table, use that to search the products table for id's, and then use those results to grab the row data from the parts table (again). Not very efficient.
I also tried this, but I'm obviously trying to compare two arrays here, not sure how this should be done.
SELECT * FROM `products` WHERE
CONCAT(',', description, ',')
IN (SELECT `id` FROM `parts` WHERE `description` LIKE '%{$search}%')
Can anybody help?
I would perhaps try a combination of LOCATE() and SUBSTR(). I work mainly in MSSQL which has CHARINDEX() that I think works like MySQL's LOCATE(). It is bound to be messy. Are there a variable number of elements in the parts-list field?

Populating a single-dimensional array with multiple MySQL column values

I am quite new to PHP and MySQL, but have experience of VBA and C++. In short, I am trying to count the occurrences of a value (text string), which can appear in 11 columns in my table.
I think I will need to populate a single-dimensional array from this table, but the table has 14 columns (named 'player1' to 'player14'). I want each of these 'players' to be entered into the one-dimensional array (if not NULL), before proceeding to the next row.
I know there is the SELECT DISTINCT statement in MySQL, but can I use this to count distinct occurrences across 14 columns?
For background, I am building a football results database, where player1 to player14 are the starting 11 (and 3 subs), and my PHP code will count the number of times a player has made an appearance.
Thanks for all your help!
Matt.
Rethink your database schema. Try this:
Table players:
player_id
name
Table games:
game_id
Table appearances:
appearance_id
player_id
game_id
This reduces the amount of duplicate data. Read up on normalization. It allows you to do a simple select count(*) from appearances inner join players on player_id where name='Joe Schmoe'
First of all, the database schema you're using is terrible, and you just found out a reason why.
That being said, I see no other way then to first get a list of all players by distinctly selecting the names of players into an array. Before each insertion, you would have to check if the name is already in the array (if it is already in, don't add it again).
Then, when you have the list of names, you would have to run an SQL statement for each player, adding up the number of occurences, like so:
SELECT COUNT(*)
FROM <Table>
WHERE player1=? OR player2=? OR player3=? OR ... OR player14 = ?
That is all pretty complicated, and as I said, you should really change your database schema.
This sounds like a job for fetch_assoc (http://php.net/manual/de/mysqli-result.fetch-assoc.php).
If you use mysqli, you would get each row as an associative array.
On the other hand the table design seems a bit flawed, as suggested before.
If you had on table team with team name and what not and one table player with player names.
TEAM
| id | name | founded | foo |
PLAYER
| id | team_id | name | bar |
With that structure you could add 14 players, which point at the same team and by joining the two tables, extract the players that match your search.

MySQL: How to get a sequential number with rows?

How can I number my results where the lowest ID is #1 and the highest ID is the #numberOfResults
Example: If I have a table with only 3 rows in it. whose ID's are 24, 87, 112 it would pull like this:
ID 24 87 112
Num 1 2 3
The reason why I want this, is my manager wants items to be numbered like item1, item2, etc. I initially made it so it used the ID but he saw them like item24, item87, item112. He didn't like that at all and wants them to be like item1, item2, item3. I personally think this is going to lead to problems because if you are deleting and adding items, then item2 will not always refer to the same thing and may cause confusion for the users. So if anyone has a better idea I would like to hear it.
Thanks.
I agree with the comments about not using a numbering scheme like this if the numbers are going to be used for anything other than a simple ordered display of items with numbers. If the numbers are actually going to be tied to something, then this is a really bad idea!
Use a variable, and increment it in the SELECT statement:
SELECT
id,
(#row:=#row+1) AS row
FROM table,
(SELECT #row:=0) AS row_count;
Example:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
INSERT INTO table1 VALUES (24), (87), (112);
SELECT
id,
(#row:=#row+1) AS row
FROM table1,
(SELECT #row:=0) AS row_count;
+-----+------+
| id | row |
+-----+------+
| 24 | 1 |
| 87 | 2 |
| 112 | 3 |
+-----+------+
How it works
#row is a user defined variable. It is necessary to set it to zero before the main SELECT statement runs. This can be done like this:
SELECT #row:=0;
or like this:
SET #row:=0
But it is handy to tie the two statements together. This can be done by creating a derived table, which is what happens here:
FROM table,
(SELECT #row:=0) AS row_count;
The the second SELECT actually gets run first. Once that's done, it's just a case of incrementing the value of #row for every row retrieved:
#row:=#row+1
The #row value is incremented every time a row is retrieved. It will always generate a sequential list of numbers, no matter what order the rows are accessed. So it's handy for some things, and dangerous for other things...
Sounds like it would be better just making that number in your code instead of trying to come up with some sort of convoluted way of doing it using SQL. When looping through your elements, just maintain the sequentiality there.
What is the ID being used for?
If it's only for quick and easy reference then that's fine, but if it's to be used for deleting or managing in any way as you mentioned then your only option would be to assign a new ID column that is unique for each row in the table. Doing this is pointless though because that duplicates the purpose of your initial ID column.
My company had a similar challenge on a CMS system that used an order field to sort the articles on the front page of the site. The users wanted a "promote, demote" icon that they could click that would move an article up or down.
Again, not ideal, but the strategy we used was to build a promote function and accompanying demote function that identified the current sort value via query, added or subtracted one from the previous or next value, respectively, then set the value of the initially promoted/demoted item. It was also vital to engineer the record insert to accurately set the initial value of newly added records so inserts wouldn't cause a duplicate value to be added. This was also enforced at the DB level for safety's sake. The user was never allowed to directly key in the value of the sort, only promote or demote via icons. To be honest, it worked quite well for the user.
If you have to go this route.....it's not impossible. But there is brain damage involved....

Categories