I am just getting started in learning how to do INNER JOINS correctly and I can't think of the best/easiest way to do this.
I am building a url shortener and I am trying to build a query that will get all long_url.destination's matching a slug "test". One slug might point to multiple long_url.destination's(URL shuffling, GEO matching, etc...). So I need the slug to get all long_url.destination's with the same short_url.slug.
Before I was running another query to get the short_id from the slug, then running another query to select all rows in long_url that had a matching short_id.
I think it might be quicker if I use an inner join, but I am unsure how to properly set it up.
I want to get all destination columns in table long_url with only the slug data in short_url without having to run a separate query to get the short_id from the slug.
Table: short_url
Columns: short_id | slug | enabled | timestamp
example: 1 test 1 1323343922
Table: long_url
Columns: long_id | short_id | destination | geo | enabled | timestamp
example: 1 1 http://www.test.com US 1 132334922
example: 2 1 http://www.test.co.uk UK 1 132334922
I got this so far:
SELECT destination, geo FROM long_url INNER JOIN short_url
ON long_url.short_id = short_url.short_id WHERE enabled = 1;
function get_long_urls($slug) {
$query = "SELECT....";
$stmt = $db->prepare($query);
$stmt->execute(array(':slug' => $slug));
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
return (array) $results:
}
example $results = array(
'http://www.test.com' => 'US',
'http://www.test.co.uk' => 'UK',
);
Thanks for any help.
select long_url.destination
, long_url.geo
from long_url
inner
join short_url
on long_url.short_id = short_url.short_id
where short_url.slug = :slug
and long_url.enabled = 1
You don't need to qualify all column names like I did, because in this particular query there wasn't any ambiguity. All I really did is add a bound parameter placeholder.
SELECT destination, geo FROM long_url LEFT JOIN short_url
ON (long_url.short_id = short_url.short_id) WHERE enabled = 1
Related
Okay so I have a simple category table and a separate posts table easy right but when the user posts a post I wast think should I store both the sub and parent cat in the posts table but would that not be a lot of data duplication so I instead just store the sub_cat then I use a few PHP functions to query the database for the primary cat and its name.
categories table
ID | cat_name | main_cat
1 | Dinner | 0
2 | Chicken | 1
posts table
ID | title | sub_cat | fields that are not related to Q
1 | test | 2 |
Get parent(main) category
$sub_cat = is from a selection query that gets posts and their sub_cats
function main_cat($sub_cat){
require("conn_posts.php");
$stmt = $conn_posts->prepare("SELECT `main_cat` FROM `cats` WHERE `ID` = ?");
$stmt->bind_param("s", $sub_cat);
$stmt->execute();
$stmt_results = $stmt->get_result(); // get result
while($row_get = $stmt_results->fetch_assoc()){
if($row_get['main_cat'] == 0){
return $sub_cat;
}elseif($row_get['main_cat'] !== ""){
return $row_get['main_cat'];
}
}
}
This function gets any category name as long as the id is valid
function cat_name($cat_number){
require("conn_posts.php");
$stmt = $conn_posts->prepare("SELECT `cat_name` FROM `cats` WHERE `ID` = ?");
$stmt->bind_param("s", $cat_number);
$stmt->execute();
$stmt_results = $stmt->get_result(); // get result
$row_get = $stmt_results->fetch_assoc();
if($stmt_results->num_rows <= 0){
return 0;
}elseif($stmt_results->num_rows == 1){
return $row_get['cat_name'];
}
}
My question is is this a good way to process my posts sub-category and parent category are there better ways of doing what I am currently doing? eg. is my database schema good(by good I mean is it better to just include the parent cat id in the posts table than to do the PHP server-side processing)?
Your database schema is good: it doesn't include any replication, I wouldn't change it. The way you're handling fetching the categories in PHP isn't really optimal though: you should almost always aim to minimize the number of queries as it (in general) will affect performance more than the complexity of a query.
If you're running MySQL 8+, a great way to do this is with a recursive CTE; it will allow you to fetch all parents with one query:
WITH RECURSIVE cte AS (
SELECT id, cat_name, main_cat, 0 as depth FROM categories WHERE ID=3
UNION ALL
SELECT categories.id, categories.cat_name, categories.main_cat, cte.depth+1 as depth
FROM cte inner join categories
ON cte.main_cat = categories.id
)
SELECT cat_name FROM cte order by depth ASC
The number '3' in that query can be replaced by the category you're trying to retrieve. You can check this DB fiddle for a live example. If I see your code, incorporating it into your PHP should be fairly trivial. If not, leave a comment and I'll try to expand.
The question has been resolved. But if you have a "better" or another way to do it then feel free to add a comment! Thanks all for reading! :)
I'm trying to make a dynamic query. Everything is working perfectly except for one thing. I've Google'd for days but I can't figure out how I can make the following work;
SELECT project.name, project.description, track.name, track.description
, SDG.position, SDG.title, SDG.description
, sprint_numbers.number, sprint_options.option
, resources.name, resources.description
, URLs.URL
FROM project INNER JOIN track ON project.track_id = track.id
INNER JOIN project_SDG ON project.id = project_SDG.project_id
INNER JOIN SDG ON project_SDG.SDG_id = SDG.id
INNER JOIN sprint ON sprint.project_id = project.id
INNER JOIN sprint_numbers ON sprint_numbers.id = sprint.sprint_number_id
INNER JOIN sprint_options ON sprint_options.id = sprint.sprint_option_id
INNER JOIN resources ON project.id = resources.project_id
INNER JOIN URLs ON URLs.id = resources.id
WHERE 1=1
AND MATCH (project.name) AGAINST (:name_project)
AND MATCH (project.description) AGAINST (:description_project)
AND SDG.id = :SDG_1
AND SDG.id = :SDG_2
The query executes but does not return anything. The problem is that the SDG.id can't be true to both :SDG_1 and :SDG_2.
Using the OR operator works, but that does not return it the way I want. It must "act" as an AND operator. (:SDG_1 & :SDG_2 are the names of the PHP variables that bind to the SQL statement parameters.)
The query should filter for both values. The values given to :SDG_1 and :SDG_2 must both exist in the SDG.id column of the project_SDG table. If the value of :SDG_1 exists, but :SDG_2 not, then the query should not return anything.
I found this on StackOverflow but it did not work for me: SELECTING with multiple WHERE conditions on same column
I hope someone can help me out.
EDIT: minimal reproducible example
QUERY:
SELECT * FROM project
INNER JOIN project_SDG ON project.id = project_SDG.project_id
INNER JOIN SDG ON project_SDG.SDG_id = SDG.id
WHERE SDG.id = 1 AND SDG.id = 7 AND SDG.id = 14 AND SDG.id = 17
Project table
+------------------+---------------------------+------------+
| id name | description | track_id |
+------------------+---------------------------+------------+
| 1 project name | This is a description 2 | |
+------------------+---------------------------+------------+
SDG table
+-----+-----------+-------------+---------------------------------------------+
| id | position | title | description |
+-----+-----------+-------------+---------------------------------------------+
| 1 | 1 | SDG 1 to 17 | There're multiple SDGs ranging from 1 to 17 |
| 17 | 17 | SDG 1 to 17 | There're multiple SDGs ranging from 1 to 17 |
+-----+-----------+-------------+---------------------------------------------+
project.SDG (bridge-table)
+------------+--------+
| project.id | SDG.id |
+------------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+------------+--------+
You want for each project.id both values :SDG_1 and :SDG_2 to exist for SDG.id, so use this in the WHERE clause:
WHERE 1=1
AND MATCH (project.name) AGAINST (:name_project)
AND MATCH (project.description) AGAINST (:description_project)
AND project.id IN (
SELECT project_id
FROM project_SDG
WHERE SDG_id IN (:SDG_1, :SDG_2)
GROUP BY project_id
HAVING COUNT(DISTINCT SDG_id) = 2
)
Could you provide a minimal reproducible example for your query?
Generally speaking, one field cannot be equal to two different values in the same time. So, you have either mixed up the logical operators or you need two different fields.
I can assume that in your case there may be several related records with different values. In this case, you need to join the same table twice with different aliases. Let's say as SDG1 and SDG2. After that you can compare
... `SDG1`.id = :SDG_1 AND `SDG2`.id = :SDG_2
Update:
The win trick is groupping. You can enumerate all required SDG IDs and count how many of them is in group. Just for example in case of two IDs:
SELECT project.id
FROM project
JOIN project_SDG ON project_SDG.project_id = project.id
JOIN SDG ON SDG.id = project_SDG.SDG_id
WHERE SDG.id IN(1,2)
GROUP BY project.id
HAVING COUNT(*) = 2
See my sandbox here: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/0
If you need all project's fields, you have to put this into sub-query as
... WHERE id IN ( subquery here )
Subquery example: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/1
I have already answered here, but I have another approch.
1. Find bunch of IDs assotiated with some project
To find project IDs we can test lonely pivot table without any join:
SELECT project_id FROM project_SDG
WHERE SDG_id IN(1,2,6)
GROUP BY project_id HAVING COUNT(*) = 3
it gives us list of Project IDs
2. Access all project fields and add extra conditions
SELECT project.*
FROM project
JOIN (
SELECT project_id FROM project_SDG
WHERE SDG_id IN(1,2,6)
GROUP BY project_id HAVING COUNT(*) = 3
) AS ids ON ids.project_id = project.id
WHERE
MATCH(project.name) AGAINST ('project') AND
MATCH(project.description) AGAINST ('sit')
you can play with it here: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/3
3. Prepare query on the PHP side
I will use known technique to prepare SQL statement.
$ids = [1, 2, 6]; // it can come from request parameters
$text1 = 'project';
$text2 = 'sit';
// build ?,?,?,... pattern
$qmarks = implode(',', array_fill(0, count($ids), '?'));
// Use SQL query above
$sth = $dbh->prepare("
SELECT project.*
FROM project
JOIN (
SELECT project_id FROM project_SDG
WHERE SDG_id IN({$qmarks})
GROUP BY project_id HAVING COUNT(*) = ?
) AS ids ON ids.project_id = project.id
WHERE
MATCH(project.name) AGAINST (?) AND
MATCH(project.description) AGAINST (?)
");
$sth->execute(array_merge($ids, [count($ids), $text1, $text2]));
$records = $sth->fetchAll();
I can't figure out how to get results from 2 tables, in 1 query result (can't simple JOIN)
I have these 2 tables in my MySQL database:
Table 1: sales
id
name
info
Table 2: users
sale_id
user_id
Now, every sale have different number of assigned users. Some sale have 2 users, some sale have 10 users.
In single row, I need to have columns from sale table, and all assigned users to it (connected with same Sale_id)
I need result, something like this:
enter image description here
Try this :
SELECT s.*,
(SELECT GROUP_CONCAT(u.user_id SEPARATOR ', ')
FROM users u
WHERE u.sale_id = s.id) AS users
FROM sales s
Some insight on your programming language would have been nice.
And yes, as suggested by wogsland and icoder, one typically use joins and loop through results to build en array. But the use of GROUP_CONCAT, as Yoleth pointed out, is what you need. I don’t know if it was the goal here, but it can reduce memory used in the result because there is no row repetition.
SELECT info FROM Sales AS s,
(
SELECT sale_id, GROUP_CONCAT(user_id) AS assigned_users
FROM Users
GROUP BY sale_id) AS u
WHERE s.id=u.sale_id;
In a single query, with a fancy JOIN:
SELECT s.info AS info, u.sale_id AS sale_id, GROUP_CONCAT(u.user_id) AS assigned_users
FROM Sales AS s LEFT JOIN Users AS u
ON s.id=u.sale_id
WHERE sale_id IS NOT NULL GROUP BY u.sale_id;
You can simply join two tables and get query result set like this:
saleID | saleName | userID | userName
1 | Oct Sale | 5 | Tim
1 | Oct Sale | 6 | Nik
2 | Nov Sale | 7 | Bill
Then you can walk each row and build associative array from that data:
$sales = array();
while( $row = mysqli_fetch_assoc($result)) {
if (!array_key_exists($row['saleID'], $sales)) {
$sales[$row['saleID']] = array(
'saleID' => $row['saleID'],
'saleName' => $row['saleName'],
'users' => array()
);
}
array_push($sales[$row['saleID']]['users'], array(
'userID' => $row['userID'],
'userName' => $row['userName']
));
}
Well, MySQL isn't going to return you a nice nested array like that. But you can create it by looping through the result. Assuming your MySQL connection is named $mysqli then try something like
$sales = array();
$result = $mysqli->query("SELECT sales.*, users.user_id FROM sales, users WHERE sales.id = users.sales_id");
while ($row = $result->fetch_assoc()) {
$sales[$row->id]['sales_id'] = $row->id;
$sales[$row->id]['name'] = $row->name;
$sales[$row->id]['info'] = $row->info;
$sales[$row->id]['assigned_users'][] = $row->user_id;
}
I'm having a problem at the moment where I have a column called rating in the links table and there is definitely values other than 0 within the column but 0 is the only value which is returned foreach link. When I do a simple get for that column it then shows all the other values but not when I do an SQL Join.
I know the problem is my joining of the tables but I'm unsure how I would go about joining these specific tables.
Database Table Structure
The rating column is the one which is causing me problems.
'links' id | title | url | user_id | list_id | rating | weight | date_created
'list' id | list_title | list_description | user_id | rating | views | date_created
'link_ratings' id | user_id | link_id | rated | date_created
Model:
public function get_latest(){
$this->db->limit(100);
$this->db->order_by('links.date_created', 'DESC');
$this->db->select('*');
$this->db->select('links.id as current_link_id');
$this->db->from('links');
$this->db->join('list', 'links.list_id = list.id');
$this->db->join('users', 'links.user_id = users.id');
$this->db->join('link_ratings', 'links.id = link_ratings.link_id','left');
$get_latest = $this->db->get();
return $get_latest;
}
Any Help is appreciated.
You should try this:
function get_latest(){
$this->db->select('list.*, users.*, links.id as current_link_id');
$this->db->from('links');
$this->db->join('list', 'links.list_id = list.id');
$this->db->join('users', 'links.user_id = users.id');
$this->db->join('link_ratings', 'links.id = link_ratings.link_id','left');
$this->db->order_by('links.date_created', 'DESC');
$this->db->limit(100);
$get_latest = $this->db->get()->result_array(); #fetch all rows here
echo "<pre>";print_r( $get_latest );die; #print all rows and see if its fetching ratings corrctly or not.
echo $this->db->last_query();die; #check the query generated
return $get_latest;
}
The reason will be purely logical, in that the join will be causing no results to be returned because there are no results. I've fallen into this many times.
I am not able to diagnose your particular problem but when faced with issues like this I:
1- turn on the CI profiler
2- var_dump the array so you can see what's going on
3- write a traditional SQL query and run it in PHPMyAdmin
One, or a combination of all three, will enable you to diagnose.
i'v been trying to find a way to imitate Facebook urls on my website.
Facebook url structure are the same for all object types here are some urls and their type from Facebook:
Person - https://facebook.com/personUsername/
Page - https://facebook.com/page-identifier/
App - https://facebook.com/app-identifier/
after searching their apis i noticed they have type for each object for example a person have a type of "person".
i tried to implement this without having just one table with all my objects and a type column:
i have 5 objects on my website with 5 tables.
tracks, artists, users, albums, clips
here is how i implemented it.
a user goes to https://mysite.com/track-slug/
i take the slug and query the slug on all mysql tables until i find a match. then i render the correct view.
the question: is it possible to imitate Facebook urls without having just one mysql table or doing multiple queries on each request to mysql ?
If your MySQL tables have the same columns, you could do a UNION of these:
SELECT * FROM
(SELECT name, col1, col2 FROM table1
UNION
SELECT name, col1, col2 FROM table2
UNION
SELECT name, col1, col2 FROM table3
) AS t
WHERE t.name = 'abc'
Example result:
+------+------+------+
| name | col1 | col2 |
+------+------+------+
| abc | 1 | 2 |
+------+------+------+
If you in addition need to know the table name for each item, you could add another column in each SELECT statement:
SELECT * FROM
(SELECT name, col1, col2, 'table1' AS 'table' FROM table1
UNION
SELECT name, col1, col2, 'table2' AS 'table' FROM table2
UNION
SELECT name, col1, col2, 'table3' AS 'table' FROM table3
) AS t
WHERE t.name = 'abc'
Example result:
+------+------+------+--------+
| name | col1 | col2 | table |
+------+------+------+--------+
| abc | 1 | 2 | table2 |
+------+------+------+--------+
Even if you have different columns in your tables, you can use this technique for the tables which are the same (e.g., name, plus the table column in ex. 2) and do a second query for the rest of the columns, instead of having to do up to one query per table.
here's a hint
htaccess:
RewriteRule ^(.*)$ index.php/$1 [L]
PHP:
$uri = rawurldecode(trim(parse_url(getenv('REQUEST_URI'), PHP_URL_PATH), '/'));
$params = explode('/', $uri);
$params = explode('_', $params);
$controller = array_shift($params);
$method = array_shift($params);
now go to
domain.com/track_slug
then in your index.php do
php > echo $controller
you should get track
php > echo $method
also, you will get slug
what you do with that information is up to your controller, for example. Have the Track::slug() do your database fetches and queries.
or you can do something a little more to the poing
domain.com/query/track/slug
$uri = rawurldecode(trim(parse_url(getenv('REQUEST_URI'), PHP_URL_PATH), '/'));
$params = explode('/', $uri);
$request = array_shift($params);
$controller = array_shift($params);
$method = array_shift($params);
which then your request would be query, your controller would be track, and your method would be slug.
So now you know that you want to query the track object for the slug in your databases.