How can I count rows based on a association with a third row?
In the example below I get the same result for both counts which is not the amount of "cwt_packages " nor the "cwt_products " table.
Find below the query I wrote
SELECT *, COUNT(p1.pkg_id) pkg_count, COUNT(p2.prd_id) prd_count FROM cwt_companies
LEFT JOIN cwt_packages p1 ON p1.pkg_ins_cmp = ic_id
LEFT JOIN cwt_products p2 ON p2.prd_pkg = p1.pkg_id AND p1.pkg_ins_cmp = ic_id
It's a bit hard to write a query, without knowing the structure of the tables, but something like this shoud work:
select *, count( pkg1.pkg_id ) as pkg_count,
( select count( prd.prd_id ) as prd_count
from cwt_companies c2
left join cwt_packages pkg2 on pkg2.pkg_ins_cmp = c2.ic_id
left join cwt_products prd on prd.prd_pkg = pkg2.pkg_id
where c1.ic_id = c2.ic_id ) as prd_count
from cwt_companies c1
left join cwt_packages pkg1 on pkg1.pkg_ins_cmp = c1.ic_id
group by c1.ic_id;
Related
I have a SQL query with groupby clause
the query looks like this:
SELECT
products.product_name AS product_name,
contracts_balance.contract_prod AS contract_prod,
SUM( contracts.opt_one_firm + contracts.opt_two_firm + contracts.opt_three_firm +contracts.opt_four_firm +contracts.opt_five_firm +contracts.opt_six_firm)
AS total_open_balance
FROM
(
(
contracts_balance
LEFT JOIN products ON
(
(
contracts_balance.product_id_fk = products.product_id
)
)
)
LEFT JOIN supplier ON
(
(
products.supplier_id_fk = supplier.supplier_id
)
)
LEFT JOIN contracts ON
(
(
contracts_balance.contract_id_fk = contracts.contract_id
)
)
)
GROUP BY
products.product_name , products.pack_size
The output looks like this:
In the group by query I want the query to group by names ignoring the text inside brackets ()
So, It should return only 8 rows.
Thank you
You can use substring_index():
SELECT SUBSTRING_INDEX(p.product_name, ' (', 1) AS product_name,
cb.contract_prod AS contract_prod,
SUM( c.opt_one_firm + c.opt_two_firm + c.opt_three_firm + c.opt_four_firm + c.opt_five_firm + contracts.opt_six_firm) AS total_open_balance
FROM contracts_balance cb LEFT JOIN
products p
ON cb.product_id_fk = p.product_id LEFT JOIN
supplier s
ON p.supplier_id_fk = s.supplier_id LEFT JOIN
contracts c
ON cb.contract_id_fk = c.contract_id
GROUP BY SUBSTRING_INDEX(p.product_name, ' (', 1), cb.contract_prod;
Notes:
Table aliases make the query easier to write and read.
The unaggregated columns in the SELECT should match the GROUP BY keys. I don't understand why cb.contract_prod is in the SELECT, but not the GROUP BY and why p.pack_size is in the GROUP BY but not the SELECT.
All those parentheses and white space just makes the query look more complicated than it is.
It is also irregular to use LEFT JOINs in a query and to aggregate by a column that is not in the first table -- because it would have NULL values on non-matching rows. I suspect you want products as the first table reference in the FROM.
I have a table structure like this
Table Structure
Here I want to select all the data from bus_routines table with bus details and avaliable_seat which is calculated from buses.number_of_seat - reserved_seats.number_of_reserved_seat - booking.number_of_seat even if the data is not present in booking table and reserved_seats table too where the bus_routines.sector_from=Ktm ,bus_routines.sector_to=Pkr and bus_routines.date=2015-12-15
Relation between them are :
buses and bus_routines --> one to many bus_routines and booking -->
one to many bus_routines and reserved_seats --> one to many
I have tried the following query
SELECT r.* , b.* ,
(
SELECT b.number_of_seat - sum(booking.number_of_seat)-sum(reserved_seats.number_of_reserved_seat)
FROM bus_routines AS r
INNER JOIN buses AS b
ON b.id = r.bus_id
INNER JOIN
(SELECT number_of_seat , bus_routine_id FROM booking GROUP BY booking.bus_routine_id) AS booking
ON booking.bus_routine_id = r.id
INNER JOIN (SELECT number_of_reserved_seat , routine_id FROM reserved_seats GROUP BY reserved_seats.routine_id) AS reserved_seats
ON r.id = reserved_seats.routine_id
WHERE
r.sector_from = "KTM" AND
r.sector_to = "PKR" AND
r.departure_date = "2015-12-15"
) AS avaliable_seat
FROM bus_routines AS r
INNER JOIN buses AS b
ON b.id = r.bus_id
WHERE
r.sector_from = "KTM" AND
r.sector_to = "PKR" AND
r.departure_date = "2015-12-15"
HAVING avaliable_seat > 0
I get the result what I want but the avaliable_seat is same for all the row
I have tried another query too but it give me the single result
SELECT r.* , b.* , b.number_of_seat - sum(booking.number_of_seat)-sum(reserved_seats.number_of_reserved_seat) AS available_seat
FROM bus_routines AS r
INNER JOIN buses AS b
ON b.id = r.bus_id
INNER JOIN
(SELECT number_of_seat , bus_routine_id FROM booking GROUP BY booking.bus_routine_id) AS booking
ON booking.bus_routine_id = r.id
INNER JOIN
(SELECT number_of_reserved_seat , routine_id FROM reserved_seats GROUP BY reserved_seats.routine_id) AS reserved_seats
ON r.id = reserved_seats.routine_id
WHERE
r.sector_from = "KTM" AND
r.sector_to = "PKR" AND
r.departure_date = "2015-12-15"
HAVING available_seat > 0
I also tried another query and it give me Subquery returns more than 1 row . The query is
SELECT r.* , b.* ,
b.number_of_seat - (SELECT sum(number_of_seat) FROM booking GROUP BY booking.bus_routine_id)
- (SELECT sum(number_of_reserved_seat) FROM reserved_seats GROUP BY reserved_seats.routine_id) AS available_seat
FROM bus_routines AS r
INNER JOIN buses AS b
ON b.id = r.bus_id
WHERE
r.sector_from = "KTM" AND
r.sector_to = "PKR" AND
r.departure_date = "2015-12-15"
HAVING available_seat > 0
One approach is to use correlated subqueries to get the reserved_seats and booked_seats for each bus_routine.
Let's assume that this query returns the rows you are wanting to return, it's just missing the available_seat column you want calculated:
SELECT r.*
, b.*
FROM bus_routines r
JOIN buses b
ON b.id = r.bus_id
WHERE r.sector_from = 'KTM'
AND r.sector_to = 'PKR'
AND r.departure_date = '2015-12-15'
To number of "reserved seats" for a given bus_routine, you can query the reserved_seats table, like this:
SELECT IFNULL(SUM(s.number_of_reserved_seat),0)
FROM reserved_seats s
WHERE s.routine_id = '649'
And the number of "booked seats" for a given bus_routine can be returned from the booking table, like this:
SELECT IFNULL(SUM(k.number_of_seat),0)
FROM booking k
WHERE k.bus_routine_id = '649'
We can incorporate the queries to get "reserved seats" and "booked seats" into the first query, as correlated subqueries. In place of the literal '649', with a reference the id from the bus_routine table.
SELECT r.*
, b.*
-- number of booked seats
, ( SELECT IFNULL(SUM(k.number_of_seat),0)
FROM booking k
WHERE k.bus_routine_id = r.id
) AS booked_seats
-- number of reserved seats
, ( SELECT IFNULL(SUM(s.number_of_reserved_seat),0)
FROM reserved_seats s
WHERE s.routine_id = r.id
) AS reserved_seats
-- calculate available seats as
-- (bus number_of_seat) - (booked_seats) - (reserved_seats)
, ( b.number_of_seat
- ( SELECT IFNULL(SUM(k.number_of_seat),0)
FROM booking k
WHERE k.bus_routine_id = r.id
)
- ( SELECT IFNULL(SUM(s.number_of_reserved_seat),0)
FROM reserved_seats s
WHERE s.routine_id = r.id
)
) AS avaliable_seat
FROM bus_routines r
JOIN buses b
ON b.id = r.bus_id
WHERE r.sector_from = 'KTM'
AND r.sector_to = 'PKR'
AND r.departure_date = '2015-12-15'
If there's no requirement to return the booked_seats and reserved_seats columns, those can be omitted from the query. The subqueries to get those values can just appear in the calculation of the available_seat column.
SQL Fiddle demonstration here: http://sqlfiddle.com/#!9/64eaa/7
Please try GROUP_CONCAT for return one column from multiple in subquery.
refer the links.
How to use GROUP_CONCAT in a CONCAT in MySQL
http://www.w3resource.com/mysql/aggregate-functions-and-grouping/aggregate-functions-and-grouping-group_concat.php
I've taken over a project that is a real mess so I've left with bad code structure that is forcing me to basically program in SQL. So changing the way of calculating this is for now not an option.
I have $sqlAdd variable that i need to populate in function and then concatenate that to main query to count number of lost tickets.
Main query looks like this:
$sql = "SELECT COUNT(*) as num_tickets, SUM(t.total_amount) as total_payin, SUM(t.total_payout) as total_payout
FROM t WHERE t.tickettime BETWEEN '$dateFrom' AND '$dateTo' AND t.bsid = $bsID
$sqlAdd";
So $sqlAdd is getting from another function
$sqlAdd = getSqlAdd();
And in that function i have this:
$sqlAdd = " AND 'WON' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'PAYEDOUT' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'CLOSED' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'OPEN' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)";
GROUP_CONCAT(tr.ticketstatus) is getting me these rows when i execute it
CLOSED,CLOSED,CLOSED
PAYEDOUT,PAYEDOUT
CLOSED,CLOSED
WON,LOST
LOST,WON,WON,WON,WON,WON
CLOSED,CLOSED
LOST,LOST,WON
WON,WON,WON,LOST,LOST,WON,WON
LOST
I just want to count rows that have only LOST status in it. So the result should be 1. But i keep getting 7. It it counting every LOST status in results.
You would seem to want something like this:
SELECT COUNT(*) as num_tickets, SUM(t.total_amount) as total_payin,
SUM(t.total_payout) as total_payout
FROM t
WHERE t.tickettime BETWEEN '$dateFrom' AND
'$dateTo' AND t.bsid = $bsID AND
NOT EXISTS (SELECT 1
FROM tr NATURAL JOIN
m NATURAL JOIN
tg
WHERE tr.ticketid = t.ticketid AND
tr.ticketstatus IN ('WON', 'PAYEDOUT', 'CLOSED', 'OPEN')
);
Some notes:
GROUP_CONCAT() is not appropriate for this type of comparison. In SQL, you don't convert lists to strings and then do comparisons -- at least if you want performance.
You should avoid NATURAL JOIN. A small change to any of the tables can totally change the semantics of the query. In addition, it is unclear what the JOIN keys are. I would recommend USING instead.
You might want tr.ticketstatus NOT IN ('LOST') in the subquery.
So i've wrote very ugly query that is probably slow but its working i'm getting results that i need.
$sql = " AND ticket_groupid IN (
SELECT tg FROM t as tt
NATURAL JOIN tr NATURAL JOIN m
WHERE tt.tickettime BETWEEN '$dateFrom' AND '$dateTo' AND tt.bsid = $bsid
AND 'LOST' IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr NATURAL JOIN m as mm
WHERE mm.ticket_groupid = m.ticket_groupid
)
GROUP BY mm.ticket_groupid )"
I have 3 tables:
-Sales
-Items_cstm
-Items
Sales and Items_cstm contains the data I have to get with the query and Items the ability of the item "Deleted (1 or 0)" (and dome more info I don't need).
The Items_cstm's id is = Items's id.
I have to list the Sales of the Items which aren't deleted (0).
I've tried somehow with inner join but it didn't work and I don't really know what am I doing wrong:
SELECT Items_cstm.quantity
FROM Items_cstm, Sales
WHERE '".$_POST['name']."' = Sales.name
AND
INNER JOIN Items ON Items_cstm.id = Items.id
WHERE Items.deleted = 0;
You can join your tables like this (but you did not show the link between Items_cstm and sales - you have to modify that)
SELECT ic.quantity
FROM Items_cstm ic
INNER JOIN Sales s on s.id = ic.id
INNER JOIN Items i ON ic.id = i.id
WHERE i.deleted = 0
AND s.name = '".$escapedName."'
Also always escape your user input.
Try something like this:
SELECT Items_cstm.quantity
FROM Items_cstm
INNER JOIN Sales ON sales.JOINCOLUMN = Items_cstm.JOINCOLUMN
INNER JOIN Items ON Items_cstm.id = Items.id
WHERE Sales.name = '".$_POST['name']."'
WHERE Items.deleted = 0;
Replace JOINCOLUMN above with the columns that let you match records between these two tables
Something like this should work:
SELECT Items_cstm.quantity
FROM Items_cstm
INNER JOIN Items ON Items_cstm.id = Items.id
INNER JOIN Sales ON Items_cstm.id = Sales.item_id // ??? check this one for column names
WHERE '$name' = Sales.name AND Items.deleted = 0;
Beware of SQL injection.
I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'