Joining MySQL tables and comparing columns to list data - php

I have been struggling with the proper way to extract the data I need. I am using MySQL with PHP and will be putting the data into a list format. My only problem is with the actual query itself. Here is how the DB is setup:
I have a 'chars' table for characteristics with the columns 'id', 'descrip', and 'class'.
I have a 'animal' table with the columns 'animal_id', 'charlist', ...
In 'chars' id is an incremented int, 'descrip' and 'class' are text/strings.
'id' and 'descrip' are different for every row but 'class' will be the same sometimes having a value like 'habitat', 'size', 'diet', et cetra.
What I am going for is a list that will look like this:
Habitatundergroundarctic
Dietfishinsectsomnivore
Here is where I start to have trouble. On a 'Details' page I am showing all of the data specific to a certain animal (whichever is clicked) and to reference the 'characteristics' each animal has it's own 'charlist' value in the database. This value is a string of numbers that reference the 'chars' table, like '2,55,67,90,122'.
So I've pulled all my (specific) animal data to a php variable on the page that I want to supply with the information. Now I need to use the 'charlist' data from that animal to lookup and list the characteristics relative to the animal.
My queries are looking like this, and I know I'm waaay off:
SELECT * FROM 'chars' LEFT INNER JOIN 'animals' ON chars.id IN (animals.charlist) WHERE ...
I've tried a lot of different ways and this is where I get lost. My brain is telling me to join the tables, find the numbers that are in the charlist WHERE animal.animal_id = mysql_real_escape_string($animal_id) - and of course MySQL tells me I can't do it this way.
I know that for the titles of the lists I'll probably have to do this with a GROUP BY condition.
Any help on the query and query syntax would be great. I'm fairly new to MySQL and I'm very happy and eager to learn how to do this right.
Thanks for reading.

a IN (x) returns true if (and only if) a equals x.
If x is a string (even one delimited by commas), then the statement will only be true if a is equal to that same string. That is, 123 IN ('123,456,789') is false. Note that this is not the same as passing multiple arguments such as 123 IN (123, 456, 789).
In MySQL, one could instead use FIND_IN_SET(), which expects a string delimited by commas. However, storing delimited lists in database columns is a really bad idea. You should instead define a third, relationship, table in which you store foreign keys into both of your existing tables: (animal_id, characteristic_id):
CREATE TABLE animal_characteristics (
PRIMARY KEY (animal_id, characteristic_id),
FOREIGN KEY (animal_id) REFERENCES animal (animal_id),
FOREIGN KEY (characteristic_id) REFERENCES chars (id)
) SELECT animal.animal_id, chars.id AS characteristic_id
FROM animal JOIN chars ON FIND_IN_SET(chars.id, animal.charlist)
;
Then you can do:
SELECT *
FROM animals
JOIN animal_characteristics USING (animal_id)
JOIN chars ON chars.id = animal_characteristics.characteristic_id
WHERE ...

Related

How to check if a DB array matches any values of a given array

I'm a little bit stuck.
I have an SQL column that contains weather codes (like Rain, Snow, etc.)
It is comma separated so the column would have a value of something like
rain,snow,haze
Now, I want to select the rows that contain values from an array.
I have an SQL code that is something like this:
SELECT * FROM locations WHERE currentWeather IN ('rain', 'snow', 'cloudy') ORDER BY name ASC
The problem is that this obviously works when currentWeather column only contains one item.
Is there a way to do it so that if the column value contains any of the items from the given array, it selects it?
Also, would it select it twice if two items match?
Best wishes
Use unnest in a subselect.
Select distinct A.myArray from (select unnest(column) as myArray from table) A where A.myArray in (your words to filter for)
Notice that using arrays in sql isn't very ideal and does not follows normalization rules. Your tables should ideally not contain arrays but rather just several rows each one containing the specific value you Want. It prevents issues such as this one.
To avoid the selection of repeated values, use the Distinct keyword right after you write select.
Rsference:
https://www.w3resource.com/PostgreSQL/postgresql_unnest-function.php
WHERE FIND_IN_SET(currentWeather, "rain,snow,cloudy")
Picks apart the string at commas (only) to see if currentWeather is any one of those 3 'words'.
See also FIELD(...)

How to check if some string is combination of values from two separate columns i.e tables PHP MySql

