How to combine two SQL queries in one query - mysql

I have two queries which are unrelated to each other, first query returns 4 column whereas the second one returns only 1 column.
so how to combine it?
query 1-
$sql = "select postlist.* from postlist order by postlist.id desc ";
query 2-
$sql1 = "select count (commentlist.id) as 'comments',commentlist.id,commentlist.name,commentlist.comment from postlist,commentlist where postlist.id=commentlist.post_id";
current query-
$sql = "select postlist.*, count (commentlist.id) as 'comments' from postlist LEFT JOIN commentlist ON postlist.id=commentlist.post_id order by postlist.id desc ";
Basically, I want to return all records from postlist, whether the commentlist table has any related comments or not.
Here is a database design
drop table if exists postlist;
create table postlist (
id integer not null primary key autoincrement,
post varchar(1000) not null,
name varchar(80) not null,
title varchar(80) not null
);
drop table if exists commentlist;
create table commentlist (
id integer not null primary key autoincrement,
post_id integer not null,
comment varchar(80) not null,
name varchar(80) not null
);

The get() will cast it to a Collection, that is a lot more powerful than an array. You can append it, iterate over it and more.
Have a bash at that. Hopefully it should be what you need: http://laravel.com/docs/4.2/eloquent#collections
$items = DB::select($sql)->get();
$items1 = DB::select($sql1)->get();
$items = items->toArray();
$items1 = items1->toArray();
$items = array_merge($items, $items1);

Related

Get value of mySQL query grouped by id

