How can I simplify the query without using UNION ALL? - mysql

SELECT i.id invn_id, IF(ss.serial_id IS NOT NULL, ss.serial_id, NULL) serial_number,
IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty)) qty,
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
UNION ALL
(
SELECT i.id invn_id,
IF(ss.serial_id IS NOT NULL,ss.serial_id, NULL) serial_number,
-(SUM(IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty))) - -(i.qty)) qty, -- difference
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
GROUP BY i.id -- difference
HAVING qty > 0 -- difference
)
ORDER BY site_id , prod_id , trans_date , qty ASC
Observe that the second statement (union table) is almost the same as the first statement except for the lines in comment. I'm satisfied with the outcome, but not satisfied with the query because of its redundancy. Is there a possibility to make this brief?
The output I wanna get is something like this, for instance:
I have a total quantity of 5 with an ID of 95514:
+-------+-----------+---------+
| id | qty | prod_id |
+-------+-----------+---------+
| 95514 | 5.000000 | 7655 |
+-------+-----------+---------+
If I'll execute the query above, the result will be like this:
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
| invn_id | serial_number | qty | flag | site_id | prod_id | fifo_total_amount | trans_date |
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
| 95514 | 237658 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237671 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237699 | 1.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
| 95514 | 237658 | 2.000000 | auto | 1 | 7655 | 2763.0194 | 2010-07-22 09:48:24 |
+---------+---------------+----------+------+---------+---------+-------------------+---------------------+
The first three rows returned the first statement of the query in UNION ALL, while the fourth row returned the second statement. If we'll sum up the qty column, we can get the value of 5.

I suspect you want group by with rollup:
SELECT i.id invn_id,
IF(ss.serial_id IS NOT NULL,ss.serial_id, NULL) serial_number,
-(SUM(IF(ss.serial_id IS NOT NULL, 1.000000, -(i.qty))) - -(i.qty)) qty, -- difference
'auto' flag, i.sid site_id, i.prod_id, i.fifo_total_amount, i.trans_date
FROM inventories i
INNER JOIN prod p ON p.id = i.prod_id
LEFT JOIN sale_products sp ON sp.inventory_id = i.id
LEFT JOIN sale_serial ss ON ss.prod_id = i.prod_id AND ss.sale_id = sp.sale_id
WHERE i.qty < 0
AND ('2015-03-25 00:00:00' IS NULL OR i.trans_date >= '2015-03-25 00:00:00')
AND ('2015-03-27 08:27:36' IS NULL OR i.trans_date <= '2015-03-27 08:27:36')
AND p.name NOT IN ('Starting Balance' , 'Opening Balance', 'Equity')
AND i.prod_id = 7655 AND (0 = 0 OR i.sid = 0)
GROUP BY i.id WITH ROLLUP
HAVING qty > 0 -- difference
However, I'm not 100% sure how this fits into the SELECT logic.

Related

Complex SQL Query for Inventory System

I have 5 tables:
Items
Inventory
ConsumedItemsMonitoring
DamagedItemsMonitoring
UnaccountedItems
I'm new to Complex SQL queries did some research and asked for help and this is what I my code looks like so far.
SELECT Items.ItemID, Items.Item,
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity ELSE 0 END) -
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) <CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)<CURDATE() THEN damagedinventory.Quantity ELSE 0 end) -
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)<CURDATE() THEN unaccounteditems.Quantity ELSE 0 end) AS 'PrevBalance',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate)=CURDATE() THEN Inventory.Quantity else 0 END) AS 'DeliveredToday',
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate)=CURDATE() THEN damagedinventory.Quantity ELSE 0 END) AS 'DamagedToday',
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate)=CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END) AS 'ConsumedToday',
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate)=CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) AS 'UnAccountedToday',
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) < CURDATE() THEN Inventory.Quantity else 0 end)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) < CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) < CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) < CURDATE() THEN unaccounteditems.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(consumeditemmonitoring.TransactionDate) = CURDATE() THEN consumeditemmonitoring.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(damagedinventory.ItemTransactionDate) = CURDATE() THEN damagedinventory.Quantity ELSE 0 END)-
SUM(CASE WHEN DATE(unaccounteditems.ItemTransactionDate) = CURDATE() THEN unaccounteditems.Quantity ELSE 0 END) +
SUM(CASE WHEN DATE(Inventory.ItemTransactionDate) = CURDATE() then Inventory.Quantity ELSE 0 end) AS 'Total Balance'
FROM Items
LEFT OUTER JOIN consumeditemmonitoring ON consumeditemmonitoring.ItemID = Items.ItemID
LEFT OUTER JOIN damagedinventory ON damagedinventory.ItemID = Items.ItemID
LEFT OUTER JOIN unaccounteditems ON unaccounteditems.ItemID = Items.ItemID
LEFT OUTER JOIN inventory ON inventory.ItemID= Items.ItemID
GROUP BY Items.ItemID
The output looks like some of the table are multiplied.
What you are seeing is a result of how joins work and the fact that the joins are executed before the group by. I can illustrate this with a simplified version of your data.
drop table if exists
items,
items_inventory,
items_consumed,
items_damaged,
items_unaccounted;
create table items (id int);
create table items_inventory(id int,itemid int,qty int);
create table items_consumed(id int,itemid int,qty int);
create table items_damaged(id int,itemid int,qty int);
create table items_unaccounted(id int,itemid int,qty int);
insert into items values(1),(2);
insert into items_inventory values (1,1,10),(2,1,10),(2,2,20);
insert into items_consumed values(1,1,5),(2,2,15);
insert into items_damaged values(1,1,25);
If we run a simple select
select i.id,
ii.id,ii.qty,
ic.id,ic.qty,
id.id,id.qty,
iu.id,iu.qty
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
;
we get 2 rows for item 1 even though there is only 1 row for items_consumed
+------+------+------+------+------+------+------+------+------+
| id | id | qty | id | qty | id | qty | id | qty |
+------+------+------+------+------+------+------+------+------+
| 1 | 1 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 1 | 2 | 10 | 1 | 5 | 1 | 25 | NULL | NULL |
| 2 | 2 | 20 | 2 | 15 | NULL | NULL | NULL | NULL |
+------+------+------+------+------+------+------+------+------+
3 rows in set (0.00 sec)
When we aggregate
select i.id,
count(*) as rows,
sum(ii.qty) as inventory,
sum(ic.qty) as consumed,
sum(id.qty) as damaged,
sum(iu.qty) as unaccounted
from items i
left join items_inventory ii on ii.itemid = i.id
left join items_consumed ic on ic.itemid = i.id
left join items_damaged id on id.itemid = i.id
left join items_unaccounted iu on iu.itemid = i.id
group by i.id;
we get 'doubling' up of consumed and damaged.
+------+------+-----------+----------+---------+-------------+
| id | rows | inventory | consumed | damaged | unaccounted |
+------+------+-----------+----------+---------+-------------+
| 1 | 2 | 20 | 10 | 50 | NULL |
| 2 | 1 | 20 | 15 | NULL | NULL |
+------+------+-----------+----------+---------+-------------+
2 rows in set (0.00 sec)
One way to deal with this is to aggregate BEFORE you join by pushing the aggregations into sub queries which you would then join. For example
select i.id, ii.inventory,ic.consumed,id.damaged,iu.unaccounted,
coalesce(ii.inventory,0)+coalesce(ic.consumed,0)+coalesce(id.damaged,0)+coalesce(iu.unaccounted,0) total
from items i
left join (select ii.itemid,sum(ii.qty) as inventory from items_inventory ii group by itemid) ii on ii.itemid = i.id
left join (select ic.itemid,sum(ic.qty) as consumed from items_consumed ic group by itemid) ic on ic.itemid = i.id
left join (select id.itemid,sum(id.qty) as damaged from items_damaged id group by itemid) id on id.itemid = i.id
left join (select iu.itemid,sum(iu.qty) as unaccounted from items_unaccounted iu group by itemid) iu on iu.itemid = i.id
;
+------+-----------+----------+---------+-------------+-------+
| id | inventory | consumed | damaged | unaccounted | total |
+------+-----------+----------+---------+-------------+-------+
| 1 | 20 | 5 | 25 | NULL | 50 |
| 2 | 20 | 15 | NULL | NULL | 35 |
+------+-----------+----------+---------+-------------+-------+
2 rows in set (0.00 sec)
Working Query thanks to sir #P.Salmon
SELECT I.ItemID,
I.Item,
COALESCE(II.InventoryPrevBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) PrevBalance,
COALESCE(II.InventoryBal,0) CurrentDelivered,
COALESCE(IC.Consumed,0) CurrentConsumed,
COALESCE(ID.Damaged,0) CurrentDamaged,
COALESCE(IU.Unaccounted,0) CurrentUnaccounted,
COALESCE(II.InventoryPrevBal,0) + COALESCE(II.InventoryBal,0) - COALESCE(ICP.ConsumedPrevBal,0) - COALESCE(IDP.DamagedPrevBal,0) - COALESCE(IUP.UnaccountedPrevBal,0) - COALESCE(IC.Consumed,0) - COALESCE(ID.Damaged,0) - COALESCE(IU.Unaccounted,0) CurrentTotal
FROM items I
LEFT JOIN (SELECT II.ItemID, SUM(CASE WHEN DATE(II.ItemTransactionDate) < CURDATE() THEN II.Quantity ELSE 0 END) as InventoryPrevBal, SUM(CASE WHEN DATE(II.ItemTransactionDate) = CURDATE() THEN II.Quantity ELSE 0 END) as InventoryBal FROM inventory II GROUP BY ItemID) II ON II.ItemID = I.ItemID
LEFT JOIN (SELECT ICP.ItemID, ICP.TransactionDate, SUM(ICP.Quantity) as ConsumedPrevBal FROM consumeditemmonitoring ICP WHERE DATE(ICP.TransactionDate) < CURDATE() GROUP BY ItemID) ICP ON ICP.ItemID = I.ItemID
LEFT JOIN (SELECT IDP.ItemID, IDP.ItemTransactionDate, SUM(IDP.Quantity) as DamagedPrevBal FROM damagedinventory IDP WHERE DATE(IDP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IDP ON IDP.ItemID = I.ItemID
LEFT JOIN (SELECT IUP.ItemID, IUP.ItemTransactionDate, SUM(IUP.Quantity) as UnaccountedPrevBal FROM unaccounteditems IUP WHERE DATE(IUP.ItemTransactionDate) < CURDATE() GROUP BY ItemID) IUP ON IUP.ItemID = I.ItemID
LEFT JOIN (SELECT IC.ItemID, IC.TransactionDate, SUM(IC.Quantity) as Consumed FROM consumeditemmonitoring IC WHERE DATE(IC.TransactionDate) = CURDATE() GROUP BY ItemID) IC ON IC.ItemID = I.ItemID
LEFT JOIN (SELECT ID.ItemID, ID.ItemTransactionDate, SUM(ID.Quantity) as Damaged FROM damagedinventory ID WHERE DATE(ID.ItemTransactionDate) = CURDATE() GROUP BY ItemID) ID ON ID.ItemID = I.ItemID
LEFT JOIN (SELECT IU.ItemID, IU.ItemTransactionDate, SUM(IU.Quantity) as Unaccounted FROM unaccounteditems IU WHERE DATE(IU.ItemTransactionDate) = CURDATE() GROUP BY ItemID) IU ON IU.ItemID = I.ItemID
ORDER BY I.Item ASC

Cartesian Join With Nonmatching Rows

I can't get my query to work when a LEFT join doesn't match. I think this is related to the cartesian join. Here's a query that works:
select i.id, z.WeekNum, 0, count(s.id) from my156rowtable as i
left JOIN (select YEARWEEK( #startDate ) WeekNum, #startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from
(select #startDate := '2016-04-20') sqlv,
my156rowtable
limit 156 ) z
on 1=1
where i.id = 2 and s.id = 0
group by z.WeekNum, s.id
Which brings back:
| 2 | 201836 | 0 | 1 |
| 2 | 201837 | 0 | 1 |
| 2 | 201838 | 0 | 1 |
| 2 | 201839 | 0 | 1 |
| 2 | 201840 | 0 | 1 |
| 2 | 201841 | 0 | 1 |
but with my addition of stats_to_my156rowtable I get rows 201838 and 201839 missing.
select i.id, z.WeekNum, 0, count(s.id) from my156rowtable as i
left JOIN (select YEARWEEK( #startDate ) WeekNum, #startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from
(select #startDate := '2016-04-20') sqlv,
my156rowtable
limit 156 ) z
on 1=1
left join stats_to_my156rowtable as s
on i.id = s.sid and z.WeekNum = YearWeek(s.date)
where i.id = 2 and s.id = 0
group by z.WeekNum, s.id
returns:
| 2 | 201836 | 0 | 8 |
| 2 | 201837 | 0 | 14 |
| 2 | 201840 | 0 | 9 |
if there is no match I'd like the count to be 0. I figured I could this with a case or coalesce later but with no return I'm not finding much luck.
Your condition on the right table in the WHERE clause turn your LEFT JOIN into INNER JOIN. Basically, it filter out rows that doesn't have id = 0, including your NULL rows. Probably, moving the condition to the ON clause will solve your problem.
select i.id, z.WeekNum, 0, count(s.id)
from my156rowtable as i
left JOIN (
select YEARWEEK( #startDate ) WeekNum, #startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from (
select #startDate := '2016-04-20'
) sqlv, my156rowtable
limit 156
) z on 1=1
left join stats_to_my156rowtable as s on i.id = s.sid and z.WeekNum = YearWeek(s.date) AND s.id = 0
where i.id = 2
group by z.WeekNum, s.id

How to get the result in single row when using sub queries in SQL Server 2008?

I am trying to get a table with stage_name and its count in respective loan product. Like in below example stage_name is RCO and there are three loan product, Auto loan, Consumer loan and Credit card. Though I have used the logic and getting the right output, but in the output, I am getting the separate row for each stage_name and loan product case. I want only one row with all the three result. Please look at my code below, actual output and desired output:
SELECT
'RCO',
CASE
WHEN sq2.loan_type = 'Consumer loan'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Consumer_Loan,
CASE
WHEN sq2.loan_type = 'Auto Loan'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Auto_Loan,
CASE
WHEN sq2.loan_type = 'Credit Card'
THEN SUM(ISNULL(sq2.user_count, 0))
END AS Credit_Card
FROM
(SELECT
'RC0' AS ws_name, 'Consumer loan' AS loan_type,
COUNT(DISTINCT a.bpm_referenceno) AS user_count,
a.takenby AS user_id
FROM
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM
BM_RLOS_EXTTABLE m
WHERE
m.loan_type = 'Consumer Loan') sq1 ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE
a.winame='RCO'
GROUP BY
a.takenby
UNION
SELECT 'RC0','Auto loan',
count (DISTINCT a.bpm_referenceno), a.takenby
from
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM BM_RLOS_EXTTABLE m
WHERE m.loan_type='Auto Loan')sq1
ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE a.winame='RCO'
GROUP BY a.takenby
UNION
SELECT 'RC0','Credit Card',
count (DISTINCT a.bpm_referenceno), a.takenby
from
BM_RLOS_DecisionHistoryForm a
INNER JOIN
(SELECT
m.bpm_referenceno
FROM BM_RLOS_EXTTABLE m
WHERE m.loan_type='Credit Card')sq1
ON a.bpm_referenceno = sq1.bpm_referenceno
WHERE a.winame='RCO'
GROUP BY a.takenby) sq2
GROUP BY sq2.ws_name,sq2.loan_type
Actual output:
|--------------|-------------|-------------|-------------|
| Stg_nm | Cons_ln | Auto_lan | Credit_card |
|--------------|-------------|-------------|-------------|
| RCO | NULL | NULL | 8 |
|--------------|-------------|-------------|-------------|
| RCO | NULL | 55 | NULL |
|--------------|-------------|-------------|-------------|
| RCO | 81 | NULL | NULL |
|--------------|-------------|-------------|-------------|
Required Output
|--------------|-------------|-------------|-------------|
| Stg_nm | Cons_ln | Auto_lan | Credit_card |
|--------------|-------------|-------------|-------------|
| RCO | 81 | 55 | 8 |
|--------------|-------------|-------------|-------------|
The top half should be like this - reverse the usage of SUM and CASE, and remove the last GROUP BY altogether
SELECT
'RCO',
SUM(CASE
WHEN sq2.loan_type = 'Consumer loan'
THEN sq2.user_count
ELSE 0 END
)
AS Consumer_Loan,
SUM(CASE
WHEN sq2.loan_type = 'Auto loan'
THEN sq2.user_count
ELSE 0 END
)
AS Auto_Loan,
SUM(CASE
WHEN sq2.loan_type = 'Credit Card'
THEN sq2.user_count
ELSE 0 END
)
AS Credit_Card
FROM
<your existing query, with the final GROUP BY removed>
But you need to remove the GROUP BY from the end altogether

Return a ranking from multiple table with mySQL

Here my table structure:
___Lang:
|--------|------------|
| LAN_Id | LAN_En |
|--------|------------|
| DI | Direct |
| WE | Web |
| OT | Other |
|--------|------------|
___Segmentations:
|--------|------------|
| SEG_Id | SEG_Code |
|--------|------------|
| 1 | DI |
| 2 | WE |
| 3 | OT |
|--------|------------|
___Bookings:
|--------|------------------|
| BOO_Id | BOO_Segmentation |
|--------|------------------|
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
|--------|------------------|
___BillableDatas:
|--------|---------------|------------|------------|
| BIL_Id | BIL_BookingId | BIL_Date | BIL_Item |
|--------|---------------|------------|------------|
| 1 | 1 | 2017-02-21 | Night |
| 2 | 1 | 2017-02-22 | Night |
| 3 | 1 | 2017-02-23 | Night |
| 4 | 1 | 2017-02-24 | Night |
| 5 | 2 | 2017-02-25 | Night |
| 6 | 2 | 2017-02-26 | Night |
| 7 | 3 | 2017-02-28 | Night |
| 8 | 3 | 2017-03-01 | Night |
| 9 | 3 | 2017-03-02 | Night |
| 10 | 3 | 2017-03-03 | Night |
|--------|---------------|------------|------------|
I would like to know the most popular segmentation for a range of date.
The desired result should be this one for the following date range :
Form 2017-02-01 to 2017-02-28 inclusive
|------------|------------|------------|--------------|------------|
| ROO_Name | Night_Nb | Percentage | Booking_Nb | Percentage |
|------------|------------|------------|--------------|------------|
| Direct | 6 | 85.71 | 2 | 66.66 |
| Website | 1 | 14.28 | 1 | 33.33 |
| Other | 0 | 0 | 0 | 0 |
|------------|------------|------------|--------------|------------|
What I already tried:
SELECT r.SEG_Id
, Sum(CASE WHEN BOO_Id IS NULL THEN 0 ELSE 1 END) Night_Nb
, Concat(
Format(
Sum(CASE WHEN BOO_Id IS NULL THEN 0 ELSE 1 END)
/ TotalBookings
* 100
, 0) ) AS PercentageTotal
FROM ( ___Segmentations r LEFT JOIN ___Bookings b ON r.SEG_Id = b.BOO_Segmentation
) INNER JOIN (SELECT BOO_HotelId
, Count(*) AS TotalBookings
FROM ___Bookings
GROUP BY BOO_HotelId
) AS TotalHotelBookings
ON r.SEG_HotelId = TotalHotelBookings.BOO_HotelId
WHERE r.SEG_HotelId = :hotel_id
GROUP BY r.SEG_Id
ORDER BY NumBookings DESC
But it doesn't work actually.
Could anyone help me with this please ?
You could use the SQL Fiddle:
http://sqlfiddle.com/#!9/1aa10a
I suggest we build the query incrementally, step by step. Verify that the query results are as we expect at each step. When something "doesn't work", backup a step.
We want to return three rows, one for each row in ___Segmentations, for a specific hotelid
SELECT r.seg_id
, r.seg_text
FROM ___Segmentations r
WHERE r.seg_hotelid = :hotel_id
ORDER BY r.seg_id
Add the outer join to __Bookings
SELECT r.seg_id
, r.seg_text
, b.boo_id
FROM ___Segmentations r
LEFT
JOIN ___Bookings b
ON b.boo_segmentation = r.seg_id
WHERE r.seg_hotelid = :hotel_id
ORDER
BY r.seg_id
, b.boo_id
Add the outer join to ___BillableDatas
SELECT r.seg_id
, r.seg_text
, b.boo_id
, d.bil_id
FROM ___Segmentations r
LEFT
JOIN ___Bookings b
ON b.boo_segmentation = r.seg_id
LEFT
JOIN `___BillableDatas` d
ON d.bil_bookingid = b.boo_id
WHERE r.seg_hotelid = :hotel_id
ORDER
BY r.seg_id
, b.boo_id
, d.bil_id
If that's the rows we're interested in, we can work on aggregation.
SELECT r.seg_id
, r.seg_text
, COUNT(DISTINCT b.boo_id) AS cnt_bookings
, COUNT(DISTINCT d.bil_id) AS cnt_billable
FROM ___Segmentations r
LEFT
JOIN ___Bookings b
ON b.boo_segmentation = r.seg_id
LEFT
JOIN `___BillableDatas` d
ON d.bil_bookingid = b.boo_id
WHERE r.seg_hotelid = :hotel_id
GROUP
BY r.seg_id
, r.seg_text
ORDER
BY r.seg_text
Now to get the aggregation with the "total".
The approach I would take would be to make "copies" of the rows, using a CROSS JOIN operation. We can do the join to the rows returned by the very first query we wrote, referenced as an inline view. (Aliased as q below.)
If we have a complete set of rows, repeated for each seg_id/seg_text (that first query we wrote), we can use conditional aggregation.
That last query we wrote (just above) is an inline view in the query below, aliased as c.
SUM of cnt_bookings from all the rows is the total.
For the individual counts, we can include only the rows that have a matching seg_id, a total of that subset.
SELECT q.seg_id
, q.seg_text
, SUM(IF(c.seg_id=q.seg_id,c.cnt_bookings,0)) AS cnt_bookings
, SUM(c.cnt_bookings) AS tot_bookings
, SUM(IF(c.seg_id=q.seg_id,c.cnt_billable,0)) AS cnt_billable
, SUM(c.cnt_billable) AS tot_billable
FROM ( SELECT t.seg_id
, t.seg_text
FROM ___Segmentations t
WHERE t.seg_hotelid = :hotel_id_1
ORDER BY t.seg_id
) q
CROSS
JOIN ( SELECT r.seg_id
, COUNT(DISTINCT b.boo_id) AS cnt_bookings
, COUNT(DISTINCT d.bil_id) AS cnt_billable
FROM ___Segmentations r
LEFT
JOIN ___Bookings b
ON b.boo_segmentation = r.seg_id
LEFT
JOIN `___BillableDatas` d
ON d.bil_bookingid = b.boo_id
WHERE r.seg_hotelid = :hotel_id
GROUP
BY r.seg_id
) c
GROUP
BY q.seg_id
, q.seg_text
ORDER
BY q.seg_text
In the SELECT list, we can do the division to get the percentage: cnt_bookings * 100.0 / tot_bookings
e.g.
SELECT q.seg_id
, q.seg_text
, SUM(IF(c.seg_id=q.seg_id,c.cnt_bookings,0)) AS cnt_bookings
, SUM(c.cnt_bookings) AS tot_bookings
, SUM(IF(c.seg_id=q.seg_id,c.cnt_bookings,0))
* 100.0 / SUM(c.cnt_bookings) AS pct_bookings
, SUM(IF(c.seg_id=q.seg_id,c.cnt_billable,0)) AS cnt_billable
, SUM(c.cnt_billable) AS tot_billable
, SUM(IF(c.seg_id=q.seg_id,c.cnt_billable,0))
* 100.0 / SUM(c.cnt_billable) AS pct_billable
Modify the ORDER BY clause to return the rows in the order you want
Remove from the SELECT list the expressions that return tot_bookings and tot_billable.
EDIT
I think I missed the date critera. We can make the outer joins into inner joins, and replace the CROSS JOIN with a LEFT JOIN. We have potential to return NULL values for cnt_bookings and cnt_billable, we can wrap those in IFNULL() or COALESCE() function to replace NULL with zero.
SELECT q.seg_id
, q.seg_text
, SUM(IF(c.seg_id=q.seg_id,c.cnt_bookings,0)) AS cnt_bookings
, SUM(c.cnt_bookings) AS tot_bookings
, SUM(IF(c.seg_id=q.seg_id,c.cnt_bookings,0))
* 100.0 / SUM(c.cnt_bookings) AS pct_bookings
, SUM(IF(c.seg_id=q.seg_id,c.cnt_billable,0)) AS cnt_billable
, SUM(c.cnt_billable) AS tot_billable
, SUM(IF(c.seg_id=q.seg_id,c.cnt_billable,0))
* 100.0 / SUM(c.cnt_billable) AS pct_billable
FROM ( SELECT t.seg_id
, t.seg_text
FROM ___Segmentations t
WHERE t.seg_hotelid = :hotel_id_1
ORDER BY t.seg_id
) q
LEFT
JOIN ( SELECT r.seg_id
, COUNT(DISTINCT b.boo_id) AS cnt_bookings
, COUNT(DISTINCT d.bil_id) AS cnt_billable
FROM ___Segmentations r
JOIN ___Bookings b
ON b.boo_segmentation = r.seg_id
JOIN `___BillableDatas` d
ON d.bil_bookingid = b.boo_id
AND d.bil_date BETWEEN '2017-02-21' AND '2017-02-28'
WHERE r.seg_hotelid = :hotel_id
GROUP
BY r.seg_id
) c
ON 1=1
GROUP
BY q.seg_id
, q.seg_text
ORDER
BY q.seg_text
Not sure 100% how the hotelId column comes into play here, you didn't describe it in the question, but try this:
SELECT aaa.SEG_Text, aaa.Night_NB, aaa.Night_NB / totals.total_nights * 100, aaa.Booking_Nb , aaa.Booking_Nb / totals.total_bookings * 100 FROM (
SELECT s.SEG_Text, COUNT(DISTINCT d.BIL_Id) AS `Night_Nb`, COUNT(DISTINCT b.BOO_Id) AS `Booking_Nb` FROM ___Segmentations s
LEFT JOIN ___Bookings b ON s.SEG_Id = b.BOO_Segmentation
LEFT JOIN ___BillableDatas d ON d.BIL_BookingId = b.BOO_Id AND d.BIL_Date BETWEEN '2017-02-01' AND '2017-02-28'
GROUP BY s.SEG_Id ) AS `aaa`
, ( SELECT COUNT(*) AS `total_nights`, COUNT(DISTINCT BIL_BookingId) `total_bookings` FROM ___BillableDatas WHERE BIL_Date BETWEEN '2017-02-01' AND '2017-02-28') AS totals
It basically does the same stuff you did, but uses SELECT(DISTINCT ...) and thus is easier to understand, debug, and I think will also run faster.
For me it returns correct results.
The challenge here seems to be to avoid doing almost the same query twice (repeating the date condition) in order to calculate the two percentages.
You could use the with rollup modifier to generate the total counts which you need to calculate those percentages. You could then capture these totals in variables, and use them in a wrapping query as divisors. Finally, the outer query's where clause would eliminate the rollup record, as it has served its purpose:
select seg_text
, night_nb
, 100*night_nb/#sum_night_nb as night_pct
, booking_nb
, 100*booking_nb/#sum_booking_nb as booking_pct
from (
select seg_text
, #sum_night_nb := count(bil_id) night_nb
, #sum_booking_nb := count(distinct bil_bookingid) booking_nb
from ___segmentations seg
left join (___bookings boo
inner join ___billabledatas bil
on bil_bookingid = boo_id
and bil_hotelid = boo_hotelid)
on seg_id = boo_segmentation
and seg_hotelid = boo_hotelid
and bil_date between '2017-02-01' and '2017-02-28'
where seg_hotelid = 'AAA00'
group by seg_text with rollup
) base
where seg_text is not null
order by night_nb desc
See this sqlfiddle

mysql delete all records if if one of the records has a certain order_status_id

Here is a table structure:
order_id | order_status_id | ip | date_added
-----------------------------------------------
1 | 0 | 192.168.1.1 | 2016-12-07
2 | 0 | 192.168.1.1 | 2016-12-07
3 | 0 | 192.168.1.1 | 2016-12-07
4 | 0 | 192.168.1.1 | 2016-12-07
5 | 1 | 192.168.1.1 | 2016-12-07
I have a sql request which deletes rows with the lowest id values in my case we delete records with order_id = 1,2,3
delete n1 FROM `order` n1, `order` n2
WHERE n1.order_id > n2.order_id
AND n1.order_status_id = '0'
AND n2.order_status_id = '0'
AND n1.ip = n2.ip
AND Day(n1.date_added) = Day(n2.date_added)
But I want to delete all records with order_status_id = 0 (1,2,3,4) if one of the records (from same ip/date_added) has order_status_id = 1 (in my case because we have record order_id 5 with order_status_id = 1).
You can use join:
delete o
from orders o join
(select ip, date_added
from orders o
where order_status_id = 1
group by ip, date_added
) oo
on o.ip = oo.ip and o.date_added = oo.date_added
where o.order_status_id = 0;
Actually, still not known what you really want to do, but try this:
DELETE n1 FROM `order` n1
JOIN (
SELECT
MAX(`order_id`) AS `order_id`
FROM `order` o1
WHERE
(NOT EXISTS(SELECT 1 FROM `order` o2 WHERE o1.`ip` = o2.`ip` AND o1.`date_added` = o2.`date_added` AND o2.`order_status_id` = '1')
AND o1.`order_status_id` = '0')
OR
(EXISTS(SELECT 1 FROM `order` o2 WHERE o1.`ip` = o2.`ip` AND o1.`date_added` = o2.`date_added` AND o2.`order_status_id` = '1')
AND o1.`order_status_id` = '1')
GROUP BY `ip`, `date_added`
) n2
ON n1.`order_id` <> n2.`order_id`
SqlFiddle demo