Contitionally structured and ordered MySQL query - php

I have a table in a MySQL Database.
It is structured as such:
CREATE TABLE `wall` (
`wall_id` int(10) NOT NULL auto_increment,
`user_id` int(10) NOT NULL,
`wall_content` varchar(1024) NOT NULL,
`time_posted` varchar(64) NOT NULL,
`is_reply` int(10) NOT NULL,
PRIMARY KEY (`wall_id`)
) ENGINE=MyISAM
The column 'is_reply' will be the id of 'wall_id' to which it is a reply of. How would I structure a query to get all the rows based on an inner join of another table to cross reference the user_id, and to group the wall posts with the comments below it whilst ordering the wall posts by 'time_posted'
My current query does that without grouping the comments. It is:
SELECT wall.*, user_wall.*, users.username, users.avatar_id
FROM `wall`
INNER JOIN user_wall ON user_wall.wall_id = wall.wall_id
INNER JOIN users ON users.user_id = wall.user_id
WHERE user_wall.user_id=15
I hope you can understand this.
Edit:
The table 'user_wall' is a table that stores what values are on the users wall, and the 'wall' table stores what is actually posted. The user_id in the 'wall' table is a reference to who posted that post.
The current query as stated above is fully functional and returns data as such:
wall_id | user_id | wall_content | time_posted | is_reply | user_id | wall_id | username | avatar_id
1 | 1 | *content* | *time* | 0 | 2 | 1 | User1 | 1
2 | 1 | *content2* | *time2* | 0 | 2 | 2 | User1 | 1
3 | 1 | *content3* | *time3* | 1 | 1 | 3 | User1 | 1
Whereas my question is, how do you structure the query so the result is like so:
wall_id | user_id | wall_content | time_posted | is_reply | user_id | wall_id | username | avatar_id
1 | 1 | *content* | *time* | 0 | 2 | 1 | User1 | 1
3 | 1 | *content3* | *time3* | 1 | 1 | 3 | User1 | 1
2 | 1 | *content2* | *time2* | 0 | 2 | 2 | User1 | 1
Where the row with 'wall_id' 3 which has and 'is_reply' of 1 to be beneath the row with 'wall_id'. Similarly a row with an 'is_reply' of 2 will be under the row with the row with a 'wall_id' of 2.

Now that you've edited it I understand what you mean. This should do it:
ORDER BY IF(wall.is_reply, wall.is_reply, wall.wall_id), wall.wall_id
Format: IF(EXPRESSION, IF_TRUE, IF_FALSE)

SQL can't return multiple rows from one table (e.g. the wall_comments) and only one from the ones it is joined with. In other words, that can't be done. There is an alternative that will get the same results but use two SQL queries and some PHP code.
Query #1:
SELECT wall_comments.*
FROM `wall_comments`
INNER JOIN user_wall ON wall_comments.wall_id = user_wall.wall_id
WHERE user_wall.user_id=15
Query #2:
SELECT wall.*, user_wall.*, users.username, users.avatar_id
FROM `wall`
INNER JOIN user_wall ON user_wall.wall_id = wall.wall_id
INNER JOIN users ON users.user_id = wall.user_id
WHERE user_wall.user_id=15
PHP:
<?php
$result1 = mysql_query($query1);
$result2 = mysql_query($query2);
$comments = array();
while($row = mysql_fetch_assoc($result1))
{
$comments[$row['wall_id']][] = $row;
}
$walls = array();
while($row = mysql_fetch_assoc($result2))
{
$walls[] = array_merge(
$row,
array(
'comments' => isset($comments[$row['wall_id']]) ? $comments[$row['wall_id']] : array(),
),
);
}
?>

Related

How to display name instead of id if records are in one table?

