MySQL - join same table twice - mysql

I have a table called 'booking'
> id status
1 P
2 P
and another called 'call'
id calldate type booking
1 01/01/2012 DEL 1
2 01/02/2012 COL 1
3 01/03/2012 DEL 2
4 31/12/2019 COL 999
I want to list each record in 'bookings' ONCE, showing associated records from 'call' as another column like this:
bookingId deliverydate collectiondate
1 01/01/2012 01/02/2012
2 01/03/2012 null
I've tried:
select `b`.`bookingid` AS `bookingid`,
`del`.`calldate` AS `Delivery`,
`col`.`calldate` AS `Collection`
from `booking` `b`
left join `call` `del` on `b`.`bookingid` = `del`.`booking`
left join `call` `col` on `b`.`bookingid` = `col`.`booking`
where ((`del`.`type` = 'DEL') OR (`col`.`type` = 'COL') and (`b`.`status` = 'P'));
but I get bookingid 1 listed 3 times. Can someone please fix my joins?

I think you want to move your types into the join conditions:
select `b`.`bookingid` AS `bookingid`,
`del`.`calldate` AS `Delivery`,
`col`.`calldate` AS `Collection`
from `booking` `b`
left join `call` `del` on `b`.`bookingid` = `del`.`booking` AND `del`.`type` = 'DEL'
left join `call` `col` on `b`.`bookingid` = `col`.`booking` AND `col`.`type` = 'COL'
where `b`.`status` = 'P';

You don't need to join the same table twice, you could use this:
SELECT
`call`.booking,
max(case when type='DEL' then calldate end) as deliverydate,
max(case when type='COL' then calldate end) as collectiondate
FROM
booking inner join `call`
on booking.id=`call`.booking
WHERE
booking.status='P'
GROUP BY `call`.booking

Related

How can i optimize this Mysql query? It has paging already ,but i didnt add here

