i have a form that has a multiple select drop down. a user can select more than one options in the select. the name of the select is array[]; using php I call implode(",",$array)
in mysql db, it stores the field as a text in this format "places"= "new york, toronto, london" when i want to display these fields i explode the commas.
I am trying to run a report to display the places. here is my select:
"select * from mytable where db.places .. userSelectedPlaces"
how can i check toronto in lists of "places" that user selected? note "places" in the db might be either just "toronto" or it might be comma separated lists of places like "ny, toronto, london, paris, etc".
If it is possible, you would be much better off using another table to hold the places that the user has selected. Call it SelectedPlaces with columns:
mytable_id - To join back to the table in your query
place - EG: "Toronto"
Then you can run a simple query to figure out if Toronto has been selected:
SELECT *
FROM mytable m
INNER JOIN SelectedPlaces sp ON sp.mytable_id = m.id
WHERE sp.place = 'Toronto'
If I understand you correctly, your database design is just wrong. Try reading about it more. Generally, in good design you should not have lists of values as one field in database and you should introduce new table for it.
But if you want to do it this way, you can use strcmp function.
If i understood correctly, this should work:
WHERE DB.PLACES LIKE '%TORONTO%'
but as other users said, its not a nice thing to have denormalized tables.
To directly answer your question, your query needs to look something like this
SELECT *
FROM mytable
WHERE places LIKE( '%toronto%' )
But, be aware, that LIKE() is slow.
To indirectly answer your question, your database schema is all wrong. That is not the right way to do a M:N (many-to-many) relationship.
Imagine instead you had this
mytable place mytable_place
+------------+ +----------+----------+ +------------+----------+
| mytable_id | | place_id | name | | mytable_id | place_id |
+------------+ +----------+----------+ +------------+----------+
| 1 | | 1 | new york | | 1 | 1 |
| 2 | | 2 | toronto | | 1 | 2 |
| 3 | | 3 | london | | 1 | 3 |
+------------+ +----------+----------+ | 2 | 2 |
| 3 | 1 |
| 3 | 3 |
+------------+----------+
The table mytable_places is what's called a lookup table (or, xref/cross-reference table, or correlation table). Its only job is to keep track of which mytable records have which place records, and vice versa.
From this example we can see that The 1st mytable record has all 3 places, the 2nd has only toronto, and the 3rd has new york and london.
This opens you up too all sorts of queries that would be difficult, expensive, or impossible with your current design.
Want to know how many mytable records have toronto? No problem
SELECT COUNT(*)
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
WHERE p.name = 'toronto';
How about the number of mytable records per place, sorted?
SELECT p.name
, COUNT(*) as `count`
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
GROUP BY p.place_id
ORDER BY `count` DESC, p.name ASC
And these are going to be much faster than any query using LIKE since they can use indexes on columns such as place.name.
Related
I am trying to display information on a website using PHP and MYSQL that shows all the locations an event can take place, along with the facilities each location includes. For example, a park (location1) may contain toilets (facility1), swings (facility3) and a slide (facility4).
Location1 Location2 Location3 Location4
Facility1 x x
Facility2 x x
Facility3 x x
Facility4 x x
Firstly, I am unsure of the best way to display these as tables in MySQL and then how I would display this clearly using PHP calls onto a webpage.
Any help would be appreciated
Database schema
I would like to recommend you to create 3 tables in your database:
locations
facilities
location_facility
Locations table
+----+------------+
| id | name |
+----+------------+
| 1 | location_1 |
| 2 | location_2 |
+----+------------+
Facilities table
+----+------------+
| id | name |
+----+------------+
| 1 | facility_1 |
| 2 | facility_2 |
| 3 | facility_3 |
+----+------------+
Pivot table (location_facility)
+-------------+-------------+
| location_id | facility_id |
+-------------+-------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 3 |
+-------------+-------------+
So, in pivot table you can store required information.
PHP application meta code
To get the data from your database in pure PHP - you can use PDO extension.
$sql = 'SELECT locations.name as loc_name,
facilities.name as facility_name
FROM location_facility
INNER JOIN locations ON locations.id = location_facility.location_id
INNER JOIN facilities ON facilities.id = location_facility.facility_id';
foreach ($conn->query($sql) as $row) {
print $row['loc_name'] . "\t";
print $row['facility_name'] . "\n";
}
In the database it is only one additional table, holding both PK from the related tables.
For display you'd choose a "master-detail" view: seleting one item from either table (master), showing all related records from the other (detail).
As others had commented, the tables required a third pivot table to connect the facilities and the location tables so many thanks for that. However, the next issue that arose was using these tables and connecting them to allow searches by facilities.
Hence an SQL query that grouped the facilities into one column within a view alongside all information from the location table.
CREATE VIEW locations AS
SELECT location.*, group_concat(facilities.FacilityName separator ',') AS facility_name
FROM location_facility
INNER JOIN location
ON location.LocationID = location_facility.LocationID
INNER JOIN facilities
ON facilities.FacilitiesID = location_facility.FacilitiesID
GROUP BY location.LocationName
ORDER BY location.LocationID ASC
This table can be queried using
SELECT *
FROM locations
WHERE facility_name LIKE %Toilet% AND facility_name LIKE %Parking%
This solved my issues and allowed the data to be displayed on a webpage exactly how I had desired.
Thanks again for the comments and help all!
I found this query that can combine two tables with different number of rows and without any related fields:
SELECT a.*, b.* FROM table1 a, table2 b;
Now my problem is that in my case I cannot change the value of the FROM.
Example I have this Query:
SELECT * FROM table1;
I cannot change that to:
SELECT * FROM table2;
At most, I can only add statements after FROM table_name LEFT JOIN somedefaulttable ON somedefaulttable2.id = somedefaulttable .id_c. (I forgot to add this important detail, I can only add custom queries after this line, I cannot remove this predetermined LEFT JOIN as this is system generated) The reason for this is because I am restricted by the CRM I am using, and I cannot edit the default select value but I can add additional custom queries after the said statement.
Now what my main problem here is that I need to combine a different table in my Query and I cannot use join as they don't have any common values and their number of rows are also different, I also tried using JOIN before to no avail, and thus decided to try using a different approach such as merging the two tables.
This is the link to my previous question where I was using JOIN to achieve my goal of combining the tables. In this link, you can see that I want to combine Table A and Table 4 but I cannot do so with JOIN as they have a different number of rows and that I am limited in changing my current Query to fit the posted answer.
Table A.
id | name | deleted | amount | due_date | status
1 | a | 0 | 10 | 2016-07-18 | Unpaid
2 | b | 0 | 20 | 2016-07-19 | Unpaid
3 | c | 0 | 15 | 2016-07-18 | Unpaid
Table B
id | name | due_date | status
1 | a | | Unpaid
2 | b | | Unpaid
3 | c | | Unpaid
4 | d | 2016-07-19 | Unpaid
Table C
id | table_d_id | table_a_id
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
Table D
id |
1
2
3
Try this for (inner) joining two tables without a JOIN Statement.
SELECT a.x, a.y, b.x, b.y FROM table1 a, table2 b WHERE a.x = b.x
What I do not understand is, how different numbers of rows affect the requested solution.
i have created a link of one to many between the tables
with your example and number of rows you inserted you should try this:
SELECT TableC.*
FROM TableB, TableD INNER JOIN (TableA INNER JOIN TableC ON TableA.ID = TableC.table_a_id) ON TableD.ID = TableC.table_d_id;
this will bring all the records of table C
Is it possible to make a list inside the cell of a table in mysql and outputs it the same way when being retrieved through php?
I've been trying to work it out but I don't know if it's possible.
Sample of this is in my packages table I have a column_name (Inclusions) and in every cell it has lists of data separated by new line. But whenever I retrieve it in php, it displays in one sentence.
Use nl2br() in your PHP code to translate the newlines to HTML <br> markup.
echo nl2br($row['inclusions']);
However, it probably would be better for you to normalize the database. Lists should not be in a single cell, they should be in a relation table. Then you can use a JOIN to find all the related values, and display them in whatever way is appropriate, such as in a <table> or <ul>.
While Barmar is correct, I would suggest a design change in your database. Instead of listing several values in a single cell, you should have a separate entry for each value and reference it to the list name.
+-----------+ +------------------+
| List_Name | | List_Item |
|-----------| |------------------|
| ID | Name | | List_ID | Value |
| 0 | Frt | | 0 | Apple |
| 1 | Veg | | 1 | Tomato |
+-----------+ | 1 | Potato |
| 0 | Pear |
+------------------+
Querying by List_Name:
SELECT b.Value
FROM List_Item b
JOIN List_Name a ON b.List_ID = a.ID
WHERE a.Name = 'Frt'
Return all of the values:
SELECT b.Value
FROM List_Item b
JOIN List_Name a ON b.List_ID = a.ID
Return all of the values and order by name:
SELECT b.Value
FROM List_Item b
JOIN List_Name a ON b.List_ID = a.ID
ORDER BY a.Name ASC | DESC
Return individual values:
SELECT b.Value
FROM List_Item b
JOIN List_Name a ON b.List_ID = a.ID
WHERE b.Value = 'Apple'
This will give you the raw output: Apple Pear (you can manually add a line break)
Of course, you can combine this with a loop and you can add a line break with php.
Edit: It took me a while to type this, and I did not see the edit on Barmar's answer.
I have 3 tables country_data, user_data and topic_data with table structures as given.
country_data:
name | code
---------------|---------------
India | IN
United States | US
Australia | AU
user_data:
user_ip | topic_code | country
---------------|---------------|---------------
192.168.1.1 | topic_code_1 | India
192.168.1.2 | topic_code_2 | United States
192.168.1.3 | topic_code_3 | Australia
topic_data:
name | code
---------------|---------------
topic_1 | topic_code_1
topic_2 | topic_code_2
topic_3 | topic_code_3
I have about one hundred thousand(100,000) rows in user_data table.
What I want is, I need to filter the count of users from each country with its corresponding country code for a given topic. For example, I need the count of users who viewed topic_2 in each country. The requered output format is
country_code | count
---------------|---------------
IN | 150
US | 120
AU | 100
Now please check my query:
SELECT cd.code, COUNT(ud.country) as count
FROM topic_data as td, user_data as ud, country_data as cd
WHERE td.name = 'topic_1' AND td.code = ud.topic_code AND ud.country = cd.name
GROUP BY ud.country
This one takes about 2 seconds to complete the execution in phpmyadmin. In the php webpage, it takes 15 seconds to load the page even in the server. by removing the group by in the query, ie GROUP BY ud.country, it takes more than 30 seconds to execute and the output is with the last country code and total of all countries visits. what am I doing wrong? please help.
----UPDATE----
Altered the tables using foreign keys and so as my queries too. now it works with lightning speed. thanks for those who helped.
The query doesn't look too bad IMO. However the normalization of the data looks a bit strange, e.g. why would you have a country (name) field on user_data table, just to join into country on name to look up the code? Instead, the more logical thing to me would be to reference country by country code (or other indexed key constraint). This would also save a join to country, if you just need the code as per your example query. If user_data is a high volume table, you will want to keep the data in it to a minimum to reduce IO when reading (density).
Also, as an aside, joining using JOIN instead of in the WHERE clause will improve the readability of your code, IMO:
SELECT cd.code, COUNT(ud.country) as count
FROM topic_data as td
INNER JOIN user_data as ud
ON td.code = ud.topic_code
INNER JOIN country_data as cd
ON ud.country = cd.name
WHERE td.name = 'topic_1'
GROUP BY ud.country;
To address the performance issues, check that the following indexes are in place:
Index on topic_data.name
Index on the foreign keys user_data.topic_code and user_data.country (or user_data.country_code if you do change the foreign key to user_data.country_code)
try this instead:
use below database structure for using numerical matching in INNER JOIN statement may decrease search time,
so index your id column of tables (e.g. primary key):
**country_data**
id|name | code
--|---------------|---------------
1 |India | IN
2 |United States | US
3 |Australia | AU
**user_data**
user_ip | topic_id | county_id
---------------|-----------|---------------
192.168.1.1 | 1 | 1
192.168.1.2 | 2 | 2
192.168.1.3 | 3 | 3
**topic_data**
id|name
--|------------
1 |topic_1
2 |topic_2
3 |topic_3
and run multiple INNER JOIN statment like:
SELECT cd.code, count(ud.topic_code) as count
FROM ud
INNER JOIN cd ON cd.id = ud.country
INNER JOIN td ON td.id = ud.topic_code
WHERE td.code='topic_1'
GROUP BY ud.country;
So I have this query:
SELECT * FROM cars {$statement} AND deleted = 'no' AND carID NOT IN (SELECT carID FROM reservations WHERE startDate = '".$sqlcoldate."') GROUP BY model
It basically checks the reservations table and then if there are reservations, it gets those carIDs and excludes them from the loop.
This is cool, so as there may be three dodge vipers and 2 are booked out it will only display the last one, and it will only display one at a time anyway because I group the results by model.
All that is good, however when it runs out of entries, so all the cars are booked out, the car does not appear in the list of cars. (As i clear from the query).
I would like a way to say if no rows of a certain car model are in the results, to display a placeholder, that says something like 'UNAVAILABLE'.
Is this possible at all? Its mainly so users can see the company owns that car, but knows its not available on that date.
You should probably handle this in the PHP, checking the number of rows returned and replacing the 0 with "UNAVAILABLE".
Based on TO comment:
In this case you want to look at
http://dev.mysql.com/doc/refman/5.1/en/case.html
This would need to go into the SELECT list like
SELECT
CASE car_count WHEN 0 THEN 'UNAVAILABLE'
WHERE ...
Without seen some of your data, its hard to give you a query, but if you move your subquery to your select expression, you could return the count available (which would be 0 when they are all reserved). Then when you display your data, you could then check if the count is 0, and display your unavailable message.
Edit:
Given the table cars:
+----+----------+
| id | model |
+----+----------+
| 1 | viper |
| 2 | explorer |
| 3 | viper |
| 4 | explorer |
+----+----------+
and the table reservations:
+-------+------------+
| carid | date |
+-------+------------+
| 1 | 2013-03-07 |
| 3 | 2013-03-07 |
+-------+------------+
A query similar to yours above will return:
+----+----------+
| id | model |
+----+----------+
| 2 | explorer |
+----+----------+
If you change it to something like:
SELECT
`outer`.`model`,
(
SELECT COUNT(*)
FROM
`cars` AS `inner`
WHERE
`inner`.`model` = `outer`.`model` AND
`inner`.`id` NOT IN(
SELECT `carid`
FROM `reservations`
WHERE `date` = '2013-03-07'
)
GROUP BY `inner`.`model`
) AS `count`
FROM cars AS `outer`
GROUP BY `outer`.`model`;
then you would get results like:
+----------+-------+
| model | count |
+----------+-------+
| explorer | 2 |
| viper | NULL |
+----------+-------+
If you then needed the NULL value to come back as a 0, you could use COALESCE, as Liv mentioned previously.
It's not pretty, and I'm sure it could be done a much cleaner way, but it does work.
There was a similar question asked here that might get you headed in the right direction. Check out the COALESCE() function.
The built-in function COALESCE() returns the first not-null value in its arguments. This lets you structure queries like SELECT COALSECE(foo, 'bar') [...] such that the result will be the value in column 'foo' if it is not null, or the value 'bar' if it is.