I am using CodeIgniter. I have an employee table and records are
id |firstname | lastname | mobileno | created_by
2 |mnb | nbgfv | 1452145625 | 1
3 |jhg | uhgf | 1452365478 | 2
4 |poi | ijuy | 1458745632 | 2
5 |tgf | tgfd | 1458745254 | 2
6 |wer | qwes | 1523654512 | 2
Now My issue is in the column created_by. When I am displaying the record of any id value then I am getting the output like
id |firstname | lastname | mobileno | created_by
3 |jhg | uhgf | 1452365478 | 2
But my expected output
id |firstname | lastname | mobileno | created_by
3 |jhg | uhgf | 1452365478 | mnb nbgfv
I have to display the name of created_by
I tried only this query.
$get_single_emp_record = array('id' => 3);
$this->db->where($get_single_emp_record);
$query = $this->db->get('tbl_employee');
$result = $query->row();
if($result)
{
return $result;
}
else
{
return 0;
}
I have a hint (maybe this can give solution in your problem).
Try query like this :
SELECT
t1.id , t1.firstname , t1.lastname ,t1.mobileno,
CONCAT(t2.firstname ," ",t2.lastname ) AS createby
FROM tbl_employee AS t1
JOIN tbl_employee AS t2 ON t2.id = t1.created_by
WHERE t1.id = '3'
With above query you no need create temporary table or other additional tables.
I Tested it. >>>
http://sqlfiddle.com/#!9/e693cf/2/0
Thanks
you will have to create another table maybe tbl_creator which will have the id, creator_name then in your query you will perform a Join operation

is it safe to run a another query in side a mysql loop

I have a left join query to get posts liked by a users. if 2nd logged in user visit the 1st user profile will show the likes by 1st user and also will show a text on the post if 2nd user (logged in user) like the same post
user table
user_id | username
likes table
like_id | post_id | uid
MySQL
$SQL = "SELECT * FROM likes LEFT JOIN users ON users.user_id = likes.uid WHERE likes.uid = 'user1'"
If i run another query inside the while loop of above query it will work
$check_id = row['post_id']; //get post id from 1st loop
if(isset($_SESSION['userid'])){
$check = "SELECT * FROM likes WHERE post_id='$check_id' AND uid='LOGED-IN-USER-ID'"
}
Then i can get the num_rows and add text. This work perfectly fine but i like to know is there a better way to do this without running so many queries inside the while loop. Is there a way to combine the queries or do the 2nd query outside of the loop.
That's "safe" from "data consistency point of view", but querying in a while after a query is called a "1+N" and is typically a performance killer, you may easily find documentation about SQL 1+N problem.
The solution is to let the SQL server do the job for you in a single query, avoiding playing ping pong with it (read: TCP packets back-and-forth, query parsing, ...).
Given:
> SELECT * FROM user;
+---------+----------+
| user_id | username |
+---------+----------+
| 1 | root |
| 2 | user2 |
| 3 | user3 |
+---------+----------+
> SELECT * FROM `like`;
+---------+---------+---------+
| like_id | post_id | user_id |
+---------+---------+---------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 3 | 1 |
| 4 | 4 | 1 |
| 5 | 2 | 2 |
+---------+---------+---------+
> SELECT * FROM `post`;
+---------+--------+
| post_id | text |
+---------+--------+
| 1 | post 1 |
| 2 | post 2 |
| 3 | post 3 |
| 4 | post 4 |
+---------+--------+
There's multiple way to request what you want, but one way may be:
> SELECT like_id, like.post_id, text,
(SELECT 1 FROM `like`
WHERE post_id = post.post_id AND
user_id = 2 /* logged in user */) AS I_like_it_too
FROM `like`
JOIN post USING (post_id)
WHERE user_id = 1 /* user id of seen profile */;
+---------+---------+--------+---------------+
| like_id | post_id | text | I_like_it_too |
+---------+---------+--------+---------------+
| 1 | 1 | post 1 | NULL |
| 2 | 2 | post 2 | 1 |
| 3 | 3 | post 3 | NULL |
| 4 | 4 | post 4 | NULL |
+---------+---------+--------+---------------+
The use the I_like_it_too alias to display post differently as needed.
From a performance point of view you'll need an index on like.user_id to restrict the selected rows on a little subset, the dependent subquery will only be ran for this subset, so that's OK.
Another possibility may be:
> SELECT displayed.like_id, displayed.post_id, text, my_likes.like_id is not null AS i_also_like
FROM `like` AS displayed
JOIN post USING (post_id)
LEFT JOIN `like` AS my_likes ON
displayed.post_id = my_likes.post_id AND
my_likes.user_id = 2 /* logged user */
WHERE displayed.user_id = 1 /* user id of seen profile */;
+---------+---------+--------+-------------+
| like_id | post_id | text | i_also_like |
+---------+---------+--------+-------------+
| 1 | 1 | post 1 | 0 |
| 2 | 2 | post 2 | 1 |
| 3 | 3 | post 3 | 0 |
| 4 | 4 | post 4 | 0 |
+---------+---------+--------+-------------+
Do u mean like this ?
Table SO_LIKES ( ur "Like" Table )
like_id | post_id | uid
1 | 1 | 1
2 | 2 | 1
3 | 1 | 2
Table SO_USERS ( ur "Users" Table )
user_id | username
1 | User1
2 | User2
SQL
SELECT * FROM SO_LIKES as t1 LEFT JOIN SO_USERS as t2 ON t1.uid = t2.user_id INNER JOIN SO_LIKES as t3 ON t1.post_id = t3.post_id WHERE t2.user_id = 1 AND t3.uid = 2
SO Simply call the Same Table in ur query again and use the ID of user 2 there
WHERE t2.user_id = 1 AND t3.uid = 2
Output Looks then like this
like_id | post_id | uid | user_id | username | like_id | post_id | uid
1 | 1 | 1 | 1 | User1 | 3 | 1 | 2
SO u get the POST_id 1 That both Users has Liked