I need the least expensive way to check if my url slug is formed from the values from two separate columns from two separate tables.
I will use dummy example with stores and locations to make this more human readable.
I have the following url:
www.domain.com/store-location
This could be, for example:
www.domain.com/three-words-store-chicago or
www.domain.com/nicestore-new-york-or-some-neighbourhood-with-more-words or
www.domain.com/oneword-oneword
(you get the idea)
Stores are located in table called stores, and locations in the table called locations.
All the combinations are possible in theory.
So, I would need some clever mysql query combined with php which will check if my slug (what goes after .com/) is the exact combination of store+location. So, to make it more descriptive:
url: www.domain.com/cool-store-los-angeles
Check is there "cool-store" in the table stores.slug_stores and is there "los-angeles" in the table locations.slug_location. The number of words of both is undefined as you can see above, so I don't have any possible delimiter.
IT MUST BE THE LEAST EXPENSIVE WAY because both tables tables have around 1000 lines. PLEASE HELP AND THANK YOU GUYS!
ps. IMPORTANT: I MUSTN'T CHANGE URLS IN ANY WAY
Edit: This is real project, website. Depending on the url i.e. slug I return some view with data. So I need to check for www.domain.com/nicestore-nicecity if Nicestore and Nicecity exist in tables stores and locations, and if not, or if anything else is there like www.domain.com/nicestore-nicecityBLABLA to kill that page with 404. Otherwise, if there is Nicestore and Nicecity to return some page populated with related data. I tried so far to make separate table with formed slugs like "nicestore-nicecity" and to use it for queries "SELECT whatever FROM slugs WHERE whatever = 'nicestore-nicecity' and if there is line return whatever I need to show the page ... Simplified... But, this separate table is hard to maintain. If nicestore moves to uglycity, or if it changes name, or if you add a new store or new city. I hope I was more clear now ;-)
I'm assuming that you don't have any id values on which to JOIN your tables, and that you don't have the ability to create such values. In that case, since your store/location combination could be as short as oneword-oneword, the first and last words of the slug are about as much as you can search on. You can extract the start and end parts of the slug using SUBSTRING_INDEX and use that to narrow the set of matches in each table before you try and compare the whole string. In my example, I'm using an SQL variable to store the slug:
SET #store = 'cool-store-los-angeles'
SELECT *
FROM (SELECT *
FROM stores
WHERE store LIKE CONCAT(SUBSTRING_INDEX(#store, '-', 1), '%')) s
JOIN (SELECT *
FROM locations
WHERE location LIKE CONCAT('%', SUBSTRING_INDEX(#store, '-', -1))) l
WHERE CONCAT(s.store, '-', l.location) = #store
This will return all data associated with cool-store-los-angeles assuming that such a store exists.
Demo on dbfiddle
Here's what I know about your system...
You have a stores table with column slug_stores
You have a locations table with column slug_location
I'm going to assume that each table has an id column of some type. I'm also going to assume they have a many-to-many relationship using a third junction table, something like
CREATE TABLE store_locations (
store_id <type>,
location_id <type>,
PRIMARY KEY (store_id, location_id),
FOREIGN KEY (store_id) REFERENCES stores(id),
FOREIGN KEY (location_id) REFERENCES locations(id)
);
If you don't have this sort of relationship defined, I really don't know how you maintain your store locations.
What I would suggest is creating a VIEW to calculate and represent your URLs. For example...
CREATE VIEW store_location_urls AS
SELECT
sl.store_id,
sl.location_id,
CONCAT_WS('-', s.slug_stores, l.slug_location) AS slug
FROM store_locations sl
INNER JOIN stores s ON sl.store_id = s.id
INNER JOIN locations l ON sl.location_id = l.id;
Now you can use this view to do the following...
Check if a request URL slug is valid
SELECT store_id, location_id FROM store_location_urls WHERE slug = ?
If this returns a record, you can then further query the stores and locations tables for whatever extra data you need to render your page (or even just join them in the original query). Otherwise, use
http_response_code(404);
Get all the URL slugs for a particular store
SELECT slug FROM store_location_urls WHERE store_id = ?
Similarly, you could get all the URL slugs for a particular location
An extra note... due to concatenating strings, any indexes you have on stores.slug_stores and locations.slug_location will be useless with the above VIEW. The alternative is to use a real derived table (like what you currently have) and maintain it with triggers.
I think you can query like following in mysql and if do check in php afterwards. From your description, it doesn't sound like there is any join possible between those tables so, union is required, i think.
select col1,col2 from stores where slug_stores = ?
union
select col1,col2 from locations where slug_location = ?

Replace specific column data in select query?

I am trying to replace a column in the result of the select query as denoted in
This reference but unlike the example I have many columns in the table thus I can not specify the name of every column in the select query.
I tried some ways to attain the same but none seems effective.
select
*, (REPLACE(REPLACE(role_id,1,"admin"),2,"moderator") AS role_id
from user;
or
Select *
from user
where role_id = (select REPLACE(role_id,1,"admin") as role_id from user;
Here we assume only two possible values for the role_id however at certain instanced it might have to get data from another table ie a different table that holds different ids and values corresponding to them.
So is there a way to attain the following conditions in a single query:-
to replace values of some fields returned from select query (assuming many columns writing the names of all the columns individually is not feasible)
to get the replacement values from different tables for different columns in single table.
I need to implement the above conditions in one query but the changes shouldn't be in the database only the result of select query needs to be optimized.
Already referred to the following too but could not help.
Link 1
Link 2
Link 3
I am using phpmyadmin as engine and php as the implementation language.
If i have understood your question correctly, it's easier to use CASE/WHEN
SELECT *,
CASE WHEN role_id = 1 THEN "admin" WHEN role_id = 2 THEN "moderator" END AS role_id
FROM user;
But easier still maybe to have an array in PHP,
$roles = array("1" => "admin", "2" => "moderator", .... );
and look it up in the array. that will keep your query short and sweet. The advantage of this approach is that you don't need to change your query every time you add a new role. If you get a large number of roles (say dozens) you might actually want a separate table for that.

select table name to join from SQL query

In current database design, I have a main table called "Leads" and some other tables starting with product_ ( product_life_insurance, product_medical_insurance , ... )
-Leads Table :
--ID
--Product
...
-Product_Life_insurance Table :
--ID
--LeadID
...
a Lead Row :
ID 5
Product: life_insurance
a product_life_insurance Row:
ID 1
LeadId 5
..
Is there anyway to create a query to select table Name from Leads and add "product_" prefix to it and then join it to product table ?
I mean :
SELECT *
FROM `leads` JOIN `product_life_insurance` ON `leads`.`id` = `product_life_insurance`.`leadID`
WHERE `leads`.`id` = '5';
I want to select table name for join from leads table and add "product_" prefix to it and use it in my query.
Thanks :)
You asked:
Is there any way to create a query to select table Name from Leads and
add the "product_" prefix to it and then join it to product table ?
The answer is no. In pure SQL, you can't make variables of table names and then use them in your queries. You can, of course, do this in your host php code. But you'll need to use one query to fetch the table names, and more queries to fetch the results from that table name.
You can also use Dynamic SQL. That's a MySQL feature allowing you to create the text of SQL queries dynamically in the MySQL server, and then run those queries.
It sounds to me like you're trying to store several classes of entities (life insurance, annuities, vehicle insurance, others) having widely differing attributes.
This presents you with some schema-design options.
Should you use different tables (as you are doing), showing the
entity (lead) class (life insurance) in a master table, and joining
the particular table you need?
Should you try to coerce all the attributes into a single entity,
leaving NULL or blank the attributes that are irrelevant for a
particular class of entity?
Should you use a key/value store for your entities, the way
WordPress's wp_postmeta table does?
Option 3 has a disadvantage if you do a lot of searching on attribute values: it requires your attributes to all be stored with the same data type. That data type is probably varchar(n). That means that it's hard to search on ranges of numeric attribute values. For example '10' is BETWEEN '1' AND '9' considered as text, but that's nonsense numerically. You can beat that problem using implicit typecasting, but that defeats the use of an index. That is,
0+meta_value BETWEEN 0 AND 9
forces the comparison to work numerically on the meta_value column. It works, but not fast. That being said, Option 3 is the most flexible by far; you can add new attributes without changing table definitions.
A combination of Option 2 and Option 3, putting the most commonly searched attribute values into your main lead table, will probably yield the most robust solution.
Option 1 -- your present solution -- is a performance nightmare waiting to attack you when you can least afford it: as your application is scaling up.
NOTE: if you are using the MariaDB fork of MySQL your key_value table can contain a persistent, indexed, virtual column. For example,
meta_key VARCHAR(255),
meta_value VARCHAR(255),
meta_value_int BIGINT(20) AS (0+meta_value) PERSISTENT,
meta_value_float FLOAT AS (CAST(meta_value AS DECIMAL(30,10))) PERSISTENT
You can then index those virtual (defined with AS) columns and search them fast. meta_value columns that don't start with numbers will have the value 0 in the virtual columns.

How to convert string value separated by comma into integer from mySQL

I am working on a php project to retrieve data from mysql. I have a list of codes that is stored as a string separated by commas that is referenced in another table. Is there a way to get all the value from the string and return the text it referenced?
For example, item_purchased might contain one or more than one item_code. I want the query to return item names instead of item codes.
//item_purchased for transaction 123 --> ,111,222,333,
SELECT s.transaction_id, s.item_purchased, i.item_id
FROM stock s
INNER JOIN ref_item i
ON s.item_code = i.item_code
WHERE transaction_id = 123
Desired outcome: apple, carrot, milk (not ,111,222,333,)
Is there a way to do this preferably within mySQL query or maybe in PHP?
This is one of the reasons why you shouldn't use comma-separated lists in relational databases.
The workaround is to use the FIND_IN_SET() function in the join conditions, instead of =.
SELECT s.transaction_id, GROUP_CONCAT(i.item_name)
FROM stock s
INNER JOIN ref_item i
ON FIND_IN_SET(i.item_code, s.item_purchased)
WHERE s.transaction_id = 123
GROUP BY s.transaction_id
But unfortunately, this makes the query very inefficient, because it can't use an index to search the ref_item table. It has to do a table-scan, so it ends up having very poor performance, and gets much worse the larger your table gets.
Is item_purchased a comma separated string of item_code?
I'm not good with joins but I think this will do
SELECT s.transaction_id, s.item_purchased, DISTINCT(i.item_id)
FROM stock s, ref_item i
WHERE i.item_code in (s.item_purchased )
AND s.transaction_id = 123
This will return a list of all the items in the item_purchased column if my assumption of the item_purchase is right.

Categories