mysql selecting two columns from two tables in one statement - php

I have been looking if an this has been answered before in SO, but I could not find any.
I have basically two tables in one database. Table member_privileges1 keeps users reputation, and table 2 keeps user details. They look roughly like this.
mysql> describe member_privileges;
+---------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | YES | | NULL | |
| can_comment | int(11) | NO | | NULL | |
| can_create_articles | int(11) | NO | | NULL | |
| can_edit_articles | int(11) | YES | | NULL | |
| can_edit_timeline | int(11) | NO | | NULL | |
| can_remove_comments | int(11) | NO | | NULL | |
| can_upload_profile_images | int(11) | NO | | NULL | |
| can_upload_article_images | int(11) | NO | | NULL | |
| can_approve_new_articles | int(11) | NO | | NULL | |
| can_freez_articles | int(11) | NO | | NULL | |
+---------------------------+--------------+------+-----+---------+----------------+
and this is the users table.
mysql> describe users;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| country | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | | NULL | |
| link | varchar(255) | NO | | NULL | |
| reg_date | date | YES | | NULL | |
| ip | varchar(255) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| about | varchar(255) | YES | | NULL | |
| is_activated | int(11) | YES | | NULL | |
| activation_key | varchar(255) | YES | | NULL | |
| image_path | varchar(255) | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
So, in one page for example, I am trying to check if user 'john' has a can_edit_articles permission set. So, if in member_privileges a username "john" has a can_edit_articles set to 1, then that user can edit.
The problem is checking those details at one. Unlike running two queries like I am doing it now.
$email = $_SESSION['email'];
$get_user = "SELECT id, username FROM members WHERE email = $email;
$can_user_edit = "SELECT can_edit_articles FROM member_privileges WHERE $email = ?;
So, as you can see I am running two queries, and I would like to know if there is any way to run those within one mysql statement.
...

Use an inner join and prevent your sql statements from sql injection
If you would like to select the id, username and can_edit_articles values:
"SELECT m.id, m.username, p.can_edit_articles
FROM members m inner join member_privileges p
ON m.username=p.username WHERE m.email = '$email'"

$email = $_SESSION['email'];
$can_user_edit = "SELECT can_edit_articles FROM member_privileges
WHERE username = (SELECT username FROM users WHERE email = '$email')";

You should use JOIN in your query. Like this:
SELECT can_edit_articles FROM member_privileges
P LEFT JOIN members M ON P.username = M.username WHERE email = '$email'

Related

Eloquent IF clause - always returns true

I am using Entrust's default table structure:
permissions table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| display_name | varchar(255) | YES | | NULL | |
| description | varchar(255) | YES | | NULL | |
| created_at | timestamp | NO | | NULL | |
| updated_at | timestamp | NO | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
permission_role table:
+---------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+-------+
| permission_id | int(10) unsigned | NO | PRI | NULL | |
| role_id | int(10) unsigned | NO | PRI | NULL | |
+---------------+------------------+------+-----+---------+-------+
roles table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| display_name | varchar(255) | YES | | NULL | |
| description | varchar(255) | YES | | NULL | |
| created_at | timestamp | NO | | NULL | |
| updated_at | timestamp | NO | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
Now, given a role_id I'd like to get select the following from this database:
permissions.id
permissions.display_name
whether the permission_role table contains an entry with the permission_id and the given role_id
The last one turned out to be a bit tricky in Eloquent.
This SQL query accomplishes exactly what I need (ID is obviously replaced by a valid role ID):
SELECT p.id, p.display_name, IF(pr.role_id = ID, 1, 0) AS has_role
FROM permissions p
LEFT OUTER JOIN permission_role pr ON p.id = pr.permission_id;
Example output:
+----+--------------+----------+
| id | display_name | has_role |
+----+--------------+----------+
| 1 | Edit users | 1 |
| 2 | View users | 0 |
| 3 | Delete users | 0 |
+----+--------------+----------+
Can anyone help me out here, on how to do this using Eloquent?
I've tried this, but it always returns 1 (true) in the third column, unlike the SQL query (as seen above).
$result = DB::table('permissions')
->leftJoin('permission_role', 'permission_role.permission_id', '=', 'permission_role.role_id')
->select(DB::raw('permissions.id, permissions.display_name, IF(permission_role.role_id = ID, 1, 0) AS has_role'))
->get();
Ideally, I'd like to do this without using DB::raw, although it is completely fine if that is what it takes.
Thanks in advance for any help!
Structurally, the Query Builder query you've shown looks fine.
What does not look fine is the left join. Shouldn't this:
->leftJoin('permission_role', 'permission_role.permission_id', '=', 'permission_role.role_id')
be this:
->leftJoin('permission_role', 'permission_role.permission_id', '=', 'permissions.id')
?

