ORDER BY clause constrained by a sub-selection in another table - mysql

I have kind of a edge-case while working on a WordPress project. I am using Advanced Custom Fields to store metadata about posts, which is stored in a "postmeta" table (whereas posts are stored in a "post" table; here prefixed by "otca_").
The posts here are events, which feature a mandatory evt_date and may feature a evt_date_fin ("fin" stands for "end" in French, sorry about the lame naming convention).
My goal is to select a range of events, keeping only those featuring a evt_date set in the future OR a evt_date_fin set in the future (in the latter case, no matter the evt_date), while ordering by evt_date, so as to display them in a paginated, sorted way.
This is what I came up with:
$today = date('Ymd');
$perPage = 12;
$offset = $perPage * ($paged-1); // $paged equals 1, 2, … n (the current page)
$querystr = "
SELECT SQL_CALC_FOUND_ROWS * FROM otca_posts AS post
INNER JOIN otca_postmeta AS meta ON (post.ID = meta.post_id)
WHERE post.post_type = 'agenda'
AND post.post_status = 'publish'
AND (
(meta.meta_key = 'evt_date' AND CAST(meta.meta_value AS CHAR) >= '". $today ."')
OR (meta.meta_key = 'evt_date_fin' AND CAST(meta.meta_value AS CHAR) >= '". $today ."')
)
ORDER BY (meta.meta_key = 'evt_date' AND CAST(meta.meta_value AS CHAR)) ASC
LIMIT ". $perPage ." OFFSET ". $offset;
$evts = $wpdb->get_results($querystr);
$total = $wpdb->get_var('SELECT FOUND_ROWS()');
// then looping over the $evts and using $perPage / $total to build the pagination links
So, for a given post stored in otca_post, there are several records in the otca_postmeta table referencing this post (using post_id), which differ by their meta_key / meta_value pairs (one pair for evt_date, another for evt_date_fin). I use them to filter out posts in the query, which works fine.
The ORDER BY clause does not actually works though.
I would like to know how I could make it so that the posts are ordered by evt_date, a piece of information stored in otca_postmeta; evt_date is not a row, but rather a value stored in the row "meta_key", which means I need to perform a sub-selection somehow in order to… order.
Thank you.

I do this fairly often in my WordPress plugins - you need to join the posts table to the postmeta table for each of the meta_key's that you care about, in this case "evt_date" and "evt_date_fin". Once you have joined them you can use them in the WHERE clause to compare to DATE(CURRENT_TIMESTAMP). Using the MySql function STR_TO_DATE() will convert the strings stored in the meta_value to a DATE datatype - this example assumes you use a Y-m-d format.
$perPage = 12;
$offset = $perPage * ($paged-1);
// create the SQL query by joining posts to postmeta
$querystr = <<<SQL
SELECT SQL_CALC_FOUND_ROWS
p.*,
STR_TO_DATE(s.meta_value, '%Y-%m-%d') AS start_date,
STR_TO_DATE(e.meta_value, '%Y-%m-%d') AS end_date
FROM otca_posts AS p
-- join postmeta for the start
JOIN otca_postmeta AS s ON p.ID = s.post_id AND s.meta_key = 'evt_date'
-- join postmeta for the end
JOIN otca_postmeta AS e ON p.ID = e.post_id AND e.meta_key = 'evt_date_fin'
WHERE
-- is the start greater than or equal to today?
STR_TO_DATE(s.meta_value, '%Y-%m-%d') >= DATE(CURRENT_TIMESTAMP) OR
-- or is the end greater than or equal to today?
STR_TO_DATE(e.meta_value, '%Y-%m-%d') >= DATE(CURRENT_TIMESTAMP)
-- order the results by the start, then the end, then the post title
ORDER BY start_date, end_date, p.post_title
-- paginate the results
LIMIT $perPage OFFSET $offset
SQL;
// get posts that match
$evts = $wpdb->get_results( $querystr );
// get the total number of results
$total = $wpdb->get_var( 'SELECT FOUND_ROWS()' );

Related

database query to get lowest price based on last crawel date

I would like to get lowest price of product based on last crawled dates by various resellers. My current function is very basic, it gets me lowest price from table without considering reseller ids and crawled timestamps.
I've rough idea that we can SELECT * FROM "custom_data_table" and process the data using php. Please have a look at attachment for further clarification.
function get_lowest_price($table_id) {
global $wpdb;
$table_prices = $wpdb->get_results(
$wpdb->prepare(
"SELECT price FROM `custom_data_table` WHERE tableid= %d"
,$table_id)
);
if (!empty($table_prices) && $table_prices !== NULL)
return rtrim(min($table_prices)->price, '00');
}
The right query here is:
SELECT price
FROM custom_data_name cdn, (
SELECT MAX(crawled) AS maxCrawled, resellerid
FROM custom_data_name
GROUP BY resellerid
) cdnFiltered
WHERE cdn.crawled = cdnFiltered.maxCrawled AND
cdn.resellerid = cdnFiltered.resellerid AND
tableid = %d;
Try this:
SELECT B.price
FROM (SELECT resellerid, MAX(crawled) max_crawled
FROM custom_data_table
GROUP BY resellerid) A
JOIN custom_data_table B
ON A.resellerid=B.resellerid AND A.max_crawled=B.crawled;
Maybe use ORDER BY crawled and LIMIT 1

