I'm pretty useless at SQL it seems and I'm trying to figure out what is the correct query to use.
I have a table Items and a table Reservations. An item can have many reservations and reservations are made between two dates.
I'm trying to create a search query which will return all the Items which don't have reservations between two user inputted dates.
the SQL I have at the minute looks like:
SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email`
FROM `database`.`items` AS `Item`
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id` AND `ReservationJoin`.`start` >= '2013-07-17' and `ReservationJoin`.`finnish` <= '2013-07-20')
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`)
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%')
AND `ReservationJoin`.`id` IS NULL
LIMIT 10
I'm doing this in cakephp 2.3 so the code looks like:
$this->paginate = array(
"conditions" => array(
"or" => array(
"Item.name LIKE" => '%'.$projector.'%',
"Item.description LIKE" => '%'.$projector.'%',
),
"ReservationJoin.id" => null,
),
"joins" => array(
array(
"table" => "reservations",
"alias" => "ReservationJoin",
"type" => "LEFT",
"conditions" => array(
"ReservationJoin.item_id = Item.id",
"ReservationJoin.checkin >= '{$start}' and ReservationJoin.checkout <= '{$finnish}'",
)
)
),
"limit"=>10
);
$data = $this->paginate('Item');
This isn't working and I think it's to do with the join not excluding the reservations properly. But I've not been able to figure out what the correct mysql is. Can a kind soul tell me what I should be using?
thanks
If something has a reservation between two dates, then one of the following is true:
It has a start date between the dates
It has an end date between the dates
It has a start date before the earlier date and an end date after the second one
The following query uses this logic in a having clause. The approach is to aggregate at the item level and ensure that the three above conditions are true:
SELECT i.`id`, i.`user_id`, i.`name`, i.`description`
FROM `database`.`items`i LEFT JOIN
`database`.`reservations` r
ON r.`item_id` = i.`id`
WHERE ((i.`name` LIKE '%projector%') OR (i.`description` LIKE '%projector%')
group by i.id
having max(start between '2013-07-17' and '2013-07-20') = 0 and
max(finish between '2013-07-17' and '2013-07-20') = 0 and
max(start < '2013-07-17' and finished > '2013-07-20') = 0
LIMIT 10;
Note that none matches are returned, because the conditions are treated as false when start and ned are NULL.
I think it may be easier for you in CakePHP to put all of the conditions in a WHERE clause. (This could also be done with some OUTER JOINs but it may be difficult to transcribe into CakePHP)
SELECT id, user_id, name, description
FROM items
WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
AND NOT EXISTS(
SELECT *
FROM reservations
WHERE items.id = reservations.item_id
AND (
'2013-07-17' BETWEEN start and finnish
OR
'2013-07-20' BETWEEN start and finnish
OR
start BETWEEN '2013-07-17' AND '2013-07-20'));
Or, using the same logic the WHERE clause can be cleaned up to be
SELECT id, user_id, name, description
FROM items
WHERE ((name LIKE '%projector%') OR (description LIKE '%projector%'))
AND NOT EXISTS(
SELECT *
FROM reservations
WHERE items.id = reservations.item_id
AND NOT(
'2013-07-17' > finnish
OR
'2013-07-20' < start
));
you can try this.
SELECT `Item`.`id`, `Item`.`user_id`, `Item`.`name`, `Item`.`description`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`email`
FROM `database`.`items` AS `Item`
LEFT JOIN `database`.`reservations` AS `ReservationJoin` ON (`ReservationJoin`.`item_id` = `Item`.`id`)
LEFT JOIN `database`.`users` AS `User` ON (`Item`.`user_id` = `User`.`id`)
WHERE ((`Item`.`name` LIKE '%projector%') OR (`Item`.`description` LIKE '%projector%'))
AND `ReservationJoin`.`start` >= '2013-07-17'
AND `ReservationJoin`.`finnish` <= '2013-07-20'
AND `ReservationJoin`.`id` IS NULL
LIMIT 10
i.e just move the date clauses into the WHERE clause as oppose to being in the JOIN CLAUSE
Related
Haven't come across a situation like this so not sure how to correct it. I'm guessing a sub query is needed?
I need the SUM of votes.vote and the COUNT of votes.vote. This allows me to calculate a rating (sum of all votes / # of votes = rating) for the location selected.
Here is the query with * and static binding to make it easier to understand :
//prepare
$stmt = $db->prepare("
SELECT SQL_CALC_FOUND_ROWS
*,
COALESCE(SUM(votes.vote),0) AS vote_total,
COUNT(votes.vote) AS number_votes
FROM locations
LEFT JOIN tables
ON tables.location_id = locations.location_id
LEFT JOIN votes
ON votes.location_id = locations.location_id
LEFT JOIN events
ON events.location_id = locations.location_id
WHERE locations.location_id = :location_id
LIMIT :limit OFFSET :offset
");
//bindings
$binding = array(
'location_id' => 11,
'limit' => 20,
'offset' => 0
);
//execute
$stmt->execute($binding);
//results
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
There are 11 events joined to this location. There are only two votes joined to it (a value of 3 and a value of 4). I am getting the following :
[vote_total] => 77 (should be 7, but is 7*11)
[number_votes] => 22 (should be 2, but is 2*11)
Aside from that only one result is returned rather than 11. If I remove the votes table join and the SUM/COUNT selects for it all 11 results are shown as they should be.
Is it possible to get the SUM and COUNT totals for votes.vote in the same query in some way or will a separate query be needed just to get those values?
My best guess based on your description of the problem is that you want one row per event. If so:
SELECT SQL_CALC_FOUND_ROWS e.*
COALESCE(SUM(v.vote), 0) AS vote_total,
COUNT(v.vote) AS number_votes
FROM locations l LEFT JOIN
tables
ON t.location_id = l.location_id LEFT JOIN
votes v
ON v.location_id = l.location_id LEFT JOIN
events e
ON e.location_id = l.location_id
WHERE l.location_id = :location_id
GROUP BY e.event_id
LIMIT :limit OFFSET :offset;
how to join this in single query any help to combine these two queries as one without looping,
$today_date = mktime(0, 0, 0, $mon, $day-1, $year);
SELECT * FROM (`lead_follow_up`) LEFT JOIN `leads` ON `leads`.`id` = `lead_follow_up`.`lead_id` WHERE `date` <= $today_date GROUP BY `lead_follow_up`.`lead_id` ORDER BY `lead_follow_up`.`date` DESC
from the above query i get array $previou
$previou= Array
(
[0] => stdClass Object
(
[id] => 1
[lead_id] => 75943
[date] => 1438930800
[updated_on] => 1438884890
)
[1] => stdClass Object
(
[id] => 2
[lead_id] => 75943
[date] => 1416459600
[updated_on] => 1415901523
),
[2] => stdClass Object
(
[id] => 3
[lead_id] => 75943
[date] => 1416459600
[updated_on] => 1415901523
),....etc
);
foreach($previou as $key => $p):
$q = "SELECT `id` FROM (`lead_follow_up`) WHERE `lead_id` = '".$p->id."' AND `date` > '".$p->date."' ORDER BY `updated_on` DESC ";
if(!$this->db->query($q)){
$previouData[$key] = $p;
$pCount++;
}
endforeach;
how to join this in single query any help to combine these two queries as one without looping,
Your queries don't make much sense. For a start your first query has a GROUP BY lead_follow_up.lead_id but no aggregate functions. So in MySQL that will return one row for each value of lead_id (which row it returns is not defined).
Yet your array of sample data has multiple rows per lead_id so cannot have come from the query.
You are also LEFT OUTER JOINing the leads table, yet it doesn't seem to make sense to have a lead_follow_up which doesn't relate to a lead. As such you may as well use an INNER JOIN.
I am going to assume that what you want is a list of leads / lead_follow_ups and for each one a couple of all the follow ups after that particular follow up. That would give you something like this (making loads of assumptions as I do not know your table structure):-
SELECT leads.id AS lead_id,
lead_follow_up.id
lead_follow_up.`date`,
lead_follow_up.updated_on,
COUNT(lead_follow_up_future.id) AS future_lead_count
FROM leads
INNER JOIN lead_follow_up ON leads.id = lead_follow_up.lead_id
LEFT OUTER JOIN lead_follow_up AS lead_follow_up_future ON leads.id = lead_follow_up.lead_id AND lead_follow_up_future.`date` > lead_follow_up.`date`
WHERE lead_follow_up.`date` <= $today_date
GROUP BY leads.id AS lead_id,
lead_follow_up.id
lead_follow_up.`date`,
lead_follow_up.updated_on
ORDER BY lead_follow_up.date DESC
I need to filter according to key + value in a polymorphic association. In this example, an Incident can have many CustomFieldsValues, and I need to filter Incidents by CustomFieldsValue.custom_field_id and CustomFieldsValue.value.
I tried creating a search like the following SQL query:
SELECT id FROM incidents
WHERE id IN (
SELECT customfieldable_id from custom_fields_values
WHERE (custom_fields_values.custom_field_id = 1091 AND (custom_fields_values.value >= '2015-06-29 10:00:00') AND (custom_fields_values.value <= '2015-07-03 09:59:59'))
)
AND id IN (
SELECT customfieldable_id from custom_fields_values
WHERE (custom_fields_values.custom_field_id = 1099 AND (custom_fields_values.value = 'bbb'))
);
Or this one with LEFT OUTER JOIN:
SELECT distinct incidents.id FROM incidents
LEFT OUTER JOIN custom_fields_values ON custom_fields_values.customfieldable_id = incidents.id AND custom_fields_values.customfieldable_type = 'Incident'
WHERE incidents.id IN (
SELECT customfieldable_id from custom_fields_values
WHERE (custom_fields_values.custom_field_id = 1091 AND (custom_fields_values.value >= '2015-06-29 10:00:00') AND (custom_fields_values.value <= '2015-07-03 09:59:59'))
)
AND incidents.id IN (
SELECT customfieldable_id from custom_fields_values
WHERE (custom_fields_values.custom_field_id = 1099 AND (custom_fields_values.value = 'bbb'))
);
I thought the Ransack query would look something like this:
Incident.ransack(:m=>"and", :g=>{
"0"=>{"id_in"=>{
"0"=>{:m=>"and", :g=>{
"0"=>{"custom_fields_values_custom_field_id_is"=>1091},
"1"=>{"custom_fields_values_value_gte_any"=>["2015-06-29 00:00:00"]},
"2"=>{"custom_fields_values_value_lte_any"=>["2015-07-02 23:59:59"]}
}}}},
"1"=>{"id_in"=>{
"0"=>{:m=>"and", :g=>{
"0"=>{"custom_fields_values_custom_field_id_is"=>1099},
"1"=>{"custom_fields_values_value_is_any"=>["bbb"]}
}}}}
})
Is there a way to include nested queries in Ransack? I tried merging searches, but got NoMethodError: undefined method 'base' for #<CustomFieldsValue::ActiveRecord_Relation:0x0000010fc45eb8>.
Is there another way of doing this?
In Magento v1.9 I need a MYSQL select that would do the following:
list all the customers that did not place any orders in the past 12 months
Thanks!
Basically you want customers that have never placed an order or customers whose last order was over 12 months ago.
You could do it using a customer collection:
$date = new DateTime();
$date->sub(new DateInterval('P12M'));
$customers = Mage::getModel('customer/customer')->getCollection();
$customers->getSelect()->joinLeft(
array('o' => Mage::getSingleton('core/resource')->getTableName('sales/order')),
'o.customer_id = e.entity_id',
array(
'last_order_date' => 'MAX(o.created_at)',
)
);
$customers->groupByAttribute('entity_id')
->getSelect()
->having('last_order_date < ?',$date->format('Y-m-d'))
->orHaving('last_order_date IS NULL');
Or if you want the mysql for that just call
$customers->getSelect();
And that will give you the following MYSQL query
SELECT `e`.*, MAX(o.created_at) AS `last_order_date` FROM `customer_entity` AS `e` LEFT JOIN `sales_flat_order` AS `o` ON o.customer_id = e.entity_id WHERE (`e`.`entity_type_id` = '1') GROUP BY `e`.`entity_id` HAVING (last_order_date < '2014-03-26') OR (last_order_date IS NULL)
Hope that helps!
About the system:
- There are tutors who create classes and packs
- A tags based search approach is being followed.Tag relations are created when new tutors register and when tutors create packs (this makes tutors and packs searcheable). For details please check the section How tags work in this system? below.
Following is the concerned query
Can anybody help me suggest an approach using temporary tables. We have indexed all the relevant fields and it looks like this is the least time possible with this approach:-
SELECT SUM(DISTINCT( t.tag LIKE "%Dictatorship%"
OR tt.tag LIKE "%Dictatorship%"
OR ttt.tag LIKE "%Dictatorship%" )) AS key_1_total_matches
,
SUM(DISTINCT( t.tag LIKE "%democracy%"
OR tt.tag LIKE "%democracy%"
OR ttt.tag LIKE "%democracy%" )) AS key_2_total_matches
,
COUNT(DISTINCT( od.id_od )) AS
tutor_popularity,
CASE
WHEN ( IF(( wc.id_wc > 0 ), ( wc.wc_api_status = 1
AND wc.wc_type = 0
AND wc.class_date > '2010-06-01 22:00:56'
AND wccp.status = 1
AND ( wccp.country_code = 'IE'
OR wccp.country_code IN ( 'INT' )
) ), 0)
) THEN 1
ELSE 0
END AS 'classes_published'
,
CASE
WHEN ( IF(( lp.id_lp > 0 ), ( lp.id_status = 1
AND lp.published = 1
AND lpcp.status = 1
AND ( lpcp.country_code = 'IE'
OR lpcp.country_code IN ( 'INT' )
) ), 0)
) THEN 1
ELSE 0
END AS 'packs_published',
td . *,
u . *
FROM tutor_details AS td
JOIN users AS u
ON u.id_user = td.id_user
LEFT JOIN learning_packs_tag_relations AS lptagrels
ON td.id_tutor = lptagrels.id_tutor
LEFT JOIN learning_packs AS lp
ON lptagrels.id_lp = lp.id_lp
LEFT JOIN learning_packs_categories AS lpc
ON lpc.id_lp_cat = lp.id_lp_cat
LEFT JOIN learning_packs_categories AS lpcp
ON lpcp.id_lp_cat = lpc.id_parent
LEFT JOIN learning_pack_content AS lpct
ON ( lp.id_lp = lpct.id_lp )
LEFT JOIN webclasses_tag_relations AS wtagrels
ON td.id_tutor = wtagrels.id_tutor
LEFT JOIN webclasses AS wc
ON wtagrels.id_wc = wc.id_wc
LEFT JOIN learning_packs_categories AS wcc
ON wcc.id_lp_cat = wc.id_wp_cat
LEFT JOIN learning_packs_categories AS wccp
ON wccp.id_lp_cat = wcc.id_parent
LEFT JOIN order_details AS od
ON td.id_tutor = od.id_author
LEFT JOIN orders AS o
ON od.id_order = o.id_order
LEFT JOIN tutors_tag_relations AS ttagrels
ON td.id_tutor = ttagrels.id_tutor
LEFT JOIN tags AS t
ON t.id_tag = ttagrels.id_tag
LEFT JOIN tags AS tt
ON tt.id_tag = lptagrels.id_tag
LEFT JOIN tags AS ttt
ON ttt.id_tag = wtagrels.id_tag
WHERE ( u.country = 'IE'
OR u.country IN ( 'INT' ) )
AND CASE
WHEN ( ( tt.id_tag = lptagrels.id_tag )
AND ( lp.id_lp > 0 ) ) THEN lp.id_status = 1
AND lp.published = 1
AND lpcp.status = 1
AND ( lpcp.country_code = 'IE'
OR lpcp.country_code IN (
'INT'
) )
ELSE 1
END
AND CASE
WHEN ( ( ttt.id_tag = wtagrels.id_tag )
AND ( wc.id_wc > 0 ) ) THEN wc.wc_api_status = 1
AND wc.wc_type = 0
AND
wc.class_date > '2010-06-01 22:00:56'
AND wccp.status = 1
AND ( wccp.country_code = 'IE'
OR wccp.country_code IN (
'INT'
) )
ELSE 1
END
AND CASE
WHEN ( od.id_od > 0 ) THEN od.id_author = td.id_tutor
AND o.order_status = 'paid'
AND CASE
WHEN ( od.id_wc > 0 ) THEN od.can_attend_class = 1
ELSE 1
END
ELSE 1
END
AND ( t.tag LIKE "%Dictatorship%"
OR t.tag LIKE "%democracy%"
OR tt.tag LIKE "%Dictatorship%"
OR tt.tag LIKE "%democracy%"
OR ttt.tag LIKE "%Dictatorship%"
OR ttt.tag LIKE "%democracy%" )
GROUP BY td.id_tutor
HAVING key_1_total_matches = 1
AND key_2_total_matches = 1
ORDER BY tutor_popularity DESC,
u.surname ASC,
u.name ASC
LIMIT 0, 20
The problem
The results returned by the above query are correct (AND logic working as per expectation), but the time taken by the query rises alarmingly for heavier data and for the current data I have it is like 10 seconds as against normal query timings of the order of 0.005 - 0.0002 seconds, which makes it totally unusable.
Somebody suggested in my previous question to do the following:-
create a temporary table and insert here all relevant data that might end up in the final result set
run several updates on this table, joining the required tables one at a time instead of all of them at the same time
finally perform a query on this temporary table to extract the end result
All this was done in a stored procedure, the end result has passed unit tests, and is blazing fast.
I have never worked with temporary tables till now. Only if I could get some hints, kind of schematic representations so that I can start with...
Is there something faulty with the query?
What can be the reason behind 10+ seconds of execution time?
How tags work in this system?
When a tutor registers, tags are entered and tag relations are created with respect to tutor's details like name, surname etc.
When a Tutors create packs, again tags are entered and tag relations are created with respect to pack's details like pack name, description etc.
tag relations for tutors stored in tutors_tag_relations and those for packs stored in learning_packs_tag_relations. All individual tags are stored in tags table.
Temporary tables are not a silver bullet. The fundamental problem with your queries lies with patterns like this:
t.tag LIKE "%Dictatorship%"
OR tt.tag LIKE "%Dictatorship%"
OR ttt.tag LIKE "%Dictatorship%"
Wildcarding the left side of a LIKE comparison guarantees that an index can not be used. Effectively, you're table scanning all three tables involved...
You need to leverage Full Text Searching, either MySQL's native FTS or 3rd party stuff like Sphinx. All the FTS I've known include a scoring/rank value indicating the strength of the match - you can read the MySQL documentation for the algorithm details. But the score/rank is not the same as what you've got: SUM(DISTINCT LIKE...), you could get the same using something like:
SELECT t.id_tag,
COUNT(*) AS num_matches
FROM TABGS
WHERE MATCH(tag) AGAINST ('Dictatorship')
GROUP BY t.id_tag