Query with 3 tables and a MAX

I have these following tables:
Table "Questoes";
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| id_quest | int(11) | NO | | NULL | |
| questao | varchar(255) | NO | | NULL | |
| nivel | int(11) | NO | | NULL | |
| tipo | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Table "Resultados";
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| id_user | int(11) | NO | MUL | NULL | |
| nivel | int(11) | NO | | NULL | |
| pontuacao | int(24) | NO | | NULL | |
| data | date | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
And table "utilizador";
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id_user | int(11) | NO | PRI | NULL | auto_increment |
| id_tipo | int(11) | NO | | 1 | |
| username | varchar(50) | NO | UNI | NULL | |
| password | varchar(20) | NO | | NULL | |
| nome | varchar(50) | NO | | NULL | |
| email | varchar(100) | NO | | NULL | |
| data_nasc | text | NO | | NULL | |
| e_valido | smallint(6) | NO | | 0 | |
+-----------+--------------+------+-----+---------+----------------+
What i need is the maximum of score (field "pontuacao") of each level (field "nivel") in the table "resultados" to the best player.
To get the players name, will be through the field "id_user" in table "resultados".
I have this, but doesn'st work:
SELECT u.id_user, MAX(u.pontuacao), u.nivel, u.data, l.nivel, r.id_user, r.username
FROM questoes l, resultados u, utilizador r
WHERE u.nivel = l.nivel AND r.id_tipo=1 AND u.id_user=r.id_user
GROUP BY u.nivel
SELECT a.id_user, r.username, b.pontuacao, b.nivel
FROM resultados a
INNER JOIN utilizador r on a.id_user=r.id_user
INNER JOIN (SELECT u.nivel, MAX(u.pontuacao) pontuacao
FROM resultados u
GROUP BY u.nivel) b ON a.nivel = b.nivel AND a.pontuacao = b.pontuacao
It doesn't work because you are relying on an extension to MySQL that doesn't do what you expect. When using group by, just be sure all the columns in the select are either aggregated or in the group by.
What you want is more like:
SELECT u.id_user, r2.pontuacao, u.nivel, u.data, l.nivel, r.id_user, r.username
FROM questoes l join
resultados u
u.nivel = l.nivel join
utilizador r
on u.id_user = r.id_user join
(select r2.nivel, max(r2.pontuacao) as maxp
from resultados r2
group by r2.nivel
) r2
on u.nivel = r2.nivel and u.pontuacao = r2.maxp
WHER r.id_tipo = 1
GROUP BY u.nivel;
This calculates the maximum value as a subquery and then joins it back in. You may not need the group by after this. Also, I fixed the joins so they use proper ANSI syntax.

Doing a proper Join with SQL

Suppose I have two tables, one with list of spells and another with a grimory, the list of spells that a user has selected for learn or already learned.
mysql> SHOW COLUMNS FROM Grimory;
+--------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| personage_id | int(11) | YES | MUL | NULL | |
| spell_id | int(11) | YES | MUL | NULL | |
| isLearned | tinyint(1) | NO | | NULL | |
| isSelected | tinyint(1) | NO | | NULL | |
+--------------+------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> SHOW COLUMNS FROM Spell;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| description | longtext | NO | | NULL | |
| chatDescription | longtext | NO | | NULL | |
| level | int(11) | NO | | NULL | |
| isActive | tinyint(1) | NO | | NULL | |
| category_id | int(11) | YES | MUL | NULL | |
| createdAt | datetime | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
The problem.
I want to display a list with ALL spells by category_id, BUT for every row I want to show if that spell is learned or selected by current user (personage_id).
Can you help me to write a proper query?
How this works:
With php I generate: List of spells | checkbox isSelected | checkBox isStudied
When I click on isSelected checkbox, a record in grimory will be added with current user and spell.
Updated
SELECT a.*, IFNULL(b.isLearned,0) as isLearned,
IFNULL(b.isSelected,0) as isSelected
FROM Spell a
LEFT JOIN Grimory b(ON b.spell_id =a.id
AND b.personage_id =:current_user_id)
WHERE a.category_id = :current_category_id
SELECT * FROM Spell
INNER JOIN Grimory
ON Spell.id = Grimory.spell_id
WHERE (Grimory.isLearned = 1 OR Grimory.isSelected = 1)
AND Spell.category_id = 'YOUR CAT ID IS HERE'

