How to minimize sql query - mysql

I have a booking table in MySQL database where I need to get 3 data.
1) Total Booking
2) Pending Booking ( where is_confirm = 1 )
3) Complete Booking ( where is_confirm = 0 )
Now, I am writing 3 separate query to get this but how can I get it using 1 query?
current query:
$booking = new Admin;
$booking->rowQuery("SELECT count(bid) AS totalBooking FROM booking");
$bookingData = $booking->result->fetch_assoc();
$totalBooking = $bookingData['totalBooking'];
$booking->rowQuery("SELECT bid FROM booking WHERE is_confirm = 1 ");
$completeBooking = $booking->rows;
$booking->rowQuery("SELECT bid FROM booking WHERE is_confirm = 0 ");
$pendingBooking = $booking->rows;

you can use case sql . Try this:
SELECT
COUNT(CASE WHEN is_confirm = 1 THEN 1 END) AS confirmCount,
COUNT(CASE WHEN is_confirm = 0 THEN 1 END) AS noconfirmCount,
COUNT(*) AS total
FROM booking;

SELECT count(bid) AS totalBooking FROM booking
UNION
SELECT bid FROM booking
WHERE is_confirm=1
UNION
SELECT bid FROM booking
WHERE is_confirm=0

Aggregate functions usually skip nulls, including group_concat. If you don't mind explodeing the result string later, you could use case expressions to get the total bookings and coma-delimited strings for the IDs:
SELECT COUNT(bid) AS totalBooking,
GROUP_CONCAT(CASE is_confirm WHEN 1 THEN bid END) AS pendingBookings,
GROUP_CONCAT(CASE is_confirm WHEN 0 THEN bid END) AS completeBookings
FROM booking

Related

View doesn't show same result as command with subquery on mariadb

Dears,
I tried to create a view that sumarize transaction per category (row) and months (columns) on one year as this :
SELECT counterpart.id_category as catid, category AS Category, (
SELECT COALESCE(SUM(amount), 0)
FROM transaction
INNER JOIN counterpart
ON transaction.id_counterpart = counterpart.id_counterpart
WHERE YEAR(date) = YEAR(now())
AND MONTH(date) = 1
AND id_category = catid
) AS Jan, [...]
FROM transaction
INNER JOIN counterpart
ON transaction.id_counterpart = counterpart.id_counterpart
INNER JOIN category
ON counterpart.id_category = category.id_category
WHERE transaction.id_user = 2
AND YEAR(date) = YEAR(now())
GROUP BY category
I don't know if it's a good way or not, at least it works as single command but not as view.
On view, the WHERE id_category = catid for subquery doesn't work properly.
Thank you.
I suspect conditional aggregation would be more appropriate and quicker than a lot of sub queries.
for example
SELECT counterpart.id_category as catid, category AS Category,
sum(case when month(date) = 1 then 1 else 0 end) as Jan,
sum(case when month(date) = 2 then 1 else 0 end) as feb,
sum(case when month(date) = 3 then 1 else 0 end) as Mar
FROM transaction
INNER JOIN counterpart
ON transaction.id_counterpart = counterpart.id_counterpart
INNER JOIN category
ON counterpart.id_category = category.id_category
WHERE transaction.id_user = 2
AND YEAR(date) = YEAR(now())
GROUP BY counterpart.id_category,category

Sum on same column with two different conditions mysql

I have a table named Order with schema as
user_id, state amount
11 success 100
11 FAILED 10
11 FAILED 10
11 success 17
state can have two values (Success/Failed).
I want to fetch sum(amount) when state = "SUCCESS" - sum(amount) when state = "FAILED"
means difference total amount when success - total amount when failed.
I can solve this problem in 2 queries.
A = select id, sum(amount) when state = "SUCCESS"
B = select id, sum(amount) when state = "FAILED"
And solution will be A-B.
Is there any way I can achieve this in single sql query?
use case when
select user_id,sum(case when state = 'SUCCESS' then amount else 0 end)-sum(case when state = 'FAILED' then amount else 0 end)
from table group by user_id
Use conditional aggregation:
select id,
sum(case when state = 'SUCCESS' then amount else - amount end) as total
from t
where state in ('SUCCESS', 'FAILED')
group by id;
I assume that you want this sum per id and not overall in the table.
select sum(case when state = "SUCCESS" then amount else o end) -
sum(case when state = "FAILED" then amount else o end)
from tbl
group by userid

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

sql group by date gives same value everyday

So I want to group by days for statistics so I have data on each day (type 1 is bought and 0 is sold) but the query gives me the same result everyday and that is not correct can someone help me with this code?
SELECT
DATE(from_unixtime(credit_transaction_time)) AS data_date,
total_spend AS credits_spend,
total_bought AS credits_bought
FROM credit_transactions
JOIN (SELECT SUM(`credit_transaction_amount`) AS total_spend FROM credit_transactions WHERE `credit_transaction_type` = 0 GROUP BY DATE(from_unixtime(credit_transaction_time))) AS spend
JOIN (SELECT SUM(`credit_transaction_amount`) AS total_bought FROM credit_transactions WHERE `credit_transaction_type` = 1 GROUP BY DATE(from_unixtime(credit_transaction_time))) AS bought
GROUP BY DATE(from_unixtime(credit_transaction_time))
Use conditional aggregation:
SELECT DATE(from_unixtime(credit_transaction_time)) AS data_date,
SUM(CASE WHEN credit_transaction_type = 0 THEN credit_transaction_amount ELSE 0 END) as credits_spend,
SUM(CASE WHEN credit_transaction_type = 1 THEN credit_transaction_amount ELSE 0 END) as credits_bought
FROM credit_transactions
GROUP BY DATE(from_unixtime(credit_transaction_time));
Your query doesn't work because you don't have an ON condition. In most databases, this would result in a syntax error, but MySQL allows this syntax.

MySQL select subqueries

This is what I have at the moment.
$db =& JFactory::getDBO();
$query = $db->getQuery(true);
$query->select('`#__catalog_commit`.`id` as id, `#__catalog_commit`.`date` as date, COUNT(`#__catalog_commit_message`.`commit_id`) as count,
(SELECT COUNT(`#__catalog_commit_message`.`type`) as count_notice FROM `#__catalog_commit_message` WHERE `#__catalog_commit_message`.`type` = 1 GROUP BY `#__catalog_commit_message`.`type`) as count_notice,
(SELECT COUNT(`#__catalog_commit_message`.`type`) as count_warning FROM `#__catalog_commit_message` WHERE `#__catalog_commit_message`.`type` = 2 GROUP BY `#__catalog_commit_message`.`type`) as count_warning,
(SELECT COUNT(`#__catalog_commit_message`.`type`) as count_error FROM `#__catalog_commit_message` WHERE `#__catalog_commit_message`.`type` = 3 GROUP BY `#__catalog_commit_message`.`type`) as count_error');
$query->from('#__catalog_commit_message');
$query->leftjoin('`#__catalog_commit` ON `#__catalog_commit`.`id` = `#__catalog_commit_message`.`commit_id`');
$query->group('`#__catalog_commit_message`.`commit_id`');
$query->order('`#__catalog_commit`.`id` DESC');
What I have is 2 tables with the following structures:
catalog_commit
==============
id
date
catalog_commit_message
======================
id
commit_id
type
message
Basically I want to have the count of each different types of messages per group items. In what I have it actually select every rows (Which is normal) but I'm looking for a way (nicier if possible) to have the count per messages type within the query.
EDIT: Just wanted to add that it's a JModelList.
From what I gather, this should be your query:
SELECT c.id
,c.date
,count(cm.commit_id) as ct_total
,sum(CASE WHEN cm.type = 1 THEN 1 ELSE 0 END) AS count_notice
,sum(CASE WHEN cm.type = 2 THEN 1 ELSE 0 END) AS count_warning
,sum(CASE WHEN cm.type = 3 THEN 1 ELSE 0 END) AS count_error
FROM catalog_commit c
LEFT JOIN catalog_commit_message cm ON cm.commit_id = c.id
GROUP BY c.id, c.date
ORDER BY c.id DESC
You had the order of your tables reversed in the LEFT JOIN. Also, you had weird subqueries in the SELECT list.