Eloquent query building complex query to get unique records searching for an ID in 2 different columns in same table

I'm migrating a project to Laravel 4 and I am stuck with a quite complex query, which I'd like to migrate into a proper Eloquent query.
I have a table that contains chat messages, called chat_messages with a representing Model Chatmessage
The table contains a sender and a receipient column with a user id linking to the users table and User Model.
The query to get a list with all user IDs of all chat partners in raw SQL on the old version of the application is as follows:
$sql_allChatPartners = "SELECT DISTINCT chatPartner
FROM ( SELECT * FROM (
SELECT cm_receipient AS chatPartner, cm_sent_at
FROM chat_messages WHERE cm_sender = '".$me->userID."'
UNION
SELECT cm_sender AS chatPartner, cm_sent_at
FROM chat_messages WHERE cm_receipient = '".$me->userID."'
) whateva ORDER BY whateva.cm_sent_at DESC ) other";
Sorry for naming the "fake" tables whateva and other :-)
Could anyone put me in the right direction to do this with Eloquent Querybuilder?
It is important that I get the list of chatPartner IDs in the correct order, where the last chat message has been exchanged as first chatPartner. And the chatPartner where longest inactivity was in the chat as last entry.
This is what I got so far in my User Model...
public function allopenchats(){
$asSender = Chatmessage::where('sender', $this->id)->select('receipient as chatPartner, created_at');
$asBoth = Chatmessage::where('receipient', $this->id)->select('sender as chatPartner, created_at')
->union($asSender)->orderBy('created_at', 'desc')->get();
}
I renamed the columns cm_receipient to receipient, cm_sender to sender and sent_at to created_at in the new database for the new version
Your help would be very much appreciated!
You sql may change to:
SELECT IF (cm_receipient = '10', cm_sender, IF (cm_sender = '10',cm_receipient, 'no')) AS chatPartner, cm_sent_at
FROM chat_messages
WHERE cm_receipient = '10' OR cm_sender = '10'
GROUP BY chatPartner
HAVING chatPartner != 'no'
order by cm_sent_at DESC
In orm:
Chatmessage::where('sender','=',$this->id)
->orWhere('receipient','=',$this->id)
->select(DB::raw('IF (receipient = '.$this->id.', sender, IF (sender = '.$this->id.',receipient, 'no' )) AS chatPartner'), 'created_at')
->groupBy('chatPartner')
->having('chatPartner', '!=', 'no')
->orderBy('created_at', 'desc')
->get();
Thanks very much to Vitalik_74, I wouldn't have come that far without him.
Here is now the final query, although its not in ORM, but it is working fine and gives me the result I need.
$result = DB::select("SELECT *
FROM (
SELECT IF( receipient = '".$this->id."', sender, IF( sender = '".$this->id."', receipient, 'no' ) ) AS chatPartner, created_at
FROM chatmessages
WHERE receipient = '".$this->id."'
OR sender = '".$this->id."'
HAVING chatPartner != 'no'
ORDER BY created_at DESC
)whateva
GROUP BY whateva.chatPartner
ORDER BY whateva.created_at DESC");
if there is someone out there who can do this query with the Laravel Query Builder, I would be happy to see it. For now I'll leave it like this.

Simplify sql query to obtain one line per id

I have a multi-table SQL query.
My need is: The query should I generate a single line by 'etablissement_id' ... and all information that I want to be back in the same query.
The problem is that this query is currently on a table where "establishment" may have "multiple photos" and suddenly, my query I currently generates several lines for the same id...
I want the following statement - LEFT JOINetablissementContenuMultimediaON etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId - only a single multimedia content is displayed. Is it possible to do this in the query below?
Here is the generated query.
SELECT DISTINCT `etablissement`. * , `etablissementContenuMultimedia`. * , `misEnAvant`. * , `quartier`. *
FROM `etablissement`
LEFT JOIN `etablissementContenuMultimedia` ON etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId
LEFT JOIN `misEnAvant` ON misEnAvant.misEnAvant_etablissementId = etablissement.etablissement_id
LEFT JOIN `quartier` ON quartier_id = etablissement_quartierId
WHERE (
misEnAvant_typeMisEnAvantId =1
AND (
misEnAvant_dateDebut <= CURRENT_DATE
AND CURRENT_DATE <= misEnAvant_dateFin
)
)
AND (
etablissement_isActive =1
)
ORDER BY `etablissement`.`etablissement_id` ASC
LIMIT 0 , 30
Here is the code used ZF
public function find (){
$db = Zend_Db_Table::getDefaultAdapter();
$oSelect = $db->select();
$oSelect->distinct()
->from('etablissement')
->joinLeft('etablissementContenuMultimedia', 'etablissement.etablissement_id = etablissementContenuMultimedia.etablissementContenuMultimedia_etablissementId')
->joinLeft('misEnAvant', 'misEnAvant.misEnAvant_etablissementId = etablissement.etablissement_id')
->joinLeft('quartier', 'quartier_id = etablissement_quartierId ')
->where ('misEnAvant_typeMisEnAvantId = 1 AND (misEnAvant_dateDebut <= CURRENT_DATE AND CURRENT_DATE <= misEnAvant_dateFin) ')
->where ('etablissement_isActive = 1')
->order(new Zend_Db_Expr('RAND()'));
$zSql = $oSelect->__toString();
if(isset($_GET['debug']) AND $_GET['debug'] == 1)
echo $zSql ;
//die();
$oResultEtablissement = $db->fetchAll($oSelect);
return $oResultEtablissement ;
}
Can you help me?
Sincerely,
If you are looking to have only one of the media displayed out of many regardless of which it may be then you can just add a limit to the query? After that you can tweak the query for ASCending or DESCending perhaps?
Is this query supposed to have images (or image as it were) for one establishment, or one image each for each active establishment? I see you have a limit 0,30 which means you're likely paginating....
If the result you want is a search for only one establishment, and the first image it comes to would work fine .. just use "limit 1" and you'll only get one result.
I took the time to redo the whole model of the database ... and now it works. There was no solution for a system as flawed

How to include zero when counting with a MySQL GROUP BY expression

I'd like to count the number events that occur on each day over the last month, but also include a count of zero when no events are found. Is that possible?
Here's what I'm starting from...
SELECT COUNT(*) AS count,
DATE(usage_time_local) AS d
FROM usages
WHERE user_id=136
AND DATE(usage_time_local) >= DATE('2011-04-24')
AND DATE(usage_time_local) <= DATE('2011-05-24')
GROUP BY DATE(usage_time_local);
UPDATE: Given the answer, I implemented a code solution by initializing a loop and then filling in the details.
$dailyCount = array();
for( $i=1; $i<=30; $i++ ) {
$day = date('Y-m-d',(time()-($i*24*60*60)));
$dailyCount[$day] = 0;
}
foreach( $statement as $row ) {
$dailyCount[$row['d']] = $row['count'];
}
You can't do this with standard SQL queries - you'd be trying to group on a date(s) that doesn't exist in the table.
Standard workaround is to make a temporary table that contains the date range in sequential order with no gaps, join that against your table and do the count/aggregate as usual.

grouping by non-database field

How can I group a query result by a field that is not saved in the database.
For example I want to group the result by duration which is came from subtraction of start time and end time.
here is how i find out the duration
date1= $row_TicketRS['CloseDate'];
$date2 = $row_TicketRS['OpenDate'];
$diff = abs(strtotime($date2) - strtotime($date1));
$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
if ( $days > 0)
{
$time1 = $row_TicketRS['OpenTime'];
$time2= $row_TicketRS['CloseTime'];
$t1=($time1);
$t2=($time2);
$end=('14:30');
$start=('07:30');
$n = $end- $t1;
$n2 = $t2- $start;
$Hours2 = floor(($n+$n2)+(($days-1)*7));
echo $Hours2.' Hours';
but know i do not know how to add it to the query
here is my query
$strQuery = "SELECT count(`ticket`.TicketID) as TotOutput, department.`DeptName` FROM `ticket`, `user`, department where ticket.OwnerID = user.EmpNo and user.`DepartmentID` = department.`DepartmentID` and OpenDate between'".$DateFrom."' And '".$DateTo."'"
It'd be better to have details, but a derived table/inline view would allow you to group by a computed value:
SELECT x.duration,
COUNT(*)
FROM (SELECT t.col,
t.end_time - t.start_time AS duration
FROM YOUR_TABLE t) x
GROUP BY x.duration
How about adding that computed value to the query with an alias like this:
SELECT some_fields, end - start AS duration FROM table ORDER BY duration
dont put alias for hidden column , use directly
exmaple:
SELECT id, FLOOR(value/100)
FROM tbl_name
GROUP BY id, FLOOR(value/100);
Reference
MySQL permits expressions in GROUP BY
clauses, so the alias is unnecessary: