Related
Basically I need help in my query here. I want to be in right order which is child must be under parents name and in A-Z order. But if I add a subChild under child (Split 1) seem the order is wrong. It should be under Room Rose.
p/s : A subChild also can create another subChild
HERE I PROVIDE A DEMO
Appreciate your help me get this ordered correctly?
SELECT A.venueID
, B.mainVenueID
, A.venueName
FROM tblAdmVenue A
LEFT
JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
ORDER
BY COALESCE(B.mainVenueID, A.venueID)
, B.mainVenueID IS NOT NULL
, A.venueID
I want it return an order something like this.
venueName
--------------
Banquet
Big Room
-Room Daisy
-Room Rose
-Split 1
Hall
-Meeting Room WP
Seem this recursive approach also in not working
WITH venue_ctg AS (
SELECT A.venueID, A.venueName, B.mainVenueID
FROM tblAdmVenue A LEFT JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
WHERE B.mainVenueID IS NULL
UNION ALL
SELECT A.venueID, A.venueName, B.mainVenueID
FROM tblAdmVenue A LEFT JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
WHERE B.mainVenueID IS NOT NULL
)
SELECT *
FROM venue_ctg ORDER BY venueName
output given
For your data you can use this:
To display this correctly, you can use a SEPARATPR like comma, and split the returned data, and check the hirarchy
-- schema
CREATE TABLE tblAdmVenue (
venueID VARCHAR(225) NOT NULL,
venueName VARCHAR(225) NOT NULL,
PRIMARY KEY(venueID)
);
CREATE TABLE tblAdmVenueLink (
venueLinkID VARCHAR(225) NOT NULL,
mainVenueID VARCHAR(225) NOT NULL,
subVenueID VARCHAR(225) NOT NULL,
PRIMARY KEY(venueLinkID)
-- FOREIGN KEY (DepartmentId) REFERENCES Departments(Id)
);
-- data
INSERT INTO tblAdmVenue (venueID, venueName)
VALUES ('LA43', 'Big Room'), ('LA44', 'Hall'),
('LA45', 'Room Daisy'), ('LA46', 'Room Rose'),
('LA47', 'Banquet'), ('LA48', 'Split 1'),
('LA49', 'Meeting Room WP');
INSERT INTO tblAdmVenueLink (venueLinkID, mainVenueID, subVenueID)
VALUES ('1', 'LA43', 'LA45'), ('2', 'LA43', 'LA46'),
('3', 'LA46', 'LA48'), ('4', 'LA44', 'LA49');
✓
✓
✓
✓
with recursive cte (subVenueID, mainVenueID,level) as (
select subVenueID,
mainVenueID, 1 as level
from tblAdmVenueLink
union
select p.subVenueID,
cte.mainVenueID,
cte.level+1
from tblAdmVenueLink p
inner join cte
on p.mainVenueID = cte.subVenueID
)
select
CONCAT(GROUP_CONCAT(b.venueName ORDER BY level DESC SEPARATOR '-->') ,'-->',a.venueName)
from cte c
LEFT JOIN tblAdmVenue a ON a.venueID = c.subVenueID
LEFT JOIN tblAdmVenue b ON b.venueID = c.mainVenueID
GROUP BY subVenueID;
| CONCAT(GROUP_CONCAT(b.venueName ORDER BY level DESC SEPARATOR '-->') ,'-->',a.venueName) |
| :----------------------------------------------------------------------------------------- |
| Big Room-->Room Daisy |
| Big Room-->Room Rose |
| Big Room-->Room Rose-->Split 1 |
| Hall-->Meeting Room WP |
db<>fiddle here
You want your data ordered in alphabetical order and depth first.
A common solution for this is to traverse the structure from the top element, concatenating the path to each item as you go. You can then directly use the path for ordering.
Here is how to do it in MySQL 8.0 with a recursive query
with recursive cte(venueID, venueName, mainVenueID, path, depth) as (
select v.venueID, v.venueName, cast(null as char(100)), venueName, 0
from tblAdmVenue v
where not exists (select 1 from tblAdmVenueLink l where l.subVenueID = v.venueID)
union all
select v.venueID, v.venueName, c.venueID, concat(c.path, '/', v.venueName), c.depth + 1
from cte c
inner join tblAdmVenueLink l on l.mainVenueID = c.venueID
inner join tblAdmVenue v on v.venueID = l.subVenueID
)
select * from cte order by path
The anchor of the recursive query selects top nodes (ie rows whose ids do not exist in column subVenueID of the link table). Then, the recursive part follows the relations.
As a bonus, I added a level column that represents the depth of each node, starting at 0 for top nodes.
Demo on DB Fiddle:
venueID | venueName | mainVenueID | path | depth
:------ | :-------------- | :---------- | :------------------------- | ----:
LA47 | Banquet | null | Banquet | 0
LA43 | Big Room | null | Big Room | 0
LA45 | Room Daisy | LA43 | Big Room/Room Daisy | 1
LA46 | Room Rose | LA43 | Big Room/Room Rose | 1
LA48 | Split 1 | LA46 | Big Room/Room Rose/Split 1 | 2
LA44 | Hall | null | Hall | 0
LA49 | Meeting Room WP | LA44 | Hall/Meeting Room WP | 1
Use only one table, not two. The first table has all the info needed.
Then start the CTE with the rows WHERE mainVenueID IS NULL, no JOIN needed.
This may be a good tutorial: https://stackoverflow.com/a/18660789/1766831
Its 'forest' is close to what you want.
I suppose you have:
table tblAdmVenue A is the venue list; and
table tblAdmVenueLink B is the tree relation table for parent-child
For your question on how to get a correct sorting order, I think one of the trick is to concatenate the parent venue names.
with q0(venueID, venueName, mainVenueID, venuePath) as (
select
A.venueID,
A.venueName,
null,
A.venueName
from tblAdmVenue A
left join tblAdmVenue B on A.venueID = B.subVenueID
where B.mainVenueID is null
union all
select
A.venueID,
A.venueName,
q0.venueID,
q0.venuePath + char(9) + A.venueName
from q0
inner join tblAdmVenue B on q0.venueID = B.mainVenueID
inner join tblAdmVenue A on A.venueID = B.subVenueID
)
select venueID, venueName, mainVenueID
from q0
order by venuePath
I need your help.
I have a database with a schema like this:
teams:
id
name
fundation_date
matchs:
id
date
id_local_team (foreign key to teams)
id_visit_team (foreign key to teams)
winner ('local', 'visit', 'draw')
players:
id
name
born
position ('arq','def','med','del')
id_team
goals:
id
id_match
id_player
time
and I need to do (among other things) this:
Show by team: Played matchs, winned matchs and drawn matchs (in different columns)
I have something like this:
SELECT t.name,
SUM(CASE t.id WHEN m.id_local_team THEN 1 WHEN m.id_visit_team THEN 1 ELSE 0 END) AS played,
SUM(CASE (CASE m.winner
WHEN 'local' THEN m.id_local_team
WHEN 'visit' THEN m.id_visit_team
ELSE NULL END)
WHEN t.id THEN 1
ELSE 0 END) AS winned,
SUM(CASE m.winner WHEN 'draw' THEN 1 ELSE 0 END) AS drawn
FROM teams AS t
INNER JOIN matchs AS m
ON (t.id = m.id_local_team OR t.id = m.id_visit_team)
GROUP BY t.name;
But that is giving me wrong results. Like, there are 8 matchs total, and the (4) teams are returning 12, 9, or 10 matchs winned (total of 43 matchs), a total of 16 winned matchs and a total of 10 drawn matchs. All above of 8.
What is happening??
In the full query I also have two more inner joins:
INNER JOIN players AS p
ON (p.id_team = t.id)
INNER JOIN goals AS g
ON (p.id = g.id_jugador)
I don't think it has nothing to do with these last ones. I know (think?) that i didn't do the matchs join correctly.
I appreciate if you have made it this far into the post!
The real schema is in spanish actually in Spanish (sorry for that guys) but here is all the magic:
SCHEMA
| equipos | CREATE TABLE `equipos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nombre` varchar(180) NOT NULL,
`f_fundacion` date DEFAULT NULL,
PRIMARY KEY (`id`)
)
| partidos | CREATE TABLE `partidos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fecha` datetime DEFAULT NULL,
`id_equipo_local` int(11) DEFAULT NULL,
`id_equipo_visitante` int(11) DEFAULT NULL,
`ganador` enum('local','visitante','empate') DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_partidos_equipos_1` (`id_equipo_local`),
KEY `fk_partidos_equipos_2` (`id_equipo_visitante`),
CONSTRAINT `fk_partidos_equipos_1` FOREIGN KEY (`id_equipo_local`) REFERENCES `equipos` (`id`),
CONSTRAINT `fk_partidos_equipos_2` FOREIGN KEY (`id_equipo_visitante`) REFERENCES `equipos` (`id`)
)
QUERY
SELECT e.nombre,
SUM(CASE e.id WHEN p.id_equipo_visitante THEN 1 WHEN p.id_equipo_local THEN 1 ELSE 0 END) AS jugados,
SUM(CASE (CASE ganador
WHEN 'local' THEN p.id_equipo_local
WHEN 'visitante' THEN p.id_equipo_visitante
ELSE NULL END)
WHEN e.id THEN 1
ELSE 0 END) AS ganados,
SUM(CASE ganador WHEN 'empate' THEN 1 ELSE 0 END) AS empatados,
SUM(CASE (CASE ganador
WHEN 'local' THEN p.id_equipo_local
WHEN 'visitante' THEN p.id_equipo_visitante
ELSE NULL END)
WHEN e.id THEN 1
ELSE 0 END) * 3 + SUM(CASE ganador WHEN 'empate' THEN 1 ELSE 0 END) AS puntos,
COUNT(DISTINCT g.id) AS goles_a_favor
FROM equipos AS e
INNER JOIN partidos AS p
ON (e.id = p.id_equipo_visitante OR e.id = p.id_equipo_local)
INNER JOIN jugadores AS j
ON (j.id_equipo = e.id)
INNER JOIN goles AS g
ON (j.id = g.id_jugador)
GROUP BY e.nombre;
RESULTS
+----------------------------------+---------+---------+-----------+--------+---------------+
| nombre | jugados | ganados | empatados | puntos | goles_a_favor |
+----------------------------------+---------+---------+-----------+--------+---------------+
| Club Atlético All Boys | 12 | 6 | 3 | 21 | 3 |
| Club Atlético Chacarita Juniors | 12 | 3 | 0 | 9 | 3 |
| Club Atlético Ferrocarril Oeste | 9 | 3 | 3 | 12 | 3 |
| Club Atlético Tucumán | 10 | 4 | 4 | 16 | 2 |
+----------------------------------+---------+---------+-----------+--------+---------------+
You say that the full query contains joins to each goal made in a given match. This would lead to a situation where the each match is counted N times where N is the number of goals in the match. So for a 0-0 draw the match won't be counted at all, for a 1-0 match the match is counted once for the home team and zero times for the visiting team and 1-2 once for the home team and twice for the visiting team.
To check the number of goals in favor you should first calculate the the goal balance per match using a subquery or a view and then join with that. Then you won't have to problem caused by joining with the player-table.
It does look like the Matchs JOIN is a problem. So you are matching every match at least twice, once for the home team and once for the visiting team, but that doesn't quite explain 43 matches being displayed. Would it be possible to maybe see the full set of results? Sometimes SQL stuff can get touch to debug without access to the tables themselves, but at least seeing the results and what are duplicated might help.
You may want to join only on the winning teams - that should cut half of it out. Actually, since you seem to be trying to get match information, I would SELECT data FROM matches rather than teams. Selecting FROM the table that will limit your total selected rows is always your best bet, then JOIN from there.
I have a data and they are recorded by each year, I am trying to compare two years( the past year and the current year) data within one mysql query
Below are my tables
Cost Items
| cid | items |
| 1 | A |
| 2 | B |
Cost
| cid | amount | year |
| 1 | 10 | 1 |
| 1 | 20 | 2 |
| 1 | 30 | 1 |
This is the result I am expecting when i want to compare the year 1 and year 2. Year 1 is the past year and year 2 is the current year
Results
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 0 |
However the below query is what i used by gives a strange answer.
SELECT
IFNULL(ps.`amount`, '0') as pastCost
IFNULL(cs.`amount`, '0') as currentCost
FROM
`Cost Items` b
LEFT JOIN
`Cost` ps
ON
b.cID=ps.cID
AND
ps.Year = 1
LEFT JOIN
`Cost` cu
ON
b.cID=cu.cID
AND
cu.Year =2
This is the result i get from my query
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 20 |
Please what am i doing wrong? Thanks for helping.
I'm missing something about your query; the SQL text shown can't produce that result.
There is no source for the items column in the SELECT list, and there is no table aliased as cs. (Looks like the expression in the SELECT list would need to be cu.amount
Aside from that, the results being returned look exactly like what we'd expect. Each row returned from year=2 is being matched with each row returned from year=1. If there were three rows for year=1 and two rows for year=2, we'd get six rows back... each row for year=1 "matched" with each row for year=2.
If (cid, year) tuple was UNIQUE in Cost, then this query would return a result similar to what you expect.
SELECT b.items
, IFNULL(ps.amount, '0') AS pastCost
, IFNULL(cu.amount, '0') AS currentCost
FROM `Cost Items` b
LEFT
JOIN `Cost` ps
ON ps.cid = b.cid
AND ps.Year = 1
LEFT
JOIN `Cost` cu
ON cu.cid = b.cid
AND cu.Year = 2
Since (cid, year) is not unique, you need some additional column to "match" a single row for year=1 with a single row for year=2.
Without some other column in the table, we could use an inline view to generate a value. I can illustrate how we can make MySQL return a resultset like the one you show, one way that could be done, but I don't think this is really the solution to whatever problem you are trying to solve:
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
A query something like that would return a resultset that looks like the one you are expecting. But again, I strongly suspect that this is not really the resultset you are really looking for.
EDIT
OP leaves comment with vaguely nebulous report of observed behavior: "the above solution doesnt work"
Well then, let's check it out... create a SQL Fiddle with some tables so we can test the query...
SQL Fiddle here http://sqlfiddle.com/#!9/e3d7e/1
create table `Cost Items` (cid int unsigned, items varchar(5));
insert into `Cost Items` (cid, items) values (1,'A'),(2,'B');
create table `Cost` (cid int unsigned, amount int, year int);
insert into `Cost` (cid, amount, year) VALUES (1,10,1),(1,20,2),(1,30,1);
And when we run the query, we get a syntax error. There's closing paren missing in the expressions in the SELECT list, easy enough to fix.
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
Returns:
items pastCost currentCost
------ -------- -----------
A 10 20
A 30 0
B 0 0
I have the following database structure, and I am trying to run a single query that will show classrooms and how many students are part of the classroom, and how many rewards a classroom has allocated out, as well as how many points allocated to a single classroom (based on the classroom_id column).
Using the query at the very bottom I am trying to collect the 'totalPoints' that a classroom has assigned - based on counting the points column in the classroom_redeemed_codes table and return this as a single integer.
For some reason the values are incorrect for the totalPoints - I am doing something wrong but not sure what...
-- UPDATE --
Here is the sqlfiddle:-
http://sqlfiddle.com/#!2/a9f45
My Structure:
CREATE TABLE `organisation_classrooms` (
`classroom_id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL,
`organisation_id` int(11) NOT NULL,
`period` int(1) DEFAULT '0',
`classroom_bg` int(2) DEFAULT '3',
`sortby` varchar(6) NOT NULL DEFAULT 'points',
`sound` int(1) DEFAULT '0',
PRIMARY KEY (`classroom_id`)
);
CREATE TABLE organisation_classrooms_myusers (
`classroom_id` int(11) NOT NULL,
`user_id` bigint(11) unsigned NOT NULL,
);
CREATE TABLE `classroom_redeemed_codes` (
`redeemed_code_id` int(11) NOT NULL AUTO_INCREMENT,
`myuser_id` bigint(11) unsigned NOT NULL DEFAULT '0',
`ssuser_id` bigint(11) NOT NULL DEFAULT '0',
`classroom_id` int(11) NOT NULL,
`order_product_id` int(11) NOT NULL DEFAULT '0',
`order_product_images_id` int(11) NOT NULL DEFAULT '0',
`date_redeemed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`points` int(11) NOT NULL,
`type` int(1) NOT NULL DEFAULT '0',
`notified` int(1) NOT NULL DEFAULT '0',
`inactive` tinyint(3) NOT NULL,
PRIMARY KEY (`redeemed_code_id`),
);
SELECT
t.classroom_id,
title,
COALESCE (
COUNT(DISTINCT r.redeemed_code_id),
0
) AS totalRewards,
COALESCE (
COUNT(DISTINCT ocm.user_id),
0
) AS totalStudents,
COALESCE (sum(r.points), 0) AS totalPoints
FROM
`organisation_classrooms` `t`
LEFT OUTER JOIN classroom_redeemed_codes r ON (
r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (
r.date_redeemed >= 1393286400
OR r.date_redeemed = 0
)
)
LEFT OUTER JOIN organisation_classrooms_myusers ocm ON (
ocm.classroom_id = t.classroom_id
)
WHERE
t.organisation_id =37383
GROUP BY title
ORDER BY t.classroom_id ASC
LIMIT 10
-- EDIT --
OOPS! I hate SQL sometimes... I have made a big mistake, I am trying to count the number of STUDENTS in the classroom_redeemed_codes rather than the organisation_classrooms_myuser table. I'm really sorry I should have picked that up sooner?!
classroom_id | totalUniqueStudents
16 1
17 2
46 1
51 1
52 1
There are 7 rows in the classroom_redeemed_codes table but as classroom_id 46 has two rows although with the same myuser_id (this is the student id) this should appear as one unique student.
Does this make sense? Essentially trying to grab the number of unique students in the classroom_redeemed_codes tables based on the myuser_id column.
e.g a classroom id 46 could have 100 rows in the classroom_redeemed_codes tables, but if it is the same myuser_id for each this should show the totalUniqueStudents count as 1 and not 100.
Let me know if this isn't clear....
-- update --
I have the following query which seems to work borrowed from a user below which seems to work... (my head hurts) i'll accept the answer again. Sorry for the confusion - I think I was just over thinking this somewhat
select crc.classroom_id,
COUNT(DISTINCT crc.myuser_id) AS users,
COUNT( DISTINCT crc.redeemed_code_id ) AS classRewards,
SUM( crc.points ) as classPoints, t.title
from classroom_redeemed_codes crc
JOIN organisation_classrooms t
ON crc.classroom_id = t.classroom_id
AND t.organisation_id = 37383
where crc.inactive = 0
AND ( crc.date_redeemed >= 1393286400
OR crc.date_redeemed = 0 )
group by crc.classroom_id
I ran by first doing a pre-query aggregate of your points per specific class, then used left-join to it. I am getting more rows in the result set than your sample expected, but don't have MySQL to test/confirm directly. Howeverhere is a SQLFiddle of your query By doing your query with sum of points, and having a Cartesian result when applying the users table, it is probably the basis of duplicating the points. By pre-querying on the redeem codes itself, you just grab that value, then join to users.
SELECT
t.classroom_id,
title,
COALESCE ( r.classRewards, 0 ) AS totalRewards,
COALESCE ( r.classPoints, 0) AS totalPoints,
COALESCE ( r.uniqStudents, 0 ) as totalUniqRedeemStudents,
COALESCE ( COUNT(DISTINCT ocm.user_id), 0 ) AS totalStudents
FROM
organisation_classrooms t
LEFT JOIN ( select crc.classroom_id,
COUNT( DISTINCT crc.redeemed_code_id ) AS classRewards,
COUNT( DISTINCT crc.myuser_id ) as uniqStudents,
SUM( crc.points ) as classPoints
from classroom_redeemed_codes crc
JOIN organisation_classrooms t
ON crc.classroom_id = t.classroom_id
AND t.organisation_id = 37383
where crc.inactive = 0
AND ( crc.date_redeemed >= 1393286400
OR crc.date_redeemed = 0 )
group by crc.classroom_id ) r
ON t.classroom_id = r.classroom_id
LEFT OUTER JOIN organisation_classrooms_myusers ocm
ON t.classroom_id = ocm.classroom_id
WHERE
t.organisation_id = 37383
GROUP BY
title
ORDER BY
t.classroom_id ASC
LIMIT 10
You need sum(r.points) and a subquery in the left outer join see below
SELECT
t.classroom_id,
title,
COALESCE (
COUNT(DISTINCT r.redeemed_code_id),
0
) AS totalRewards,
COALESCE(sum(r.points),0) AS totalPoints
,COALESCE(sum(T1.cnt),0) as totalStudents
FROM
`organisation_classrooms` `t`
left outer join (select classroom_id, count(user_id) cnt
from organisation_classrooms_myusers
group by classroom_id) T1 on (T1.classroom_id=t.classroom_id)
LEFT OUTER JOIN classroom_redeemed_codes r ON (
r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (
r.date_redeemed >= 1393286400
OR r.date_redeemed = 0
)
)
WHERE
t.organisation_id =37383
GROUP BY title
ORDER BY t.classroom_id ASC
LIMIT 10
I simplified your query; there is no need to use COALLESCE together with COUNT() because COUNT() never returns NULL. For SUM() I prefer to use IFNULL() because it is shorter and more readable. The results displayed below contain only the data for classroom_id #16, #17 and #46 for easier comparison with the example provided in the question. The actual result sets are bigger and contain all the classroom_ids present in the tables. However, their presence is not needed to understand how and why it works.
SELECT
t.classroom_id,
t.title,
COUNT(DISTINCT r.redeemed_code_id) AS totalRewards,
COUNT(DISTINCT ocm.user_id) AS totalStudents,
IFNULL(SUM(r.points), 0) AS totalPoints
FROM `organisation_classrooms` t
LEFT JOIN `classroom_redeemed_codes` r
ON r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (r.date_redeemed >= 1393286400 OR r.date_redeemed = 0)
LEFT JOIN `organisation_classrooms_myusers` ocm
ON ocm.classroom_id = t.classroom_id
WHERE t.organisation_id = 37383
GROUP BY t.classroom_id
ORDER BY t.classroom_id ASC
Let's try to split it in pieces and put them together after that. First, let's see what users are selected:
Query #1
SELECT
t.classroom_id,
t.title,
ocm.user_id
FROM `organisation_classrooms` t
LEFT JOIN `organisation_classrooms_myusers` ocm
ON ocm.classroom_id = t.classroom_id
WHERE t.organisation_id = 37383
ORDER BY t.classroom_id ASC
I removed the classroom_redeemed_codes table and it fields, removed GROUP BY and replaced the aggregate function COUNT(ocm.user_id) with ocm.user_id to see what users are selected.
The result show us this part of the query is correct:
classroom_id | title | user_id
-------------+-------+--------
16 | BLUE | 2
16 | BLUE | 1
17 | GREEN | 508835
17 | GREEN | 508826
46 | PINK | NULL
There are 2 users in classroom #16, another 2 in #7 and none in class #46.
Putting back the GROUP BY clause will make it return the correct values (2, 2, 0) in the totalStudents column.
Let's check now the relationship with table classroom_redeemed_codes:
Query #2
SELECT
t.classroom_id,
t.title,
r.redeemed_code_id, r.points
FROM `organisation_classrooms` t
LEFT JOIN `classroom_redeemed_codes` r
ON r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (r.date_redeemed >= 1393286400 OR r.date_redeemed = 0)
WHERE t.organisation_id = 37383
ORDER BY t.classroom_id ASC
The result is:
classroom_id | title | redeemed_code_id | points
-------------+-------+------------------+-------
16 | BLUE | 7 | 50
17 | GREEN | 8 | 25
17 | GREEN | 9 | 75
46 | PINK | 5 | 250
46 | PINK | 6 | 100
Again, grouping by classroom_id will produce (1, 2, 2) in column totalRewards and (50, 100, 350) in column totalPoints which is correct.
The trouble starts when you want to combine these into a single query. No matter what kind of join you use, for the provided input you will get (2*1, 2*2, 1*2) rows for classroom_id having the values 16, 17 and 46 (in this order). The values I multiplied in parenthesis are the number of rows for each classroom_id in the first and in the query result set above.
Combined
Let' try the query that selects the rows before grouping them:
SELECT
t.classroom_id,
t.title,
r.redeemed_code_id, ocm.user_id, r.points
FROM `organisation_classrooms` t
LEFT JOIN `classroom_redeemed_codes` r
ON r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (r.date_redeemed >= 1393286400 OR r.date_redeemed = 0)
LEFT JOIN `organisation_classrooms_myusers` ocm
ON ocm.classroom_id = t.classroom_id
WHERE t.organisation_id = 37383
ORDER BY t.classroom_id ASC
It returns this result set:
classroom_id | title | redeemed_code_id | user_id | points
-------------+-------+------------------+---------+-------
16 | BLUE | 7 | 2 | 50
16 | BLUE | 7 | 1 | 50 <- *
-------------+-------+------------------+---------+-------
17 | GREEN | 8 | 508835 | 25
17 | GREEN | 8 | 508826 | 25 <- *
17 | GREEN | 9 | 508835 | 75
17 | GREEN | 9 | 508826 | 75 <- *
-------------+-------+------------------+---------+-------
46 | PINK | 5 | NULL | 250
46 | PINK | 6 | NULL | 100
I added horizontal rules to separate the rows that belongs to the same group when we add the GROUP BY clause. This is basically the way a SQL query with GROUP BY is executed, no matter the name of the actual software that implements it.
As you can see, for each classroom, it combines all the redeemed codes associated with the classroom with all the users associated with the classroom. If you add more users and redeemed codes for classrooms #16, #17 and #46 in your tables you will get a much larger result set.
The next step on the execution of a GROUP BY query is to produce a single row from each group you see above. There is no problem with columns classroom_id and title, they contain a single value in each group. For the columns redeemed_code_id and user_id your query counts distinct values and that works fine too. The problem is with the addition of points.
If you just SUM() them, you get a redeemed code added for each user_id in the group. If you use SUM(DISTINCT points) it is also wrong because it will ignore the duplicates even when they are different entries in table classroom_redeemed_codes.
What you want is to add points for DISTINCT redeemed_code_id. I marked on the above result set the rows you don't want.
This is not possible using this query because on calculation of the aggregate values each column is independent of the other. We need a query that selects the desired rows before grouping them.
An Idea
We can try to add the missing columns (with NULL values) to the two simple queries above, UNION ALL them then select from this and GROUP BY.
First, let's be sure it selects what we need:
SELECT
t.classroom_id,
t.title,
NULL AS redeemed_code_id, ocm.user_id, NULL AS points
FROM `organisation_classrooms` t
LEFT JOIN `organisation_classrooms_myusers` ocm
ON ocm.classroom_id = t.classroom_id
WHERE t.organisation_id = 37383
UNION ALL
SELECT
t.classroom_id,
t.title,
r.redeemed_code_id, NULL AS user_id, r.points
FROM `organisation_classrooms` t
LEFT JOIN `classroom_redeemed_codes` r
ON r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (r.date_redeemed >= 1393286400 OR r.date_redeemed = 0)
WHERE t.organisation_id = 37383
ORDER BY classroom_id
Attention! The ORDER BY clause applies to the UNIONed result set. If you want to order the rows of each SELECT (it doesn't help because UNION doesn't keep the order) you need to enclose that query in parenthesis and put the ORDER BY clauses there.
The result set looks great:
classroom_id | title | redeemed_code_id | user_id | points
-------------+-------+------------------+---------+-------
16 | BLUE | NULL | 1 | NULL
16 | BLUE | NULL | 2 | NULL
16 | BLUE | 7 | NULL | 50
-------------+-------+------------------+---------+-------
17 | GREEN | 8 | NULL | 25
17 | GREEN | 9 | NULL | 75
17 | GREEN | NULL | 508826 | NULL
17 | GREEN | NULL | 508835 | NULL
-------------+-------+------------------+---------+-------
46 | PINK | 5 | NULL | 250
46 | PINK | 6 | NULL | 100
46 | PINK | NULL | NULL | NULL
Now we could put some parenthesis around the query above (strip ORDER BY) and use it in another query, grouping the data by classroom_id, counting the users and the redeemed codes and summing their points.
You will get a query that looks awful and, on your current database schema, crawls when your tables have several hundred rows. This is why I will not write it here.
Attention!
Its performance can be improved by adding the missing indexes to your tables, on the fields that appear in the ON, WHERE, ORDER BY and GROUP BY clauses of the query.
It will bring a significant improvement but I won't rely very much on that. For really big tables (hundreds of thousands of rows) it will still crawl.
Another Idea
We can also add GROUP BY on both Query #1 and Query #2 first and UNION ALL them after that:
SELECT
t.classroom_id,
t.title,
NULL AS totalRewards,
COUNT(DISTINCT ocm.user_id) AS totalStudents,
NULL AS totalPoints
FROM `organisation_classrooms` t
LEFT JOIN `organisation_classrooms_myusers` ocm
ON ocm.classroom_id = t.classroom_id
WHERE t.organisation_id = 37383
GROUP BY t.classroom_id
UNION ALL
SELECT
t.classroom_id,
t.title,
COUNT(DISTINCT redeemed_code_id) AS totalRewards,
NULL AS totalStudents,
SUM(points) AS totalPoints
FROM `organisation_classrooms` t
LEFT JOIN `classroom_redeemed_codes` r
ON r.classroom_id = t.classroom_id
AND r.inactive = 0
AND (r.date_redeemed >= 1393286400 OR r.date_redeemed = 0)
WHERE t.organisation_id = 37383
GROUP BY t.classroom_id
ORDER BY classroom_id, totalRewards
This produces a nice result set:
classroom_id | title | totalRewards | totalStudents | totalPoints
-------------+-------+--------------+---------------+-------------
16 | BLUE | NULL | 2 | NULL
16 | BLUE | 1 | NULL | 50
17 | GREEN | NULL | 2 | NULL
17 | GREEN | 2 | NULL | 100
46 | PINK | NULL | 0 | NULL
46 | PINK | 2 | NULL | 350
This query can be embedded in another query that groups by classroom_id and SUM()s the total columns above to get the final result. But again, the final query is big and ugly and it
doesn't run very fast for large tables. And again, this is the reason I don't write it here.
Conclusion
It can be done in a single query but it doesn't look good and it doesn't work well on large tables.
Regarding the performance, put EXPLAIN in front of your query then check the values in columns type, key and Extra of the result. See the documentation for explanation of the possible values of these columns, what to try to achieve and what to avoid.
Both queries I created on both ideas produce joins of type range or ALL and having Using filesort in column Extra (all these are slow). Using them as sub-queries in bigger queries will not improve the way they are execution, on the contrary.
I recommend you to run the individual SELECT queries from the last code example as two separate queries; they will return the odd and the even rows from the above result set. Then combine their results into the client code. It will run faster this way.
I have 3 tables.They are as follows.
1.system_user_master
________________________
user_id
user_type
user_registeration_code
user_first_name
user_last_name
2.book_appointment
-----------------------
booking_id
booking_date
puser_id
duser_id
doctor_name
3.routine_queue_patient
----------------------
queue_id
booking_id
queue_checkin_time
puser_id
duser_id
qdoctor_name
Now i want the result like
patient_registeration_code, patient_first_name, patient_last_name, booking_date, queue_checkin_time
In routine_queue_patient booking_id can be null.I want the list of patient of current date of selected doctor who are in routine_queue_patient with booking_id has some value or can be null.if booking_id is null then it shows in booking_date in query result is null and if booking id exists in routine_queue_patient then it display's the booking date.
I have written the query.The result is as follows.
booking_date | quecheck_in_time | user_first_name | user_last_name | user_regis_code
2013-11-12 | 2013-11-12 15:50:53 | rushang | patel | rp9898 |
2013-11-12 | 2013-11-12 16:00:11 | anjana | bhatt | ab9087
The booking_date of rushang patel must come in null as in the routine_queue_patient the booking_id of rushang patel is null but i got the booking_date of second record in front of rushang patel.
The query I have written is as follows.
SELECT DISTINCT b.booking_date
, r.queue_checkin_time
, s.user_first_name
, s.user_last_name
, s.user_registeration_code
FROM routine_queue_patient r
JOIN system_user_master s
ON r.puser_id = s.user_id
JOIN book_appointment b
ON ((b.booking_id = r.booking_id) OR r.booking_id is NULL)
AND DATE(r.queue_checkin_time) = '2013-11-12'
WHERE r.qdoctor_name = 'kashyup Nanavati'
AND DATE(b.booking_date) = '2013-11-12'
Thanks
Rushang
The join with book_appointment table is wrong. You shouldn't try to join on a null value.
Use left join to do it : it will join if a correspondant row is found, else all joined table columns will be null.
=>
SELECT DISTINCT b.booking_date
, r.queue_checkin_time
, s.user_first_name
, s.user_last_name
, s.user_registeration_code
FROM routine_queue_patient r
JOIN system_user_master s
ON r.puser_id = s.user_id
LEFT JOIN book_appointment b
ON (b.booking_id = r.booking_id AND DATE(b.booking_date) = '2013-11-12')
WHERE r.qdoctor_name = 'kashyup Nanavati'
AND DATE(r.queue_checkin_time) = '2013-11-12'