I need your help to optimize the query ,I am using mysql mariadb.The job of the query is to get some datas for using ui.
I set to null some datas because of example. The query is:
select q.*,
(case when q.numberofcustomizations > 0 then 1 else 0 end) hascustomization,
concat_ws(' - ', q.productname, q.variant) productnamewithvariant
from (
select ca.lastname,
oh.orderid,
oh.orderno,
oh.datecreated,
p.productid, concat_ws(' ', b.name, p.model) as productname,
(case when pv.variantid is not null then concat_ws(' / ', pv.type1, pv.type2) end) variant,
ifnull(concat(' / ',ml.sku), case when pv.variantid is not null then ifnull(concat(' / ',pv.sku),concat(' / ',p.sku)) else concat(' / ',p.sku) end) as sku2,(case when pv.variantid is not null then ifnull(concat(' / ',pv.barcode),concat(' / ',p.barcode)) else concat(' / ',p.barcode) end) as barcode2,
ml.uuid, ml.merchantwarehouseid,
p.sku,
mo.lineid as merchantorderid,
mo.orderlineno,mo.pending,
ol.quantity, ol.comment,
ifnull((select sum(numberofitemshipped) from merchant_order_shipment mos where mos.merchantorderlineid=mo.lineid),0) as numberofitemshipped,
ifnull((select sum(numberofitemshipped) from merchant_order_shipment mos where mos.merchantorderlineid=mo.lineid and mos.`status`='delivered'),0) as numberofitemdelivered,
ol.price,
ol.bundlecode,
concat_ws(' ',bb.name, pb.model) as bundleproductname,
mo.statuscode,
os.isopenorder,
os.name as status, p.barcode,
p.manufactureritemcode,
r.fullsizeurl,
r.thumbnailsizeurl,
sa.postalcode as shipping_postalcode,
sa.countrycode as shipping_countrycode,
(select count(0) from order_line_property olp where olp.orderid=mo.orderid and olp.lineno=mo.orderlineno) numberofcustomizations,
(select count(0) from order_line_property olp where olp.orderid=mo.orderid and olp.lineno=mo.orderlineno and olp.value is null) numberofcustomizationrequests,
case when exists(select null from order_incident oi where oi.orderid=mo.orderid and oi.orderlineno=mo.orderlineno) then 1 else 0 end hasincident,
(ifnull((select sum(ols.price) from order_line ols where ols.orderid=oh.orderid and ols.feetypecode='shipment'),0) / (select count(distinct pm.merchantid) from order_line olm join product pm on pm.productid=olm.productid where olm.orderid=oh.orderid) ) as shippingfee,
(case when olg.giftfrom is not null then 1 else 0 end) hasgiftnote
from merchant_order mo
join merchant_listing ml on ml.merchantlistingid=mo.merchantlistingid
join order_header oh on oh.orderid=mo.orderid
join address sa on sa.addressid=oh.addressid
join order_line ol on ol.orderid=oh.orderid and ol.lineno=mo.orderlineno
join order_status os on os.statuscode=mo.statuscode and os.isvalidorder=1
join product p on p.productid=ol.productid
join customer c on c.customerid=oh.customerid
join address ca on ca.addressid=c.addressid
left outer join product_variant pv on pv.variantid=ml.variantid
left outer join brand b on b.brandid=p.brandid
left outer join product_resource pr on pr.productid=p.productid and pr.isdefault=1
left outer join resource r on r.resourceid=pr.resourceid
left outer join order_line_gift olg on olg.orderid=mo.orderid and olg.lineno=mo.orderlineno
left outer join category cat on cat.categoryid=p.categoryid
left outer join product pb on pb.bundlecode=ol.bundlecode
left outer join brand bb on bb.brandid=pb.brandid
join (select #categoryid=NULL, #brandid=NULL, #merchantwarehouseid=NULL, #hascustomization=NULL, #iscustomizationrequested=NULL, #hassuborder=NULL) params on 1=1
where
mo.statuscode=ifnull(NULL,mo.statuscode)
and oh.orderno=ifnull(NULL, oh.orderno)
and c.customerno=ifnull(NULL, c.customerno)
and ca.firstname=ifnull(NULL, ca.firstname)
and ca.lastname=ifnull(NULL, ca.lastname)
and ca.email=ifnull(NULL, ca.email)
and ml.productid=ifnull(NULL, ml.productid)
and (case when #categoryid is null then 1
when cat.categoryid=#categoryid then 1
when cat.overcategoryid=#categoryid then 1 end)
and (case when #brandid is null then 1
when p.brandid=#brandid and b.isactive=1 then 1
end)
and (case when #merchantwarehouseid is null then 1
when ml.merchantwarehouseid=#merchantwarehouseid then 1 end)
and concat(ifnull(oh.originref,'-'),' / ',ifnull(oh.originsource,'-'))=ifnull(NULL,concat(ifnull(oh.originref,'-'),' / ',ifnull(oh.originsource,'-')))
and mo.pending=ifnull(NULL, mo.pending)
and oh.datecreated between ifnull(NULL, oh.datecreated) and ifnull(NULL, oh.datecreated)
) q
where
(case when #hascustomization is null then 1
when q.numberofcustomizations > 0 and #hascustomization = 1 then 1
when q.numberofcustomizations = 0 and #hascustomization = 0 then 1
end)
and (case when #iscustomizationrequested is null then 1
when q.numberofcustomizationrequests > 0 and #iscustomizationrequested = 1 then 1
when q.numberofcustomizationrequests = 0 and #iscustomizationrequested = 0 then 1
end)
order by 1
Explain cost:
cost
I marked the problematic points in yellow.
Also I added new index for bundlecode,it fixed. But I dont know how to fix first two lines.
Thanks
Some problems!
(1) What is this?? and ca.firstname=ifnull(NULL, ca.firstname). If you don't need the test, build the query without the test.
(2) Avoid #Variables... SET #hascustomization=NULL then WHERE #hascustomization = ... will always fail. That is, NULL is not equal to either 0 or 1.
Don't try to fix that, instead get rid of #variables by constructing the query on the fly. This will [perhaps significantly] help the optimization of the query.
More specifically, get rid of
join
(
SELECT #categoryid=NULL, #brandid=NULL, #merchantwarehouseid=NULL,
#hascustomization=NULL, #iscustomizationrequested=NULL,
#hassuborder=NULL
and then simplify
(case when #hascustomization is null then 1 when q.numberofcustomizations > 0
and #hascustomization = 1 then 1 when q.numberofcustomizations = 0
and #hascustomization = 0 then 1 end
)
(etc)
(3) When you get to putting the pagination back in, rewrite the query. Having all the JOINs before doing the pagination is quite inefficient. Instead, do the minimal effort to find the next, say, 10 IDs, then do the JOINs to get the rest of the info -- this time looking up only 10 items for each JOIN.
(4) After all that, start a new Question showing the revised query; I will make suggestions of indexes (often 'composite') to further improve performance. Be sure to include SHOW CREATE TABLE and EXPLAIN SELECT ... in that Question.

Select 2 Column with Different Conditions

I need to create a query that give me completed and unpaid transaction in 2019 grouped weekly. I already create the query to generate the completed transaction, but I stuck when try to combine the unpaid transaction query into the completed transaction query
This is the query for completed transaction
SELECT WEEK(A.plat_create_time, 1) AS 'Week Create Time',
COUNT(t1.lp_sign_time) AS 'Completed Order'
FROM deli_order A
LEFT JOIN
(
SELECT order_code, code, lp_sign_time
FROM pg_send_package
UNION
SELECT D.order_code, D.oms_code, C.cm_sign_time
FROM pg_package C
INNER JOIN pg_order D ON C.pg_order_id = D.id
) t1 ON t1.order_code = A.order_code
AND t1.code = A.code
AND YEAR(A.plat_create_time) = 2019
WHERE (YEAR(A.plat_create_time) = 2019) AND A.status = 6 AND t1.lp_sign_time IS NOT NULL
GROUP BY WEEK(A.plat_create_time, 1);
And its generate something like this
Week | Completed Order
1 886
2 734
3 868
4 1000
And this is the query that I already try to generate both completed and unpaid transaction
SELECT WEEK(A.plat_create_time, 1) AS 'Week Create Time',
COUNT(t1.lp_sign_time) AS 'Completed Order',
COUNT(t2.plat_create_time) AS 'Unpaid Order'
FROM deli_order A
LEFT JOIN
(
SELECT order_code, code, lp_sign_time
FROM pg_send_package
UNION
SELECT D.order_code, D.oms_code, C.cm_sign_time
FROM pg_package C
INNER JOIN pg_order D ON C.pg_order_id = D.id
) t1 ON t1.order_code = A.order_code
AND t1.code = A.code
AND YEAR(A.plat_create_time) = 2019
LEFT JOIN
(
SELECT order_code, WEEK(plat_create_time,1) AS 'Create Time'
FROM deli_order
WHERE pay_state = 0 AND (YEAR(plat_create_time) = 2019)
) t2 ON t2.order_code = A.order_code
WHERE (YEAR(A.plat_create_time) = 2019) AND A.status = 6 AND t1.lp_sign_time IS NOT NULL
GROUP BY WEEK(A.plat_create_time, 1);
but when I execute it, MySQL always give error message 'Unknown column 't2.plat_create_time' in field list'. My expected result is something like this
Week | Completed Order | Unpaid Order
1 886 51
2 734 42
3 868 40
4 1000 31
What should I change in my query?
Change:
SELECT order_code, WEEK(plat_create_time,1) AS 'Create Time'
to:
SELECT order_code, WEEK(plat_create_time,1) AS 'Create Time', plat_create_time

CASE statement in SQL giving not proper values

I am having abnormal values when I run this part in my sql code. SQL syntax wise, everything is okay with this?
select
COUNT(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE NULL END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Overall Query is this. The result of successfulbillinghits should be equal to timesbilled
SELECT
cs.idCustomerSubscription,
cs.msisdn,
pro.name AS promoterName,
c.name AS ClubName,
c.idClub AS ClubID,
o.name AS operatorName,
o.idOperator AS OperatorID,
co.name AS country,
-- cu.customerSince AS CustomerSince,
cs.subscribeddate AS subscribeddate,
-- cs.subscriptionNotificationSent AS SubNotificationSent,
-- cs.eventId AS EventId,
cs.unsubscribeddate AS unsubscribeddate,
cs.firstBillingDate AS FirstBillingDate,
cs.lastBilledDate As LastBilledDate,
cs.lastAttemptDate AS LastAttemptDate,
-- smp.code AS packageName,
-- o.mfactor AS mmfactor,
-- cs.idSubscriptionSource AS SubscriptionChannel,
-- cs.idUnsubscribeSource AS UnsubscriptionChannel,
-- DATE(bt.creationDate) AS BillingCreationDate,
-- bt.price AS pricePerBilling,
-- cs.lastRetryDate As LastRetryDate,
-- cs.lastRenewalDate AS LastRenewalDate,
-- cs.isActive AS ActiveStatus,
-- COUNT(bt.idBillingTransaction) AS BillingAttempts,
curr.idcurreny_symbol AS CurrencyID,
curr.symbol AS currency,
date(bt.creationDate) AS BillingDate,
cs.lastBilledAmount As LastBilledAmount,
cs.timesbilled,
price,
-- sum(price),
-- revenueShareAmountLocal,
-- o.mfactor,
-- count(IFF (bt.idBillingStatus = 2,1,0)) as otherversion,
count(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
FROM
customersubscription cs
LEFT JOIN
billing_transaction bt
ON CONVERT(cs.msisdn USING latin1) = bt.msisdn
AND cs.idClub = bt.idClub
AND bt.creationDate BETWEEN cs.SubscribedDate AND COALESCE(cs.UnsubscribedDate, now())
INNER JOIN customer cu ON (cs.idCustomer = cu.idcustomer)
INNER JOIN operator o ON (o.idoperator = cu.idoperator)
INNER JOIN country co ON (co.`idCountry` = o.idCountry)
INNER JOIN curreny_symbol curr ON (curr.idcurreny_symbol = co.idCurrencySymbol)
LEFT JOIN Promoter pro ON cs.idPromoter = pro.id
INNER JOIN club_operator_relationships cor ON cor.clubId = cs.idClub
INNER JOIN club c ON c.idClub = cs.idClub
-- INNER JOIN operator op ON op.idOperator = cu.idOperator
WHERE
-- (cs.timesbilled > 0 and cs.subscribeddate < '2016-09-01 00:00:00' )
cs.subscribeddate between '2017-04-20 00:00:00' and '2017-04-21 00:00:00'
AND cs.idClub IN (39)
GROUP BY idCustomerSubscription, ClubName, operatorName, promoterName
Successfulbillinghits is much greater than timesbilled in the result
Instead of COUNTuse SUM, as count counts blanks or nulls also
select
SUM(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Instead of using CASE, you can use WHERE clause with these aggregate functions, e.g.:
SELECT COUNT(*) as `successfulbillinghits`, SUM(price) as `old_revenue`
FROM table bt
WHERE bt.idBillingStatus = 2;

How to split SQL query results into columns based on two WHERE conditions and two calculated COUNT fields?

I have the following (simplified) database schema:
Persons:
[Id] [Name]
-------------------
1 'Peter'
2 'John'
3 'Anna'
Items:
[Id] [ItemName] [ItemStatus]
-------------------
10 'Cake' 1
20 'Dog' 2
ItemDocuments:
[Id] [ItemId] [DocumentName] [Date]
-------------------
101 10 'CakeDocument1' '2016-01-01 00:00:00'
201 20 'DogDocument1' '2016-02-02 00:00:00'
301 10 'CakeDocument2' '2016-03-03 00:00:00'
401 20 'DogDocument2' '2016-04-04 00:00:00'
DocumentProcessors:
[PersonId] [DocumentId]
-------------------
1 101
1 201
2 301
I have also set up an SQL fiddle to play with: http://www.sqlfiddle.com/#!3/e6082
The relation logic is the following: every Person can work on zero or infinite number of ItemDocuments (many-to-many); each ItemDocument belongs to exactly one Item (one-to-many). Item has status 1 - Active, 2 - Closed
What I need is a report that fulfills the following requirements:
for each person in Persons table, display count of Items that have ItemDocuments related to this person
the counts should be split in two columns by ItemStatus
the query should be filterable by two optional date periods (using two BETWEEN conditions on ItemDocuments.Date field) and the Item counts should also be split into two periods
if a Person does not have any ItemDocuments assigned, it still should be shown in the results with all count values set to 0
if a Person has more than one ItemDocument for an Item, the Item still should be counted only once
Essentially, here is how the results should look like if I use both periods to NULL (to read all the data):
[PersonName] [Active Items for period 1] [Closed Items for period 1] [Active Items for period 2] [Closed Items for period 2]
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'Peter' 1 1 1 1
'John' 1 0 1 0
'Anna' 0 0 0 0
While I can create an SQL query for each requirement separately, I have a problem to understand how to combine all of them together into one.
For example, I can split ItemStatus counts in two columns using
COUNT(CASE WHEN t.ItemStatus = 1 THEN 1 ELSE NULL END) AS Active,
COUNT(CASE WHEN t.ItemStatus = 2 THEN 1 ELSE NULL END) AS Closed
and I can filter by two periods (with max/min date constants from MS SQL server specification to avoid NULLs for optional period dates) using
between coalesce(#start1, '1753-01-01') and coalesce(#end1, '9999-12-31')
between coalesce(#start2, '1753-01-01') and coalesce(#end2, '9999-12-31')
but how to combine all of this together, considering also JOINs between tables?
Is there any technique, join or MS SQL Server specific approach to do this in efficient way?
My first attempt seems to work as required but it looks like ugly subquery duplications multiple times:
DECLARE #start1 DATETIME, #start2 DATETIME, #end1 DATETIME, #end2 DATETIME
-- SET #start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
I'm not absolutely sure if I really got what you want, but you might try this
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person

MySQL query slow in Where MONTH(datetime)

I am trying to add index in datetime, but the result still same.
SELECT s.id, s.player,
COUNT(case when dg.winner = 1 AND dp.colour <= 5 then 1 when dg.winner = 2 AND dp.colour > 5 then 1 else null end) as totalwin,
COUNT(case when dg.winner = 2 AND dp.colour <= 5 then 1 when dg.winner = 1 AND dp.colour > 5 then 1 else null end) as totallose,
COUNT(dg.winner) as totalgames
FROM dotaplayers AS dp
LEFT JOIN gameplayers AS gp ON gp.gameid = dp.gameid and dp.colour = gp.colour
LEFT JOIN stats AS s ON s.player_lower = gp.name
LEFT JOIN dotagames AS dg ON dg.gameid = dp.gameid
LEFT JOIN games AS g ON g.id = dp.gameid
LEFT JOIN bans as b ON b.name=gp.name
WHERE MONTH(g.datetime) = 4
GROUP by gp.name
ORDER BY totalwin DESC LIMIT 0,10
Showing rows 0 - 9 (10 total, Query took 7.7552 seconds.)
I want order the most winner in 4th month (April). Then it shows id, username, totalwins, totallose, totaldraw, totalgames. The case in my query is the how to get that. The result is correct, but slow.
Assuming g.datetime is indexed, try this instead:
WHERE g.`datetime` BETWEEN 20150401 AND 20150430`
Using the MONTH function, or any other function, on the field data in the WHERE eliminates the benefits of any indexes you might have on those fields; this results in the query requiring a full scan of the values in the table.
Rearranging the order of JOINs will probably help as well:
SELECT s.id, s.player
, SUM(case
when dg.winner = 1 AND dp.colour <= 5 then 1
when dg.winner = 2 AND dp.colour > 5 then 1
else 0
end
) as totalwin
, SUM(case
when dg.winner = 2 AND dp.colour <= 5 then 1
when dg.winner = 1 AND dp.colour > 5 then 1
else 0
end
) as totallose
, COUNT(dg.winner) as totalgames -- Not, sure of the nature of dg.`winner`, a SUM might be more appropriate here as well.
FROM games AS g
INNER JOIN dotaplayers AS dp ON g.id = dp.gameid
LEFT JOIN gameplayers AS gp ON gp.gameid = dp.gameid and dp.colour = gp.colour
LEFT JOIN stats AS s ON s.player_lower = gp.name
LEFT JOIN dotagames AS dg ON dg.gameid = dp.gameid
LEFT JOIN bans as b ON b.name=gp.name
WHERE g.`datetime` BETWEEN 20150401000000 AND 20150430235959
GROUP by gp.name
ORDER BY totalwin DESC
LIMIT 0,10
;
Another thing to note: Depending on the relationship between tables, some of the intermediate joins may result in effectively multiplying the resulting totals; this can be resolved by doing the sums in subqueries and joining those instead.