for each value of one table get and display count number of coresponding values from another table

unfortunately i have to do this in mysql / php . I looked for three days, and there is like 10.000 explantions of this but NONE (and I repeat NONE) works for me. I tried it all. I have to ask, sorry.
I have two tables - articles and control.
table "articles"
------------------
art_id | name |
------------------
1 | aaa |
2 | bbb |
3 | ccc |
4 | ddd |
table "control"
--------------------------------------------
con_id | art_id | data |
--------------------------------------------
1 | 1 | something-a |
2 | 2 | something-b |
3 | 1 | something-a |
4 | 2 | something-c |
5 | 3 | something-f |
art_id exists in both tables. Now what i wanted - for query:
"select * from articles order by art_id ASC" displayed in a table
to have also one cell displaying the count for each of art_id's from table CONTROL...
and so i tried join, left join, inner join - i get errors ... I also tried for each get only one result (for example 2 for everything)... this is semi-right but it displays the array of correct results and it's not even with join!!! :
$query = "SELECT art_id, count(*) as counting
FROM control GROUP BY art_id ORDER BY con_id ASC";
$result = mysql_query($query);
while($row=mysql_fetch_array($result)) {
echo $row['counting'];
}
this displays 221 -
-------------------------------------------------
art_id | name | count (this one from control) |
-------------------------------------------------
1 | aaa | 221 |
2 | bbb | 221 |
3 | ccc | 221 |
and it should be:
for art_id(value1)=2,
for art_id(2)=2,
for art_id(3)=1
it should be simple - like a count of values from CONTROL table displayed in query regarding the "articles" table...
The result query on page for table articles should be:
"select * from articles order by art_id ASC"
-------------------------------------------------
art_id | name | count (this one from control) |
-------------------------------------------------
1 | aaa | 2 |
2 | bbb | 2 |
3 | ccc | 1 |
So maybe i should go with JOIN or with join plus for each... Tried tha too, but then i'm not sure what is the proper thing to echo... all-in-all i'm completely lost here. Please help. Thank you.
So imagine this in two steps:
Get the counts per art_id from the control table
Using your articles table, pick up the counts from step 1
That will give you a query that looks like this:
SELECT a.art_id, a.name, b.control_count
FROM articles a
INNER JOIN
(
SELECT art_id, COUNT(*) AS control_count
FROM control
GROUP BY art_id
) b
ON a.art_id = b.art_id;
Which will give you the results you're looking for.
However, instead of using a subquery, you can do it all in one shot:
SELECT a.art_id, a.name, COUNT(b.art_id) AS control_count
FROM articles a
INNER JOIN control b
ON a.art_id = b.art_id
GROUP BY a.art_id, a.name;
SQL Fiddle demo
SELECT *, (SELECT COUNT(control.con_id) FROM control WHERE control.art_id = articles.art_id) AS count_from_con FROM articles ORDER BY art_id DESC;
If I understood your question right, this query should do the trick.
Edit: Created the tables you have described, and it works.
SELECT * FROM articles;
+--------+------+
| art_id | name |
+--------+------+
| 1 | aaa |
| 2 | bbb |
| 3 | ccc |
| 4 | ddd |
+--------+------+
4 rows in set (0.00 sec)
SELECT * FROM control;
+--------+--------+------+
| con_id | art_id | data |
+--------+--------+------+
| 1 | 1 | NULL |
| 2 | 2 | NULL |
| 3 | 1 | NULL |
| 4 | 2 | NULL |
| 5 | 3 | NULL |
+--------+--------+------+
5 rows in set (0.00 sec)
SELECT *, (SELECT COUNT(control.con_id) FROM control WHERE control.art_id = articles.art_id) AS count_from_con FROM articles ORDER BY art_id ASC;
+--------+------+----------------+
| art_id | name | count_from_con |
+--------+------+----------------+
| 1 | aaa | 2 |
| 2 | bbb | 2 |
| 3 | ccc | 1 |
| 4 | ddd | 0 |
+--------+------+----------------+
You haven't quite explained what you want to accomplish with the print out but here is an example in PHP: (Use PDO instead of mysql_)
$pdo = new PDO(); // Make your connection here
$stm = $pdo->query('SELECT *, (SELECT COUNT(control.con_id) FROM control WHERE control.art_id = articles.art_id) AS count_from_con FROM articles ORDER BY art_id ASC');
while( $row = $stm->fetch(PDO::FETCH_ASSOC) )
{
echo "Article with id: ".$row['art_id']. " has " .$row['count_from_con'].' connected rows in control.';
}
Alternatively with the mysql_ extension:
$result = mysql_query('SELECT *, (SELECT COUNT(control.con_id) FROM control WHERE control.art_id = articles.art_id) AS count_from_con FROM articles ORDER BY art_id ASC');
while( $row = mysql_fetch_assoc($result) )
{
echo "Article with id: ".$row['art_id']. " has " .$row['count_from_con'].' connected rows in control.';
}
This should be enough examples to help you accomplish what you need.

