Something I've been stumbling on for a while now. I have a table called gears which contains rows with the names: id, mid, cid and installed. I want to search this table and return in csv format a list of mids for some unique cid. For example if cid = $cid I can use:
$query = $database -> query("SELECT COUNT(mid), GROUP_CONCAT(mid) FROM gears WHERE cid=$cid", __LINE__, __FILE__);
$gears_installed = $database -> get_result($query);
$gears = $database -> get_result($query, 0, 1);
Don't worry about the function names, they do exactly as one would expect. So if there were 3 rows for that specific $cid, with mids: bank, lottery and post then $gears_installed would be equal to 3 and $gears would be equal to bank,lottery,post. This works as intended.
Now on to the question I have. Each unique mid has its own table, named settings_mid_here. I.e, for the above three, I have the tables settings_bank, settings_lottery and finally settings_post. Each of these tables will also have a column called cid (this is how the two can be related). How do I go about running one query to return the entire row from each table where cid=$cid? I do not want to run a separate query for SELECT * FROM settings_bank WHERE cid=$cid and SELECT * FROM settings_post WHERE cid=$cid and finally SELECT * FROM settings_post WHERE cid=$cid, as this could result in around 10 extra queries on one page load (there are, at the moment, 10 different mids).
As you can see, the problem is dynamic. It must be able to adapt to a different number of mids, somehow differentiate the settings within each table (for example settings_bank may have a column with name name, and so might settings_post). Finally, it must also be able to return a default row (not null values) if there does not exist a row corresponding to the given $cid.
A complicated task but I hope someone can help me with this as I have not been able to get anywhere.
$queries = array();
foreach(explode(',', $gears) as $gear) {
$queries[] = "SELECT '$gear' AS gearname, settings_$gear.* FROM settings_$gear WHERE cid=$cid";
}
$sql = implode(' UNION ', $queries);
$query2 = $database->query($sql);
This query will return one row for each table, with an extra gearname column to indicate which table that row came from.
Or you can create a JOIN dynamically:
$gears_array = explode(',', $gears);
$joins = implode(' JOIN ', $gears_array);
$wheres = implode(' AND ',
array_map(function($g) use ($cid) {
return "$g.cid = $cid";
}, $gears_array));
$sql = "SELECT * FROM $joins WHERE $wheres";
$query2 = $database->query($sql);
This is not really an answer to your specific question, simply because there's no way to accomplish what you are trying with one query.
The reason is simple: RDBMSs are not designed to work this way. Tables are supposed to store data that represent entities and relations. In your case, for each distinct value of mid, a table named settings_{mid} must exist, thus forcing the mid column implicitly store (a part of) a table name. But that's not data, that's metadata.
That would not really be a problem if SQL syntax could accept variable, parametrized, column-related or arbitary table names. But it doesn't. And that's by design. Instead, an RDBMS provides you with all the tools you could ever need to relate your data to each other. By using it the intended way, you'll never have to resort to such 'dynamic' tricks.
In your case, there should be one config table with a mid column to distinguish the rows that refer to the specific mid value. Then, the query would be simple:
select * from `config` where mid='$mid' and cid='$cid'
This is the relational way. Thus the R in RDBMS. There's absolutely no reason at all to mix data with metadata. If you do, you move the relation resolution problem in higher levels of the application model.
And one last thing: One might argue that the config_{mid} tables might have similar but not identical structure. There's a solution for that too: IS-A relations.
Having said that, for your specific problem, a solution along the lines of Barmar's answer would do the trick.
Related
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 = ?
I need a bit help with a query. I want extract the content from three different table, this table have different records so the code key is different agains the other table. The table contains the credentials for the login, I need to check if the code and the email is found in almost one table, this is an example:
$Query = "SELECT * FROM login_tb1, login_tb2, login_tb3
WHERE (code = '".mysql_real_escape_string($code)."') OR
(email = '".mysql_real_escape_string($email)."') ";
Now when I execute this query I get this error:
Column 'code' in where clause is ambigous
I can't perform a JOIN 'cause the column code is different, all the records are different. All the table have the same structure anyway. How can I fix this?
Thanks.
It took me a while to understand that error message as well. Ambigous is such a fancy word. In human language, it means that MySQL is trying to tell you: "Hey man, the column exists in multiple tables in the select clause, so I don't know which table you mean!". You should be explicit about the table:
$Query = "SELECT * FROM login_tb1, login_tb2, login_tb3
WHERE (login_tb1.code = '".mysql_real_escape_string($code)."') OR
(login_tb1.email = '".mysql_real_escape_string($email)."') ";
If you don't care about which table and they have the same schema, you still need to be explicit, so your query will be a bit more advanced:
SELECT * FROM login_tb1 as l1, login_tb2 as l2, login_tb3 as l3
WHERE ( l1.code = 'code' OR l1.email = 'email' )
OR ( l2.code = 'code' OR l2.email = 'email' )
OR ( l3.code = 'code' OR l3.email = 'email' )
Side note: I don't know your exact use case, but it seems a bit like an anti-pattern that you have multiple login tables with the seemingly same schema. Unless you have a very specific reason not to, you should keep it in a single table.
Code column would be part of more than one table.
in this case prefix with anyone of the table..
e.g login_tb1.code
this would do the trick
You need to tell mysql which code column to use.
Apparently, that column appears in more than one table, so for each table you want to check the code column, write it like this:
tablename.code
This way mysql will know which column to use
I also suggest taking a look at your ta le structure, since having 3 tables with the same structure seems weird.
You can post a question a out the as well or elaborate.
There are similar columns in the tables. You need to properly alias the table
$Query = "SELECT t1.col,t2.col,t3.col FROM login_tb1 as t1,
login_tb2 as t2, login_tb3 as t3
WHERE (t1.code = '".mysql_real_escape_string($code)."') OR
(t2.email = '".mysql_real_escape_string($email)."') ";
I got this example, but I want to know in an overall way, how to fetch 2 column with the same name in MySQL without using an alias?
Example:
$query = "SELECT app.*, categorie.* FROM app JOIN categorie ON app.id = categorie.id";
while ($row = mysql_fetch_array($query)) {
$categorie = $row[categorie.name]; //is there a way to do this?
$aplicativo = $row[app.name];
}
This example is simply solved with an alias, but is there another way, something like the example? Thank you for your time
mysql_fetch_array
Warning
This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQL extension should be used. See also MySQL: choosing an API guide and related FAQ for more information. Alternatives to this function include:
mysqli_fetch_array()
PDOStatement::fetch()
If two or more columns of the result have the same field names, the last column will take precedence. To access the other column(s) of the same name, you must use the numeric index of the column or make an alias for the column. For aliased columns, you cannot access the contents with the original column name.
Example #1 Query with aliased duplicate field names
SELECT table1.field AS foo, table2.field AS bar FROM table1, table2
So in your case the query should be, as the documentation suggests, using alias or numeric index:
SELECT app.name as "app.name",... , categorie.name as "categorie.name",... FROM app JOIN categorie ON app.id = categorie.id
Use PDO's FETCH_NUM type when fetching your rows to get a 0 indexed array that will contain both values, even if they have the same column name.
function selectStuff(\PDO $pdo)
{
$stmt = $pdo->prepare('SELECT app.name, categorie.name FROM app JOIN categorie ON app.id = categorie.id');
$stmt->execute();
foreach ($stmt->fetchAll(\PDO::FETCH_NUM) as $row) {
// $row[0] contains app.name
// $row[1] contains categorie.name
}
}
You have to leverage a 0 indexed fetching of columns to be able to do that without alias. The reason is simple: you can't logically access two different values in an array if they share the same key.
Notice that I explicitely named the columns to be selected. You should prefer that to selecting all fields, for readability reason, and also (arguably this is in most cases not noticeable) for performance reasons.
Disclaimer: mysql_fetch_array()
I understand this is not what should be recommended theese days, but I havent moved away as of yet since its still running great on any PHP 5.x server around where I'm from. Soon the time is there so I can refactor my codebase, maby even sooner for all I know. That being said...
Back to your question...
I was googling and looking for something else I see after reading, but I am beginning to understand that the entire approach of being able to read what you are asking for has no practical use in real world. That is, compared to changing your query adding some aliases like this:
$query = "SELECT app.name AS appname, categorie.name AS categoriename, app., categorie. FROM app JOIN categorie ON app.id = categorie.id";
This way your returned query returns more fields with unique names so you can access them like you ask for.
But say you were looking for a way to accomplish what you ask without changing your sql-query. You need to know the order of results, especially if there are more than one ambiguous name in the list as you will not be able to reorder the ambiguous fields, MySQL does return all your fields you see its just the way an array works you do not get the answers you want. Or, my disclaimer, this is one of the reasons I should move away from this MYSQL_FUNCTIONS... (I hope so because if so this is a damn great reason alone, mysql_fetch_assoc() should have an option to lay ouu an array of results as for this example would be ['id','name','id','name'])
You do not say anthing about your DB however say they both have two columns id and name for this example. Return results for each database would be id, name or in the case of your query it would be : id, name, id, name
How does this work? A resulted array from mysql_fetch_array() looks like this:
Say app db has values: 1, app name
Say category db has values: 2, category name
Array
(
[0] => 1
[id] => 2
[1] => 'app name'
[name] => 'category name'
[2] => 2
[3] => 'category name'
)
So if you know the order of results you can atleast access the different fields even if they have the same name. However looking at the simple example above it would be hard to say if [2] is [id] or [name] but if you know the order...
Then again, select * and someone changes the database... adds a column...
$query = "SELECT app.name as `appname`, categorie.name as `catname`,
and_maybe_other_columns_here
FROM app JOIN categorie ON app.id = categorie.id";
while ($row = mysql_fetch_array($query)) {
$categorie = $row['appname']; //is there a way to do this?
$aplicativo = $row['catname'];
}
I'm building a small internal phone number extension list.
In the database I have two tables, a people table, and a numbers table. The relationship is a one-to-many respectively.
I want to display the results in a single HTML table with one person per row but if they have multiple numbers it shows those in a single column with a rowspan on the person row to compensate.
Now, to get the results from the database to work with, I can either do:
(pseudocode)
SELECT id, name
FROM people
foreach result as row {
SELECT number
FROM numbers
WHERE numbers.person_id = row['id']
}
This would mean that I'm doing one query to get all users, but then if I have 100 users, I'm performing 100 additional queries to get the numbers for each user.
Instead I could do it like this:
(pseudocode)
SELECT number, person_id
FROM numbers
SELECT id, name
FROM people
foreach people as person {
echo name
foreach numbers as number {
if (number.id = person.id) {
echo number
}
}
}
So, essentially it is doing the exact same thing except instead I do two queries to get all the results into arrays and then loop through the arrays to format my tables.
Which method should I be using or is there a better way to do this?
The common way is to do a regular JOIN:
SELECT id, name, number
FROM people, numbers
WHERE people.id = numbers.person_id;
You can either add an ORDER BY to get the numbers in order, or you could create an array with a single pass over the resultset, and then loop through that array.
You can also consider a GROUP_CONCAT to concatenate all the numbers for the same person:
SELECT id, name, GROUP_CONCAT(number SEPARATOR ', ')
FROM people, numbers
WHERE people.id = numbers.person_id
GROUP BY people.id;
Since you are even asking this question: I cannot stress the fact that you should pick up an introductory book on database design. It helped me wonders to learn the theories behind relational databases.
You probably want to execute just one query. Something like
select people.id, people.name, group_concat(numbers.number)
from people
inner join numbers on numbers.id = people.id
group by people.id, people.name
order by people.name
Then you can loop over the result set with simple php code.
It depends, and you may have to time it to find out. Doing multiple queries is a lot of network turns if your database is on a different computer than your web server, so often this takes longer. However, if your database server is on the same computer as your web server, this might not be an issue.
Also consider the time it will take to look up the number in the array. As an array you are doing a linear order O(N) search. If you can put it in a hash, the lookup will be much faster, and the two query approach may be faster, but not if you spend a lot of time looking up the answer in your array.
Using a join to get it into one query, may be fastest, as the numbers will be associated with the people, depending on your container structure you are storing the data into to be accessed in your foreach loop.
Use a stored procedure or function to retrive the data, don't wite the sql in your programm
You should do neither. You can do one query (join) over the tables:
select name, number
from people p, numbers n
where p.id = n.person_id
order by name, number;
and then just one loop:
$link = mysqli_connect(...);
$query = 'select name, number from people p, numbers n where p.id = n.person_id order by name, number';
if ($result = mysqli_query($link, $query)) {
$person = '';
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
if ($row['name'] !== $person) {
$person = $row['name'];
echo $person;
}
echo $row['number'];
}
}
I am accessing a database with names tied to companies. Names are unique in the table. There can be multiple people that have the same company. I need to return only ONCE instance of the company if is duplicate, so that I can display the information according to company. My code is as follows:
$result = $pdo->query('SELECT * FROM vendoreducation WHERE company LIKE "TestCompany" GROUP BY company');
That obviously doesnt work, and I dont want to specify individual companies. I just want to be able to return a list of all of the companys in the DB. If there are multiple entries that have the same company name, just return one of those.
EDIT
The answer I chose was in the right direction, but the answer that I ended up going with was m.buettner's answer.
I would ask for clarification in a comment, but I am lacking the rep, sorry. If you only need one row per company, does it even matter what the other columns are? Or do you really just want to find out a list of all companies?
For the latter, this should do:
$result = $pdo->query('SELECT DISTINCT company FROM vendoreducation;');
Of course, you could also include any columns that will be identical for all rows of the same company. However, since some columns will probably have values that differ (like the names you mentioned), using DISTINCT with * will probably just give you all rows again.
EDIT: If you DO want one full row per company you could simply use the list of all companies retrieved by the line above and then query one row for every single company, by using the LIMIT 1 solution with every individual company in turn. But maybe then you should think about normalization anyway if that is an option at all (i.e. if you can alter the database).
Use DISTINCT
$result = $pdo->query('SELECT DISTINCT * FROM vendoreducation WHERE company LIKE "TestCompany"');
$result = $pdo->query('SELECT * FROM vendoreducation WHERE company LIKE "TestCompany" GROUP BY company LIMIT 1');
OR
$result = $pdo->query('SELECT distinct * FROM vendoreducation WHERE company LIKE "TestCompany" GROUP BY company');