I want to know an efficient way to solve the following query. Essentially I have the following two classes
CREATE TABLE `example`.`doc` (
`id` INT NOT NULL AUTO_INCREMENT,
`uuid` INT NOT NULL,
`creator` VARCHAR(32) NOT NULL,
PRIMARY KEY (`id`);
CREATE TABLE `example`.`pic` (
`id` INT NOT NULL AUTO_INCREMENT,
`docuuid` INT NOT NULL,
`taken_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`lat` DECIMAL(3,10),
`lon` DECIMAL(3,10),
PRIMARY KEY (`id`);
this two tables are related by uuid (I know this is not the best idea to link two tables, but the table is like this). uuid is unique per doc. With the following query
SELECT
`doc`.`id` AS `docId`,
`doc`.`uuid` AS `uuid`,
`doc`.`creator` AS `creator`,
COUNT(`pic`.`id`) AS `pics`,
MIN(`pic`.`taken_at`) AS `min_date`,
MAX(`pic`.`taken_at`) AS `max_date`
FROM
`doc` INNER JOIN
`pic` ON (`doc`.`uuid` = `pic`.`docuuid`)
WHERE (
`pic`.`docuuid` IS NOT NULL AND
`doc`.`uuid` IS NOT NULL)
GROUP BY `doc`.`uuid`
I get, for each doc, the date at witch the first and last pics was taken. Additionally I want to get in this query, for each doc, the latitude and longitude of the first and last pic taken at that doc.
For example, If I have
doc1 = (id=0, uuid=1)
doc2 = (id=1, uuid=2)
pic1 = (taken_at=2021-01-01, docuuid=1, lat=1, lon=2)
pic2 = (taken_at=2021-01-02, docuuid=1, lat=3, lon=4)
pic3 = (taken_at=2021-01-03, docuuid=2, lat=5, lon=6)
pic4 = (taken_at=2021-01-04, docuuid=2, lat=7, lon=8)
pic5 = (taken_at=2021-01-05, docuuid=2, lat=9, lon=10)
then I want to get for doc1 minLat=1, minLon=2, maxLat=3, maxLon=4 and for doc2 minLat=5, minLon=6, maxLat=9, maxLon=10
You mat continue by joining the results of your current query with the pics table and extracting the desired details of each pic, I've included a sample below:
SELECT
t.*,
earliest_pic.lat as minLat,
earliest_pic.lon as minLon,
latest_pic.lat as maxLat,
latest_pic.lon as maxLon
FROM
(
SELECT
`doc`.`id` AS `docId`,
`doc`.`uuid` AS `uuid`,
`doc`.`creator` AS `creator`,
COUNT(`pic`.`id`) AS `pics`,
MIN(`pic`.`taken_at`) AS `min_date`,
MAX(`pic`.`taken_at`) AS `max_date`
FROM
`doc` INNER JOIN
`pic` ON (`doc`.`uuid` = `pic`.`docuuid`)
GROUP BY `doc`.`uuid`
) AS t
INNER JOIN `pic` as earliest_pic ON earliest_pic.taken_at = t.min_date AND
earliest_pic.docuuid = t.docId
INNER JOIN `pic` as latest_pic ON latest_pic.taken_at = t.max_date AND
latest_pic.docuuid = t.docId
Let me know if this works for you

Is this query optimised? - Mutual follows

Is this query optimised?
I'm trying to get the list of people personA follows who follow personB.
Few thousand of rows right now in the table but growing fast.
Want to make sure the query is performant enough for mysql.
Query:
select
*
from
(
select
*
from
creator_followers cf
where
cf.follower_user_id = 'personA'
and cf.current_active = 1
and cf.current_following = 1
) as fo
join creator_followers cf
where
fo.creator_user_id = cf.follower_user_id
and cf.creator_user_id = 'personB'
and cf.current_following = 1
and cf.current_active = 1
order by
cf.created_at desc
limit
10 offset 0;
Schema:
create table social.creator_followers
(
creator_user_id varchar(16) charset utf8 null,
follower_user_id varchar(16) charset utf8 null,
current_following bit null,
current_active bit null,
created_at bigint null,
id bigint auto_increment
primary key
)
It seems that it must be
SELECT *
FROM creator_followers cf1
join creator_followers cf2 ON cf1.creator_user_id = cf2.follower_user_id
where cf1.follower_user_id = 'personA'
and cf2.creator_user_id = 'personB'
and cf1.current_active = 1
and cf1.current_following = 1
and cf2.current_following = 1
and cf2.current_active = 1
order by cf2.created_at desc
limit 10 offset 0;
Based on a Comment, this might provide another speedup. Replace
id bigint auto_increment
primary key
with
PRIMARY KEY(creator_user_id, follower_user_id)

What's the best way to structure a log table which shows differently in each view?

I need to design the structure of a table that is going to store a log of events/actions for a project management website.
The problem is, these logs will be spelled differently depending on what the user is viewing
Example:
On the overview, an action could say "John F. deleted the item #2881"
On the single-item page, it would say "John F. deleted this item"
If the current user IS John F. it would spell "You deleted this item"
I'm not sure if I should store each different possibility in the table, this doesn't sound like the optimal approach.
For any kind of logs you can use the following table structure
CREATE TABLE logs (
id bigint NOT NULL AUTO_INCREMENT,
auto_id bigint NOT NULL DEFAULT '0',
table_name varchar(100) NOT NULL,
updated_at datetime DEFAULT NULL,
updated_by bigint NOT NULL DEFAULT '0',
updated_by_name varchar(100) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3870 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
And then create another table to record what columns were updated exactly.
CREATE TABLE logs_entries (
id bigint NOT NULL AUTO_INCREMENT,
log_id bigint NOT NULL DEFAULT '0',
field_name varchar(100) NOT NULL,
old_value text,
new_value text,
PRIMARY KEY (id),
KEY log_id (log_id),
CONSTRAINT logs_entries_ibfk_1 FOREIGN KEY (log_id) REFERENCES logs (id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=7212 DEFAULT CHARSET=utf8mb3
Now your table data look like this
Now Create a Database-View to fetch data easily in simple query
DELIMITER $$
ALTER ALGORITHM=UNDEFINED SQL SECURITY DEFINER VIEW view_logs_entries AS
SELECT
le.id AS id,
le.log_id AS log_id,
le.field_name AS field_name,
le.old_value AS old_value,
le.new_value AS new_value,
l.auto_id AS auto_id,
l.table_name AS table_name,
l.updated_at AS updated_at,
l.updated_by AS updated_by,
l.updated_by_name AS updated_by_name
FROM (logs_entries le
LEFT JOIN logs l
ON ((le.log_id = l.id)))$$
DELIMITER ;
After creating database-view your data will look like this, so now you can easily query any logs of your project
You must have noticed that I have added updated_by and updated_by_name columns in logs table, Now there are two ways to fill this updated_by_name column
You can write a query on every log entry to fetch user name and store it(not recommended)
You can make a database-trigger to fill this column (Recommended)
You can create database trigger like this, it will automatically insert user name whenever logs entry added in database.
DELIMITER $$
USE YOUR_DATABASE_NAME$$
CREATE
TRIGGER logs_before_insert BEFORE INSERT ON logs
FOR EACH ROW BEGIN
SET new.updated_by_name= (SELECT fullname FROM users WHERE user_id = new.updated_by);
END;
$$
DELIMITER ;
After doing all this, you can insert entries in logs table whenever you do changes in any database table. In my case I have PHP-CodeIgniter project, and I did it like this in my Model file, here I am updating patient table of my database
public function update($id,$data)
{
// Log this activity
$auto_id = $id;
$table_name = 'patients';
$updated_by = #$data['updated_by'];
$new_record = $data;
$old_record = $this->db->where('id',$auto_id)->get($table_name)->row();
$data['updated_at'] = date('Y-m-d H:i:s');
$this->db->where('id', $id);
$return = $this->db->update($table_name,$data);
//my_var_dump($this->db->last_query());
if($updated_by)
{
$this->log_model->set_log($auto_id,$table_name,$updated_by,$new_record,$old_record);
}
return $return;
}
set_log function code checks which fields are actually changed
public function set_log($auto_id,$table_name,$updated_by,$new_record,$old_record)
{
$entries = [];
foreach ($new_record as $key => $value)
{
if($old_record->$key != $new_record[$key])
{
$entries[$key]['old_value'] = $old_record->$key;
$entries[$key]['new_value'] = $new_record[$key];
}
}
if(count($entries))
{
$data['auto_id'] = $auto_id;
$data['table_name'] = $table_name;
$data['updated_by'] = $updated_by;
$this->insert($data,$entries);
}
}
and insert log function look like this
public function insert($data,$entries)
{
$data['updated_at'] = date('Y-m-d H:i:s');
if($this->db->insert('logs', $data))
{
$id = $this->db->insert_id();
foreach ($entries as $key => $value)
{
$entry['log_id'] = $id;
$entry['field_name'] = $key;
$entry['old_value'] = $value['old_value'];
$entry['new_value'] = $value['new_value'];
$this->db->insert('logs_entries',$entry);
}
return $id;
}
return false;
}
You need to separate data from display. In your log, store the complete information (user xxx deleted item 2881). In your log viewer, you have the luxury of substituting as needed to make it more readable.

SQL Server 2008 Merge Soft Delete Error

I'm trying to perform a soft delete on a row in my target table using the SQL server 2008 MERGE command.
I think this should fall under the "when not matched by source" section, since the source is missing the row and the target still has it. All I want to do is set the IsActive bit to false, but I'm getting an error.
"Attempting to set a non-NULL-able column's value to NULL."
What am I missing?
The Users table is:
[ID] [nvarchar](50) NOT NULL,
[FirstName] [nvarchar](200) NULL,
[LastName] [nvarchar](200) NULL,
[EmailAddress] [nvarchar](200) NULL,
[IsActive] [bit] NOT NULL
The Merge statement is:
merge into Users
using TempUserTable lu
on Users.ID = TempUserTable.ID
when matched then
update set
ID = lu.ID,
FirstName = lu.FirstName,
LastName = lu.LastName,
EMailAddress = lu.EmailAddress,
IsActive = lu.Status
when not matched then
insert (ID, FirstName, LastName, EmailAddress, IsActive)
values (lu.ID, lu.FirstName, lu.LastName, lu.EmailAddress, lu.Status)
when not matched by source then
update set IsActive = 0;
You can get this to work exactly as you want but for me I needed to add a condition in the NOT MATCHED line.
So try something like...
WHEN NOT MATCHED BY SOURCE
AND TARGET.[IsActive] = 1
AND TARGET.[DeletedOn] IS NULL
THEN UPDATE
SET
TARGET.[IsActive] = 0,
TARGET.[DeletedOn] = SYSDATETIMEOFFSET()
It appears that your temp table TempUserTable either has a NULL in the IsActive column or the ID column.

mysql structure for comments and comment replies

I've been thinking about this one for quite some time now, I need a way to add replies to comments in the database but I'm not sure how to proceed.
This is my currently comment table (doesn't say much but its a start):
CREATE TABLE IF NOT EXISTS `comments` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`comment` text,
`user_id` int(12) DEFAULT NULL,
`topic_id` int(12) NOT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `topic_id` (`topic_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=27 ;
and here is my current query:
SELECT c.id, c.comment, c.user_id, u.username, u.photo
FROM (comments c)
JOIN users u ON c.user_id = u.id
WHERE c.topic_id = 9
One option would be to create a new table called "comment_replies" but I'm not sure If I'm able to select all the comments and comment replies in one query, and If I add a new column called "reply" I'm not sure how to sort them to get each comment with each reply.
I would love to get some advice on how to deal with this.
Edit:
Following the below answers about adding parent_comment_id result in this kind of array from 1 comment and 2 replies:
array(2) {
[0]=>
object(stdClass)#17 (7) {
["id"]=>
string(2) "26"
["comment"]=>
string(36) "adding a comment from the admin page"
["user_id"]=>
string(2) "16"
["ts"]=>
string(10) "1249869350"
["username"]=>
string(5) "Admin"
["photo"]=>
string(13) "gravatar2.png"
["reply"]=>
string(23) "There is no admin page!"
}
[1]=>
object(stdClass)#18 (7) {
["id"]=>
string(2) "26"
["comment"]=>
string(36) "adding a comment from the admin page"
["user_id"]=>
string(2) "16"
["ts"]=>
string(10) "1249869350"
["username"]=>
string(5) "Admin"
["photo"]=>
string(13) "gravatar2.png"
["reply"]=>
string(13) "Yes there is!"
}
}
How should I process this array to work with it, Is it possible to separate the comment from the replies?
If you want people to be able to reply to the replies (i.e. have a hierarchy of replies such as you would see in, say, an online message forum), then I would add an optional parent_comment_id field to the comments table.
Your table would look like this
`CREATE TABLE IF NOT EXISTS `comments` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`parent_comment_id` int(12) NULL,
`comment` text,
`user_id` int(12) DEFAULT NULL,
`topic_id` int(12) NOT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `topic_id` (`topic_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=27 ;`
Your query showing all comments and replies would be something like:
SELECT c.id, c.comment, r.comment as reply, c.user_id, u.username, u.photo
FROM (comments c)
JOIN users u ON c.user_id = u.id
LEFT JOIN comments r ON c.id = r.parent_comment_id
WHERE c.topic_id = 9
Note however that with this query your replies would also show up not only in the 'reply' column, but also in the 'comment' column as additional rows each with zero or more replies.
To show the username of the users who replied to a comment, you will need to join twice to the users table (first for the user who posted the original comment, and again for the user(s) who replied). Try this query to show the usernames of the users who replied:
SELECT c.id, c.comment, c.user_id, u.username, u.photo, r.comment as reply, r.user_id as reply_user_id,
u2.username as reply_username, u2.photo as reply_photo
FROM (comment c)
JOIN users u ON c.user_id = u.id
LEFT JOIN comments r ON c.id = r.parent_comment_id
JOIN users u2 ON r.user_id = u2.id
WHERE c.topic_id = 9
Add a parent_comment_id column to your comments table. Make it optional. When you query, you can join all child comments to each parent. As a bit of selective denormalization (slight redundancy) you can make sure topic_id is set on the child comments as well, letting you pull them all a bit easier (assuming you're going to display all child comments in the main comment thread and not via smaller ajax requests).
Build the presentation however you need, toss the results into memcached (or a flat file, or memory... however you're caching) and you're set.
I decided to add the parent_id column in the database and instead of left joining the replies I just selected all the comments at once to later on sort the comments and replies with server-side code, heres the query:
SELECT c.*, u.username, u.photo
FROM (comments c)
JOIN users u ON c.user_id = u.id
WHERE c.topic_id = 9
ORDER BY c.id ASC
Now I pass the query result to the below function so that every reply will be added as an array inside the comment array, so basically it returns a multidimensional array.
function sort_comments($ar)
{
$comments = array();
foreach($ar as $item)
{
if(is_null($item['parent_id'])) $comments[] = $item;
else
{
$parent_array = array_search_key($item['parent_id'],$comments,'id');
if($parent_array !== false) $comments[$parent_array]['replies'][] = $item;
}
}
return $comments;
}
this seems all good but what about if the table contained over a million rows? and some comments could be hundreds of thousands of rows apart. how will these queries perform?
A comment reply is a comment with a parent comment_id. Try adding comment_id as a field to your comments table. What you will get is a tree-like structure.
If you wish to retrieve an entire tree of comments, your best bet is to use a nested set (https://wiki.htc.lan/Hierarchy_model). But that's a more complicated solution.
Here's some more info from MySQL: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
Looks like you are working with WordPress, adding a parent_comment_id would have been an ideal solution, but not in this case.
Firstly, I don't think modifying the WordPress basic tables is a good idea. Secondly, you'll end-up with a complex code that will break with wordpress updates.
Best is use a plugin like Intense Comments
Still if you want to create your own solution, I would say create another table for comment replies.
a) Your new table would look like this
`CREATE TABLE IF NOT EXISTS `comment_replies` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`parent_comment_id` int(12) NULL,
`comment` text,
`user_id` int(12) DEFAULT NULL,
`topic_id` int(12) NOT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `topic_id` (`topic_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
b) You'd fetch them this way
$comment_ids = array_map( 'intval', array_keys( $comments ) );
sort( $comment_ids );
$comments_id_list = join( ',', $comment_ids );
$query = "SELECT c.id, c.comment, c.user_id, u.username, u.photo
FROM (comment_replies c)
JOIN users u ON c.user_id = u.id
WHERE c.parent_comment_id IN ( $comments_id_list )"
$replies = $wpdb->get_results($query);
$replies_by_parent = array();
foreach ( array_keys( $replies ) as $i ) {
$replies_by_parent [$replies[$i]->id] = array();
}
foreach ( array_keys( $replies ) as $i ) {
$replies_by_parent [$replies[$i]->id][] =& $replies[$i];
}
c) Now within your comments loop you can get the replies like this
foreach ( $replies_by_parent [$parent_id] as $reply ) {
echo $reply->comment;
}
function sort_comments($ar)
{
$comments = [];
$i=0;
foreach($ar as $co){
if(!empty($co['comment_replyof'])) {
$comments[$co['comment_replyof']]['replies'] = $co;
}else{
foreach($co as $c => $o) $comments[$co['comment_id']][$c] = $o;
}
$i++;
}
return $comments;
}
SELECT C.*, U.id,U.fname, U.lname FROM (comment C) JOIN users U ON `enter code here`C.comment_user = U.id where C.comment_content='10'