Duplicate entries in dropdown from mySQL

Okay so I'm new to mySQL. I'm sorry this is a very novice question. Essentially I have two tables, Associates, and keys.
The content is as follows:
associates:
id,
department,
associate,
date_added
keys:
id,
key_name,
date_added,
my code to make my dropdown is as follows:
<?php
mysql_connect('hostname', 'user', 'Password');
mysql_select_db('log');
$key_fetch = "SELECT `associates`.`department`,`associates`.`associate`,`keys`.`key_name` FROM associates , `keys` ORDER BY `key_name` DESC";
$results = mysql_query($key_fetch);
echo "<select name='key_name' size='5'>";
while ($row = mysql_fetch_array($results)) {
echo "<option value='" . $row['key_name'] . "'>" . $row['key_name'] . "</option>";
}
echo "</select>";
?>
The problem is I only have 5 keys and I have ten associates, and this creates duplicates in my dropdown and I can't fix it with SELECT DISTINCT, and I'm not too sure what else to try.
To visualize cartesian product based on above Q comments.
create table t1
( id int auto_increment primary key,
stuff1 varchar(50) not null
);
insert t1 (stuff1) values ('111.1'),('111.2'),('111.3');
create table t2
( id int auto_increment primary key,
stuff2 varchar(50) not null
);
insert t2 (stuff2) values ('222.1'),('222.2'),('222.3');
A: an explicit Join
select t1.id,t1.stuff1,t2.stuff2
from t1
join t2
on t2.id=t1.id;
+----+--------+--------+
| id | stuff1 | stuff2 |
+----+--------+--------+
| 1 | 111.1 | 222.1 |
| 2 | 111.2 | 222.2 |
| 3 | 111.3 | 222.3 |
+----+--------+--------+
B: An old-style cartesian product
select t1.id,t1.stuff1,t2.stuff2
from t1,t2;
+----+--------+--------+
| id | stuff1 | stuff2 |
+----+--------+--------+
| 1 | 111.1 | 222.1 |
| 2 | 111.2 | 222.1 |
| 3 | 111.3 | 222.1 |
| 1 | 111.1 | 222.2 |
| 2 | 111.2 | 222.2 |
| 3 | 111.3 | 222.2 |
| 1 | 111.1 | 222.3 |
| 2 | 111.2 | 222.3 |
| 3 | 111.3 | 222.3 |
+----+--------+--------+
9 rows in set (0.00 sec)
C: Cross join, same output as B:
select t1.id,t1.stuff1,t2.stuff2
from t1 cross join t2
So, your output, as I see it, is like B, 50 rows.

