Yii2 join a subquery - php

I'm building a booking site, where properties (ex: hotel) have rooms and you can book them.
I've made a raw SQL query which filters the available rooms in a specified date range, but I don't know how to implement it in a YII AR way: using with active record find() as a relation
Do you guys have any suggestion or opinion about this?
SELECT
property.id,
property_lang.name,
max_people.total
FROM property
JOIN property_lang ON (property.id = property_lang.property_id AND property_lang.lang_id = 'hu')
JOIN (
SELECT
property_id AS id,
sum(max_people) AS total
FROM room
WHERE room.id NOT IN (
SELECT room_id
FROM booking
JOIN booking_status ON (booking.last_status_id = booking_status.id)
JOIN booking_room ON (booking.id = booking_room.id)
JOIN property ON booking.property_id = property.id
WHERE (booking_status.status > -1 OR booking_status.status IS NULL)
AND booking.check_in < '2017-10-18'
AND booking.check_out > '2017-10-14'
)
GROUP BY property_id
) max_people
ON max_people.id = property_id
ORDER BY property.id

The simplest solution is use the createCommand with params:
\Yii::$app->db->createCommand("SELECT
property.id,
property_lang.name,
max_people.total
FROM property
JOIN property_lang ON (property.id = property_lang.property_id AND property_lang.lang_id = 'hu')
JOIN (
SELECT
property_id AS id,
sum(max_people) AS total
FROM room
WHERE room.id NOT IN (
SELECT room_id
FROM booking
JOIN booking_status ON (booking.last_status_id = booking_status.id)
JOIN booking_room ON (booking.id = booking_room.id)
JOIN property ON booking.property_id = property.id
WHERE (booking_status.status > -1 OR booking_status.status IS NULL)
AND booking.check_in < ':startdate'
AND booking.check_out > ':finishdate'
)
GROUP BY property_id
) max_people
ON max_people.id = property_id
ORDER BY property.id")
->bindValues([':startdate' => $startDate, ':finishdate' => $startDate])
->execute();
Other way is to split query for subqueries and try to use AR. It will be something like that:
$subSubQuery=Booking::find()
->select(['room_id'])
->join('booking_status', 'booking.last_status_id = booking_status.id')
->join('booking_room', 'booking.id = booking_status.id')
->join('property', 'booking.property_id= booking_propery.id')
->andWhere(['or',
['>','booking_status.status',-1],
[ 'booking_status.status'=>null],
])
->andWhere(['<','booking.check_in',$startDate])
->andWhere(['>','booking.checkout',$finishDate])
->groupBy('property_id');
$subQuery = Room::find()->select(['property_id as id','sum(max_people) as total'])->andFilterWhere(['not in', 'answers_metering.id', $subSubQuery]);
$query = Property::find()->select(['property.id','property_lang.name','max_people.total'])
->join($subQuery. ' as max_people', 'max_people.id = property.id')
->join('property_lang', 'property.id = property_lang.property_id AND property_lang.lang_id = \'hu\'')
->orderBy(['property.id' => SORT_ASC])
->all();

Related

How to group by a single column in Laravel

I'm trying to group my results by a selected Raw Field,
$daily_summaries=DB::table("ticket_transactions")
->join('stations', 'stations.id', '=', 'ticket_transactions.station_id')
->join('companies' ,'companies.id','=' ,'ticket_transactions.company_id')
->where(['company_id'=>$id])
->select('ticket_transactions.company_id' , 'companies.name',
DB::raw('LEFT(ticket_transactions.date_issued,10) AS day', 'COUNT(ticket_transactions.id) AS tickets_sold',
'SUM(fare) AS revenue')
)
->groupBy('day')
->orderBy('day DESC')->get();
return $daily_summaries;
this is my sql when i run it direct.
select `ticket_transactions`.`company_id`, `companies`.`name`, LEFT(ticket_transactions.date_issued,10) AS day
from `ticket_transactions`
inner join `stations` on `stations`.`id` = `ticket_transactions`.`station_id`
inner join `companies` on `companies`.`id` = `ticket_transactions`.`company_id`
where (`company_id` = 2) group by `day` order by day DESC
Result

Trying to get multiple results with nested select statements

I'm trying to get this statement to return multiple rows (one for each campaign ID). This code was originally intended for only accepting one campaign ID, but I'm needing to involve it to accepting multiple campaign IDs. The following code snippet only returns 1 row, but should be returning 6 rows. I'd also assume this statement could be more efficient.
SELECT * FROM
(SELECT
campaignID,
count(ctaS.engagementID) AS numEngagements,
count(distinct(ctaS.targetID)) AS numTargets,
SUM(engagementType='email') AS numEmails,
SUM(engagementType='call') as numCalls,
SUM(engagementType='meeting') as numMeetings,
count(distinct(ctaC.email)) AS numEngagementsByEmail,
count(distinct(ctaS.ipAddress)) AS numEngagementsByIP
FROM engagements AS ctaS
LEFT JOIN engagements_content AS ctaC ON ctaS.engagementID = ctaC.engagementID
WHERE ctaS.campaignID IN ('639ba3c9d86726f2ef5a6ca3','2bce996549ed7acb','f91491361140ccd81a7ab8bb','3d4d9a45bba4b8ef9c22edbc','VhFfwsAnxr9OBfRvGWKsyr7H','0819c9ff210c24f9efcb16fc')
AND ctaS.is_active = 1
AND ctaS.targetID <> ''
AND ctaS.engagementType <> ''
AND ctaS.ipAddress NOT IN ('75.43.48.211')
) as tbl1,
(SELECT
t1.numTimesIndexLoaded,
t1.numTimesIndexLoadedUnique,
t1.numTimesResultsLoaded,
t1.numTimesResultsLoadedUnique,
t1.numTimesCTALoaded,
t1.numTimesCTALoadedUnique
FROM
(SELECT sum(ctaSL.page = 'index') AS numTimesIndexLoaded,
sum(ctaSL.page = 'results') AS numTimesResultsLoaded,
sum(ctaSL.page = 'cta') AS numTimesCTALoaded,
count(DISTINCT CASE WHEN ctaSL.page = 'index' THEN ctaSL.ip_address END) AS numTimesIndexLoadedUnique,
count(DISTINCT CASE WHEN ctaSL.page = 'results' THEN ctaSL.ip_address END) AS numTimesResultsLoadedUnique,
count(DISTINCT CASE WHEN ctaSL.page = 'cta' THEN ctaSL.ip_address END) AS numTimesCTALoadedUnique
FROM stats_page_loads AS ctaSL
WHERE ctaSL.campaign_id IN ('639ba3c9d86726f2ef5a6ca3','2bce996549ed7acb','f91491361140ccd81a7ab8bb','3d4d9a45bba4b8ef9c22edbc','VhFfwsAnxr9OBfRvGWKsyr7H','0819c9ff210c24f9efcb16fc')
AND ctaSL.is_active = 1
AND ctaSL.ip_address NOT IN ('75.43.48.211')
) t1
) as tbl2,
(SELECT tblA.numFacebookClicks, tblB.numTwitterClicks
FROM
(SELECT count(target_id) AS numFacebookClicks
FROM stats_social_clicks AS sc
WHERE sc.social_network = 'facebook'
AND sc.campaign_id IN ('639ba3c9d86726f2ef5a6ca3','2bce996549ed7acb','f91491361140ccd81a7ab8bb','3d4d9a45bba4b8ef9c22edbc','VhFfwsAnxr9OBfRvGWKsyr7H','0819c9ff210c24f9efcb16fc')
AND sc.is_active = 1
) AS tblA
JOIN
(SELECT count(target_id) AS numTwitterClicks
FROM stats_social_clicks AS sc
WHERE sc.social_network = 'twitter'
AND sc.campaign_id IN ('639ba3c9d86726f2ef5a6ca3','2bce996549ed7acb','f91491361140ccd81a7ab8bb','3d4d9a45bba4b8ef9c22edbc','VhFfwsAnxr9OBfRvGWKsyr7H','0819c9ff210c24f9efcb16fc')
AND sc.is_active = 1
) AS tblB
) as tbl3
WHERE campaignID IN ('639ba3c9d86726f2ef5a6ca3','2bce996549ed7acb','f91491361140ccd81a7ab8bb','3d4d9a45bba4b8ef9c22edbc','VhFfwsAnxr9OBfRvGWKsyr7H','0819c9ff210c24f9efcb16fc')

How to build nested query in codeigniter?

Hi below is the query to get count of different data from different tables.
select(
select count(*)
from teachers
where teacher_status = 1
)as teacher_count,
(
select count(*)
from students
where student_status = 1
)as students_count,
(
select count(*)
from housekeepers
where housekeeper_status = 1
)as housekeeping_count,
(
select count(*)
from students
where student_status = 1 and
gender = "Male"
) as total_male_student_count,
(
select count(*)
from students
where student_status = 1 and
gender = "Female"
) as total_female_student_count
Now i want to build this single query in codeigniter with the help of codeigniter builder class, so can someone guide me please..
Purpose of running single query is to minimize the database hit.
Thanks in advance..!!!
You can use: get_compiled_select
like this
$this->db->select('count(*) as count');
$this->db->from('teacher_status');
$teacher_status = $this->db->get_compiled_select();
$this->db->select("select($teacher_status)as teacher_count, ... ");
this->db ...
And use for others.

Adding a field in a MySQL Query

I need to add a field in one of our query. I'm nt a PHP programmer buy I can get my way around a little. The query is:
if (_QUERYSTRING_) {
switch ($intMode) {
case -1:
$result = mysqli_query($mysql,"
SELECT orders.id,
orders_addresses.strlastname,
orders_addresses.strfirstname,
orders_addresses.intprovince,
9 AS intmode,
Date(orders.dtimeorder) AS datepayment,
orders_costs.dbltotal AS dblamount,
orders_notes.strcod AS strtxn,
0 AS dblfee,
shipping_postalservices.strtracking"._LANG_." as strtrackingurl,
'À recevoir' AS strmode,
NULL AS strvendor
FROM orders
JOIN orders_costs
ON orders_costs.id = orders.id
JOIN orders_addresses
ON orders_addresses.id = orders.id
JOIN orders_notes
ON orders_notes.id = orders.id
JOIN shipping_postalservices
ON shipping_postalservices.id = orders_costs.intpostalservice
WHERE date(orders.dtimeorder) BETWEEN '".date("Y-m-d",$timeStart)."' AND '".date("Y-m-d",$timeEnd)."'
AND orders.boolshipped = 1
AND orders.boolcanceled = 0
AND orders_costs.boolcod = 1
AND orders_costs.dbltotal > 0
AND NOT EXISTS
(
SELECT *
FROM orders_payments
WHERE orders_payments.intorder = orders.id
AND orders_payments.intmode = 9
AND orders_payments.dblamount > 0)
GROUP BY orders.id
ORDER BY orders.dtimeorder,
orders.id");
break;
default:
$result = mysqli_query($mysql,"
SELECT orders.id,
orders_addresses.strlastname,
orders_addresses.strfirstname,
orders_addresses.intprovince,
orders_payments.intmode,
Date(orders_payments.dtimepayment) AS datepayment,
orders_payments.dblamount,
orders_payments.strtxn,
orders_payments.dblfee,
shipping_postalservices.strtracking"._LANG_." as strtrackingurl,
payments.strname"._LANG_." AS strmode,
payments.strvendor
FROM orders_payments
JOIN orders
ON orders.id = orders_payments.intorder
JOIN orders_costs
ON orders_costs.id = orders.id
JOIN orders_addresses
ON orders_addresses.id = orders.id
JOIN shipping_postalservices
ON shipping_postalservices.id = orders_costs.intpostalservice
LEFT JOIN payments
ON payments.id = orders_payments.intmode
WHERE date(orders_payments.dtimepayment) BETWEEN '".date("Y-m-d",$timeStart)."' AND '".date("Y-m-d",$timeEnd)."'".(!empty($intMode) ? "
AND orders_payments.intmode = '".$intMode."'" : NULL)."
GROUP BY orders.id,
orders_payments.intpayment
ORDER BY orders_payments.dtimepayment,
orders.id");
break;
}
The field that needs to be added is orders_addresses.intProvince so it can be displayed in the results. I tried to understand a little but I guess it's a little more complicated than I thought. It does display the province, which are numbers. My question would be, how do I "translate" those numbers by the actual names so it displays "Ontario" instead of 9? The name of the provinces are in another table called Province.
You will need to add another JOIN:
JOIN Province ON orders_addresses.intprovince = Province.x
And in the SELECT part, replace orders_addresses.intprovince by Province.y
where
x = column in table Province that holds the province id
y = column in table Province that holds the province name

Combining two SQL queries PDO

I'm quite stuck on the following issue. I have a series of tables:
What I want to do is get all the information on a room, assuming that the amount of bookings don't exceed the room number available for that Room.
So to get my Room details my SQL is this:
SELECT Rooms.RoomID as RoomID,
RoomName, NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate
FROM Rooms, RoomDetails
WHERE Rooms.AccommodationID = :aid AND
Rooms.RoomID = RoomDetails.RoomID
GROUP BY RoomName
Which upon return gets me a list of details for those rooms as follows:
I then use this query to get the number of bookings, and the ID of the room:
SELECT Booking.RoomID,
count(Booking.RoomID) as Bookings
FROM Booking
WHERE ArriveDate >= :aDate AND
DepartDate <= :dDate AND
AccommodationID = :aid
GROUP BY RoomID
I then combine both and feed the two arrays back in one array using this function:
public function get_availability($aid, $aDate, $dDate) {
$stmt = $this->db->prepare('SELECT Rooms.RoomID as RoomID, RoomName, NumOfRooms, MaxPeopleExistingBeds, MaxExtraBeds, MaxExtraPeople, CostPerExtraPerson, MaximumFreeChildren, IncludeBreakfast, MinRate FROM Rooms, RoomDetails WHERE Rooms.AccommodationID = :aid AND Rooms.RoomID = RoomDetails.RoomID GROUP BY RoomName');
$stmt->bindValue(':aid', $aid);
$stmt->execute();
$rooms = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt2 = $this->db->prepare('SELECT Booking.RoomID, count(Booking.RoomID) as Bookings FROM Booking WHERE ArriveDate >= :aDate AND DepartDate <= :dDate AND AccommodationID = :aid GROUP BY RoomID');
$stmt2->bindValue(':aid', $aid);
$stmt2->bindValue(':aDate', $aDate);
$stmt2->bindValue(':dDate', $dDate);
$stmt2->execute();
$bookings = $stmt2->fetchAll(PDO::FETCH_ASSOC);
$room = array($rooms, $bookings);
return (!empty($room)) ? $room : false;
}
The thing is, what I actually want to do is only return the room details where NumOfRooms is less than the number of Bookings.
So for instance where I have $bookings, if it tells me that for room ID 4, I have 3 bookings for a set period, and my NumOfRooms is 1. Then I know that I have no capacity that week to take any more bookings on. If however I have 1 booking and one capacity then that is still full. But if I have NumOfRooms of 2, and bookings amount to 1, I know I have room.
So basically if NumOfRooms > BookingCount then the room is available.
How can I amalgamate both queries and simplify my code to make this possible?
I.E to put it simply, how do I select all of the info from RoomDetails given an ArriveDate in Booking and a DepartDate and a RoomID, where NumOfRooms > count(Booking.RoomID) (Where it is within those dates and the room id is equal to the room id of Rooms).
Your problem can be solved by simply updating the SQL statement itself:
SELECT r.RoomID AS RoomID,
RoomName,
NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate
FROM Rooms r
JOIN RoomDetails rd
ON r.RoomID = rd.RoomID
JOIN (
SELECT b.RoomID,
AccommodationID,
count(b.RoomID) AS Bookings
FROM Booking b
WHERE ArriveDate >= :aDate
AND DepartDate <= :dDate
GROUP BY RoomID
) t
ON t.AccommodationID = r.AccommodationID
WHERE r.AccommodationID = :aid
AND t.Bookings < NumOfRooms
GROUP BY RoomName
You can select out all of the booking counts per room for the desired date range as a subquery, and then LEFT JOIN that subquery against the list of your rooms filtered by your desired AccommodationID and the desired NumOfRooms > BookingCount criteria. The key here is in the join type used for this subquery, as an inner join would limit your results to only rooms that actually had bookings.
SELECT Rooms.RoomID as RoomID,
RoomName, NumOfRooms,
MaxPeopleExistingBeds,
MaxExtraBeds,
MaxExtraPeople,
CostPerExtraPerson,
MaximumFreeChildren,
IncludeBreakfast,
MinRate,
BookingCount
FROM Rooms
INNER JOIN RoomDetails on Rooms.RoomID = RoomDetails.RoomID
LEFT JOIN (
SELECT Booking.RoomID,
count(Booking.RoomID) as BookingCount
FROM Booking
WHERE ArriveDate >= :aDate AND
DepartDate <= :dDate
GROUP BY Booking.RoomID
) RoomBookings ON Rooms.RoomID = RoomBookings.RoomID
WHERE Rooms.AccommodationID = :aid
AND NumOfRooms > BookingCount
GROUP BY RoomName

Categories