MySQL Query - Select all posts and count comments for each one

It's 3:30 AM in my country so I need to sleep but I can't without this:
I'm trying to get all posts (using Zend_Db) and count comments for each one.
Schema
blog_posts:
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| content | text | NO | | NULL | |
| alias | varchar(100) | NO | | NULL | |
| user_id | int(11) | NO | | NULL | |
| created_date | datetime | NO | | NULL | |
| modified_date | datetime | YES | | NULL | |
| thumbnail | varchar(255) | YES | | NULL | |
+---------------+------------------+------+-----+---------+----------------+
And here's blog_comments:
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| post_id | int(11) | NO | | NULL | |
| comment | text | NO | | NULL | |
| created_date | datetime | NO | | NULL | |
| modified_date | datetime | YES | | NULL | |
+---------------+------------------+------+-----+---------+----------------+
Note: the blog_comments.post_id is linked with blog_posts.id.
I would like a resulting table like that:
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| content | text | NO | | NULL | |
| alias | varchar(100) | NO | | NULL | |
| user_id | int(11) | NO | | NULL | |
| created_date | datetime | NO | | NULL | |
| modified_date | datetime | YES | | NULL | |
| thumbnail | varchar(255) | YES | | NULL | |
+---------------+------------------+------+-----+---------+----------------+
| TEMPOROARY COLUMN IN OBJECT ($post->comment) |
+---------------+------------------+------+-----+---------+----------------+
| comments | | | | | |
+---------------+------------------+------+-----+---------+----------------+
Now, here's the query I have for now:
SELECT `p`.*, `c`.*
FROM `blog_posts` `p`
LEFT JOIN (
SELECT COUNT(*)
FROM `blog_comments` `c`
WHERE c.post_id = p.id
) ON `p`.`comments`;
But it give me an error:
Error Code: 1248. Every derived table must have its own alias
So if someone can help me, it would be very appreciated!
IMPORTANT NOTE
I'm using Zend_Db and Zend_Db_Select so I must be able to use the functions like joinLeft() or anything I need.
This is in my model for the select():
$select = $this->table->select();
if ($alias) {
$select->where('alias = ?', $alias);
return $this->table->fetchRow($select);
}
if ($withComments) {
// I WILL PLACE THE CODE HERE, EXEMPLE:
$select->joinLeft(...);
}
SELECT p.*, x.*
FROM blog_posts p
LEFT JOIN
(
SELECT post_id, COUNT(*) as cc
FROM blog_comments
GROUP BY post_id
) x
ON x.post_id = p.id;

Force Specific Record to Top When Performing GROUP BY

I have the following MySQL query and tables from which I am querying:
SELECT
`Song`.`id`,
`Song`.`file_name`,
`User`.`first_name`,
`Vote`.`value`,
Sum(`Vote`.`value`) AS score
FROM `songs` AS `Song`
LEFT JOIN votes AS `Vote` ON (`Song`.`id`=`Vote`.`song_id`)
LEFT JOIN `users` AS `User` ON (`Song`.`user_id` = `User`.`id`)
GROUP BY `Vote`.`song_id`
LIMIT 20;
mysql> describe songs;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| file_name | varchar(255) | NO | | NULL | |
| user_id | int(11) | NO | | NULL | |
| created | datetime | NO | | NULL | |
| modified | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> describe users;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
| is_admin | tinyint(1) | NO | | 0 | |
| created | datetime | NO | | NULL | |
| modified | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
mysql> describe votes;
+----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| value | int(11) | NO | | NULL | |
| song_id | int(11) | NO | | NULL | |
| user_id | int(11) | NO | | NULL | |
| created | datetime | NO | | NULL | |
| modified | datetime | NO | | NULL | |
+----------+----------+------+-----+---------+----------------+
This query functions just like I want except for one thing. The value returned in the field Vote.value is not from a row that is associated with the user who is logged into the application. I need the score value to be a sum of all the values no matter which user it is associated with, but the Vote.value field should belong to the logged in user (each user only gets one vote record per song).
My first thought is to somehow sort the table so that when the group by happens the vote record for the logged in user is at the top but I have no idea how to do a sort that forces an arbitrary value to the top. Any ideas would be very helpful.
and a third join
LEFT JOIN votes AS `VotePerUser` ON (`Song`.`id`=`Vote`.`song_id`
AND `Song`.`user_id`=`votes`.`user_id`)
and replace the Vote.value with VotePerUser.Value

Categories