SQL Removing duplicates one row at a time

I have a table where I save all row-changes that have ever occurred. The problem is that in the beginning of the application there was a bug that made a bunch of copies of every row.
The table looks something like this:
copies
|ID |CID |DATA
| 1 | 1 | DA
| 2 | 2 | DO
| 2 | 3 | DO (copy of CID 2)
| 1 | 4 | DA (copy of CID 1)
| 2 | 5 | DA
| 1 | 6 | DA (copy of CID 1)
| 2 | 7 | DO
CID is UNIQUE in table copies.
What I want is to remove all the duplicates of DATA GROUP BY ID that is after one another sorted by CID.
As you can see in the table, CID 2 and 3 are the same and they are after one another. I would want to remove CID 3. The same with CID 4 and CID 6; they have no ID 1 between them and are copies of CID 1.
After duplicates removal, I would like the table to look like this:
copies
|ID |CID |DATA
| 1 | 1 | DA
| 2 | 2 | DO
| 2 | 5 | DA
| 2 | 7 | DO
Any suggestions? :)
I think my question was badly asked because the answer everybody seems to think is the best gives this result:
ID | DATA | DATA | DATA | DATA | DATA | DATA | CID |
|Expected | Quassnoi |
1809 | 1 | 0 | 1 | 0 | 0 | NULL | 252227 | 252227 |
1809 | 1 | 0 | 1 | 1 | 0 | NULL | 381530 | 381530 |
1809 | 1 | 0 | 1 | 0 | 0 | NULL | 438158 | (missing) |
1809 | 1 | 0 | 1 | 0 | 1535 | 20090113 | 581418 | 581418 |
1809 | 1 | 1 | 1 | 0 | 1535 | 20090113 | 581421 | 581421 |
CID 252227 AND CID 438158 are duplicates but because CID 381530 comes between them; I want to keep this one. It's only duplicates that are directly after one another when ordering by CID and ID.
DELETE c.*
FROM copies c
JOIN (
SELECT id, data, MIN(copies) AS minc
FROM copies
GROUP BY
id, data
) q
ON c.id = q.id
AND c.data = q.data
AND c.cid <> q.minc
Update:
DELETE c.*
FROM (
SELECT cid
FROM (
SELECT cid,
COALESCE(data1 = #data1 AND data2 = #data2, FALSE) AS dup,
#data1 := data1,
#data2 := data2
FROM (
SELECT #data1 := NULL,
#data2 := NULL
) vars, copies ci
ORDER BY
id, cid
) qi
WHERE dup
) q
JOIN copies c
ON c.cid = q.cid
This solution empoys MySQL session variables.
There is a pure ANSI solution that would use NOT EXISTS, however, it would be slow due to the way MySQL optimizer works (it won't use range access method in a correlated subquery).
See this article in my blog for performance details for quite a close task:
MySQL: difference between sets
You can use a count in a subquery for this:
delete from copies
where
(select count(*) from copies s where s.id = copies.id
and s.data = copies.data
and s.cid > copies.cid) > 0
// EDITED for #Jonathan Leffler comment
//$sql = "SELECT ID,CID,DATA FROM copies ORDER BY CID, ID";
$sql = "SELECT ID,CID,DATA FROM copies ORDER BY ID, CID";
$result = mysql_query($sql, $link);
$data = "";
$id = "";
while ($row = mysql_fetch_row($result)){
if (($row[0]!=$id) && ($row[2]!=$data) && ($id!="")){
$sql2 = "DELETE FROM copies WHERE CID=".$row[1];
$res = mysql_query($sql2, $link);
}
$id=$row[0];
$data=$row[2];
}
delete from copies c where c.cid in (select max(cid) as max_cid, count(*) as num from copies where num > 1 group by id, data)

Categories