I currently have a table with following schema:
CREATE TABLE `order_handling` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`oh_no` varchar(24) COLLATE utf8_unicode_ci DEFAULT NULL,
`date_request` datetime DEFAULT NULL,
`status` smallint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
I want to perform query that will result like this:
+-------------------------------------------------------------+
| MonthDone | OhProgress | OhHold | OhCancel | OhDone | Total |
+-------------------------------------------------------------+
| June 2016 | 2 | 1 | 1 | 3 | 7 |
| July 2016 | 6 | 1 | null | null | 7 |
+-------------------------------------------------------------+
My Query is,
SELECT
DATE_FORMAT(`oh`.`date_request`, '%m-%Y') AS `IdMonth`,
(SELECT COUNT(*) FROM order_handling as oh1 WHERE `oh1`.`status` IN (1,2) GROUP BY IdMonth) As OhProgress,
(SELECT COUNT(*) FROM order_handling as oh2 WHERE `oh2`.`status` IN (3) GROUP BY IdMonth) As OhHold,
(SELECT COUNT(*) FROM order_handling as oh3 WHERE `oh3`.`status` IN (4) GROUP BY IdMonth) As OhCancel,
(SELECT COUNT(*) FROM order_handling as oh3 WHERE `oh3`.`status` IN (5) GROUP BY IdMonth) As OhDone,
(SELECT COUNT(*) FROM order_handling as oh5 WHERE `oh5`.`status` IN (1,2,3,4,5) GROUP BY IdMonth) As SumTotal
FROM `order_handling` `oh`
GROUP BY DATE_FORMAT(`oh`.`date_request`, '%M %Y')
ORDER BY `oh`.`date_request` ASC
The output was not expected. If I change SubQuery GROUP BY with DATE_FORMAT(date_request, '%m-%Y') like follows:
SELECT
DATE_FORMAT(`oh`.`date_request`, '%m-%Y') AS `IdMonth`,
(SELECT COUNT(*) FROM order_handling as oh1 WHERE `oh1`.`status` IN (1,2) GROUP BY DATE_FORMAT(`oh1`.`date_request`, '%m-%Y')) As OhProgress,
(SELECT COUNT(*) FROM order_handling as oh2 WHERE `oh2`.`status` IN (3) GROUP BY DATE_FORMAT(`oh2`.`date_request`, '%m-%Y')) As OhHold,
(SELECT COUNT(*) FROM order_handling as oh3 WHERE `oh3`.`status` IN (4) GROUP BY DATE_FORMAT(`oh3`.`date_request`, '%m-%Y')) As OhCancel,
(SELECT COUNT(*) FROM order_handling as oh4 WHERE `oh4`.`status` IN (5) GROUP BY DATE_FORMAT(`oh4`.`date_request`, '%m-%Y')) As OhDone,
(SELECT COUNT(*) FROM order_handling as oh5 WHERE `oh5`.`status` IN (1,2,3,4,5) GROUP BY DATE_FORMAT(`oh5`.`date_request`, '%m-%Y')) As SumTotal
FROM `order_handling` `oh`
GROUP BY DATE_FORMAT(`oh`.`date_request`, '%M %Y')
ORDER BY `oh`.`date_request` ASC
It gives me Error: Subquery return more than 1 row
Here is my SQLFiddle
Please help,
thank you
No need for subqueries
SELECT
DATE_FORMAT(`date_request`, '%m-%Y') AS `IdMonth`,
SUM(status IN (1,2) ) As OhProgress,
SUM(status IN (3) ) As OhHold,
SUM(status IN (4) ) As OhCancel,
SUM(status IN (5) ) As OhDone,
SUM(status IN (1,2,3,4,5)) As SumTotal
FROM `order_handling`
GROUP BY DATE_FORMAT(`date_request`, '%M %Y')
ORDER BY `date_request` ASC
SQLFiddle (courtesy of #Giorgos)
Try this:
SELECT
DATE_FORMAT(oh.`date_request`, '%m-%Y') AS `IdMonth`,
(SELECT COUNT(*)
FROM order_handling as oh1
WHERE oh1.`status` IN (1,2) AND
`IdMonth` = DATE_FORMAT(oh1.`date_request`, '%m-%Y')) As OhProgress,
(SELECT COUNT(*)
FROM order_handling as oh2
WHERE oh2.`status` IN (3) AND
`IdMonth` = DATE_FORMAT(oh2.`date_request`, '%m-%Y')) As OhHold,
(SELECT COUNT(*)
FROM order_handling as oh3
WHERE oh3.`status` IN (4) AND
`IdMonth` = DATE_FORMAT(oh3.`date_request`, '%m-%Y')) As OhCancel,
(SELECT COUNT(*)
FROM order_handling as oh4
WHERE oh4.`status` IN (5) AND
`IdMonth` = DATE_FORMAT(oh4.`date_request`, '%m-%Y')) As OhDone,
(SELECT COUNT(*)
FROM order_handling as oh5
WHERE oh5.`status` IN (1,2,3,4,5) AND
`IdMonth` = DATE_FORMAT(oh5.`date_request`, '%m-%Y')) As SumTotal
FROM `order_handling` oh
GROUP BY DATE_FORMAT(oh.`date_request`, '%M %Y')
ORDER BY oh.`date_request` ASC
Demo here
Related
Right now I just have an aggregate of how many days a user has worked. I'm trying to change this query to most continuous days worked.
Where u12345 would be 4 and u1 would be 2.
Is this possible to do with a BigQuery statement?
EDIT I am Kind of close with the following query but my u1 is getting 3 instead of 2.
SELECT MIN(e.timestamp) as date_created, e.uid, COUNT(e.uid) + 1 AS streak
FROM OnSite e
LEFT JOIN OnSite ee
ON e.uid = ee.uid
AND DATE(e.timestamp) = DATE(DATE_ADD(ee.timestamp, INTERVAL -1 DAY))
WHERE ee.uid IS NOT NULL
GROUP BY e.uid;
Schema (MySQL v5.7)
CREATE TABLE OnSite
(`uid` varchar(55), `worksite_id` varchar(55), `timestamp` datetime)
;
INSERT INTO OnSite
(`uid`, `worksite_id`, `timestamp`)
VALUES
("u12345", "worksite_1", '2019-01-01'),
("u12345", "worksite_1", '2019-01-02'),
("u12345", "worksite_1", '2019-01-03'),
("u12345", "worksite_1", '2019-01-04'),
("u12345", "worksite_1", '2019-01-06'),
("u1", "worksite_1", '2019-01-01'),
("u1", "worksite_1", '2019-01-02'),
("u1", "worksite_1", '2019-01-05'),
("u1", "worksite_1", '2019-01-06')
;
Query #1
SELECT uid, COUNT(DISTINCT timestamp) Total
FROM OnSite
GROUP BY uid;
| uid | Total |
| ------ | ----- |
| u1 | 4 |
| u12345 | 5 |
View on DB Fiddle
Below is for BigQuery Standard SQL
In case if you are interested in max consecutive days of the users on the same worksite:
#standardSQL
SELECT uid, MAX(consecuitive_days) max_consecuitive_days
FROM (
SELECT uid, grp, COUNT(1) consecuitive_days
FROM (
SELECT uid,
COUNTIF(step > 1) OVER(PARTITION BY uid, worksite_id ORDER BY ts) grp
FROM (
SELECT uid, worksite_id, ts,
DATE_DIFF(ts, LAG(ts) OVER(PARTITION BY uid, worksite_id ORDER BY ts), DAY) step
FROM `project.dataset.table`
)
) GROUP BY uid, grp
) GROUP BY uid
In case if worksite does not matter and you are looking just for max consecutive days:
#standardSQL
SELECT uid, MAX(consecuitive_days) max_consecuitive_days
FROM (
SELECT uid, grp, COUNT(1) consecuitive_days
FROM (
SELECT uid,
COUNTIF(step > 1) OVER(PARTITION BY uid ORDER BY ts) grp
FROM (
SELECT uid, ts,
DATE_DIFF(ts, LAG(ts) OVER(PARTITION BY uid ORDER BY ts), DAY) step
FROM `project.dataset.table`
)
) GROUP BY uid, grp
) GROUP BY uid
You can test, play any of above with he sample data from your question as in below example
#standardSQL
WITH `project.dataset.table` AS (
SELECT 'u12345' uid, 'worksite_1' worksite_id, DATE '2019-01-01' ts UNION ALL
SELECT 'u12345', 'worksite_1', '2019-01-02' UNION ALL
SELECT 'u12345', 'worksite_1', '2019-01-03' UNION ALL
SELECT 'u12345', 'worksite_1', '2019-01-04' UNION ALL
SELECT 'u12345', 'worksite_1', '2019-01-06' UNION ALL
SELECT 'u1', 'worksite_1', '2019-01-01' UNION ALL
SELECT 'u1', 'worksite_1', '2019-01-02' UNION ALL
SELECT 'u1', 'worksite_1', '2019-01-05' UNION ALL
SELECT 'u1', 'worksite_1', '2019-01-06'
)
SELECT uid, MAX(consecuitive_days) max_consecuitive_days
FROM (
SELECT uid, grp, COUNT(1) consecuitive_days
FROM (
SELECT uid,
COUNTIF(step > 1) OVER(PARTITION BY uid ORDER BY ts) grp
FROM (
SELECT uid, ts,
DATE_DIFF(ts, LAG(ts) OVER(PARTITION BY uid ORDER BY ts), DAY) step
FROM `project.dataset.table`
)
) GROUP BY uid, grp
) GROUP BY uid
with result:
Row uid max_consecuitive_days
1 u12345 4
2 u1 2
does this fit for you?
set #gr=1;
select uid, max(cnt) max_cnt from (
select uid, grp, count(*) cnt from (
select uid,
case when ifnull(DATE_ADD(oldDate, INTERVAL 1 DAY), timestamp)= timestamp then
#gr
else
#gr := #gr +1
end grp
from
(
SELECT
uid,
timestamp,
lag(timestamp) over (partition by uid order by timestamp asc) as oldDate
FROM OnSite
) t
)t2
group by uid, grp
)t3
group by uid
Result
| uid | max_cnt |
| ------ | ------- |
| u1 | 2 |
| u12345 | 4 |
DB Fiddle
The following query returns a result with 5 columns (
date ,lowest_hr_price ,max_hr_price ,min_price , max_price )
instead of
(date ,lowest_hr_price ,max_hr_price , min_price ,max_price , AvgPrice, AvgPieces ).
AvgPrice and AvgPieces are instead added as rows .
(select date(m.min_max_date) as date,
max(case when m.lbl='min_hr_price' then m.min_max_hr_price else null end) as lowest_hr_price,
max(case when m.lbl='max_hr_price' then m.min_max_hr_price else null end) as max_hr_price,
max(case when n.lbl='min_price' then n.min_max_price else null end) as min_price,
max(case when n.lbl='max_price' then n.min_max_price else null end) as max_price
from (select 'min_hr_price' as lbl, price as min_max_hr_price, date_time as min_max_date
from mytable
where date_time in (select min(date_time) as min_date from mytable group by date(date_time)) and symbol = 'dollar'
UNION
select 'max_hr_price', price, date_time
from mytable WHERE symbol = 'dollar'
AND date_time in (select max(date_time) as max_date from mytable WHERE symbol = 'dollar' group by date(date_time))) as m,
(
select 'min_price' as lbl,
min(date_time) as min_max_date,
min(price) as min_max_price
from mytable
WHERE symbol = 'dollar'
group by date(date_time)
UNION
select 'max_price' as lbl,
max(date_time) as min_max_date,
max(price) as min_max_price
from mytable
WHERE symbol = 'dollar'
group by date(date_time)
) n
where m.min_max_date=n.min_max_date
group by date(m.min_max_date)
order by m.min_max_date DESC
)
UNION
(SELECT null, null, date_time, avg (price) as AvgPrice, avg (pieces) as AvgPieces FROM mytable
WHERE symbol = 'dollar'
group by date(date_time))
Actual result:
date |lowest_hr_price | max_hr_price | min_price | max_price
------------------------------------------------------------------------------------
2018-03-06 | 1 | 2 | 0 | 10
NULL | NULL | {date} | {avgprice} | {avgpieces}
Expected result:
date |lowest_hr_price | max_hr_price | min_price | max_price | AvgPrice | AvgPieces
-------------------------------------------------------------------------------------------------------------
2018-03-06 | 1 | 2 | 0 | 10 | {avgprice}| {avgpieces}
if you instead of an union (select rows append one select to each others ) need all the result on the same rows
in this case you could use a cross join eg :
select distinct * from (
(select date(m.min_max_date) as date,
max(case when m.lbl='min_hr_price' then m.min_max_hr_price else null end) as lowest_hr_price,
max(case when m.lbl='max_hr_price' then m.min_max_hr_price else null end) as max_hr_price,
max(case when n.lbl='min_price' then n.min_max_price else null end) as min_price,
max(case when n.lbl='max_price' then n.min_max_price else null end) as max_price
from (select 'min_hr_price' as lbl, price as min_max_hr_price, date_time as min_max_date
from mytable
where date_time in (select min(date_time) as min_date from mytable group by date(date_time)) and symbol = 'dollar'
UNION
select 'max_hr_price', price, date_time
from mytable WHERE symbol = 'dollar'
AND date_time in (select max(date_time) as max_date from mytable WHERE symbol = 'dollar' group by date(date_time))) as m,
(
select 'min_price' as lbl,
min(date_time) as min_max_date,
min(price) as min_max_price
from mytable
WHERE symbol = 'dollar'
group by date(date_time)
UNION
select 'max_price' as lbl,
max(date_time) as min_max_date,
max(price) as min_max_price
from mytable
WHERE symbol = 'dollar'
group by date(date_time)
) n
where m.min_max_date=n.min_max_date
group by date(m.min_max_date)
order by m.min_max_date DESC
) ) T1 INNER join
(SELECT null, null, date_time, avg (price) as AvgPrice, avg (pieces) as AvgPieces FROM mytable
WHERE symbol = 'dollar'
group by date(date_time)) T2 ON date(T1.date) = date(T2.date_time)
i have an SQL Script which generates the following Output:
+------------+------------+--------------------+----------------------+---------------------+
| CUSTOMERID | TOTALCOUNT | VALIDWARRANTYCOUNT | EXPIREDWARRANTYCOUNT | LASTPURCHASED |
+------------+------------+--------------------+----------------------+---------------------+
| 1 | 5 | 5 | 0 | 2013-12-24 14:37:45 |
| 2 | 3 | 3 | 0 | 2013-12-24 14:37:45 |
| 3 | 6 | 6 | 0 | 2013-10-23 13:37:45 |
+------------+------------+--------------------+----------------------+---------------------+
I would like to see the Companyname of the Customer in a additional column. I have an extra table with customer id and company name but I don't know how to extend my query. All trys result in an empty table.
Here is my Query:
SELECT p2c.customerid
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT DISTINCT
p2c.customerid
FROM
products2customers p2c
INNER JOIN
products p
ON
p2c.customerid = p.id
) AS p2c
I made an SQL Fiddle with the normal query without a join, I hope someone could give me a hint.
SQLFiddle
Just add your JOIN in the very last line, and select the company after the customer id:
SELECT p2c.customerid,
c.company
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT DISTINCT
p2c.customerid
FROM
products2customers p2c
) AS p2c
JOIN customers c ON c.id = p2c.customerid; <--
Updated fiddle:
http://sqlfiddle.com/#!2/60396/5/0
just add another join to the customer table as below
SELECT p2c.customerid, company
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT
p2c.customerid, c.company
FROM
products2customers p2c
INNER JOIN
products p
ON
p2c.customerid = p.id
inner join customers c
on c.id=p2c.customerid group by 1,2
) AS p2c
I have 2 tables:
olympic_medalists with columns gold_country, silver_country, bronze_country
flags with country column
I want to list the olympic medal table accordingly. I have this query, it works, but it seems to kill mysql. Hope someone can help me with an optimized query.
SELECT DISTINCT country AS sc,
IFNULL(
(SELECT COUNT(silver_country)
FROM olympic_medalists
WHERE silver_country = sc AND silver_country != ''
GROUP BY silver_country),0) AS silver_medals,
IFNULL(
(SELECT COUNT(gold_country)
FROM olympic_medalists
WHERE gold_country = sc AND gold_country != ''
GROUP BY gold_country),0) AS gold_medals,
IFNULL(
(SELECT COUNT(bronze_country)
FROM olympic_medalists
WHERE bronze_country = sc AND bronze_country != ''
GROUP BY bronze_country),0) AS bronze_medals
FROM olympic_medalists, flags
GROUP BY country, gold_medals, silver_country, bronze_medals HAVING (
silver_medals >= 1 || gold_medals >= 1 || bronze_medals >= 1)
ORDER BY gold_medals DESC, silver_medals DESC, bronze_medals DESC,
SUM(gold_medals+silver_medals+bronze_medals)
result will be like:
country | g | s | b | tot
---------------------------------
country1 | 9 | 5 | 2 | 16
country2 | 5 | 5 | 5 | 15
and so on
Thanks!
olympic medalists:
`id` int(8) NOT NULL auto_increment,
`gold_country` varchar(64) collate utf8_unicode_ci default NULL,
`silver_country` varchar(64) collate utf8_unicode_ci default NULL,
`bronze_country` varchar(64) collate utf8_unicode_ci default NULL, PRIMARY KEY (`id`)
flags
`id` int(11) NOT NULL auto_increment,
`country` varchar(128) default NULL,
PRIMARY KEY (`id`)
This will be much more efficient than your current solution of executing three different SELECT subqueries for each row in a cross-joined relation (and you wonder why it stalls out!):
SELECT a.country,
COALESCE(b.cnt,0) AS g,
COALESCE(c.cnt,0) AS s,
COALESCE(d.cnt,0) AS b,
COALESCE(b.cnt,0) +
COALESCE(c.cnt,0) +
COALESCE(d.cnt,0) AS tot
FROM flags a
LEFT JOIN (
SELECT gold_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY gold_country
) b ON a.country = b.gold_country
LEFT JOIN (
SELECT silver_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY silver_country
) c ON a.country = c.silver_country
LEFT JOIN (
SELECT bronze_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY bronze_country
) d ON a.country = d.bronze_country
What would be even faster is instead of storing the actual textual country name in each of the gold, silver, and bronze columns, just store the integer-based country id. Comparisons on integers are always going to be faster than comparisons on strings.
Moreover, once you replace each country name in the olympic_medalists table with the corresponding id's, you'll want to create an index on each column (gold, silver, and bronze).
Updating the textual names to be their corresponding id's instead is a simple task and could be done with a single UPDATE statement in conjunction with some ALTER TABLE commands.
try this:
SELECT F.COUNTRY,IFNULL(B.G,0) AS G,IFNULL(B.S,0) AS S,
IFNULL(B.B,0) AS B,IFNULL(B.G+B.S+B.B,0) AS TOTAL
FROM FLAGS F LEFT OUTER JOIN
(SELECT A.COUNTRY,
SUM(CASE WHEN MEDAL ='G' THEN 1 ELSE 0 END) AS G,
SUM(CASE WHEN MEDAL ='S' THEN 1 ELSE 0 END) AS S,
SUM(CASE WHEN MEDAL ='B' THEN 1 ELSE 0 END) AS B
FROM
(SELECT GOLD_COUNTRY AS COUNTRY,'G' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE GOLD_COUNTRY IS NOT NULL
UNION ALL
SELECT SILVER_COUNTRY AS COUNTRY,'S' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE SILVER_COUNTRY IS NOT NULL
UNION ALL
SELECT BRONZE_COUNTRY AS COUNTRY,'B' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE BRONZE_COUNTRY IS NOT NULL)A
GROUP BY A.COUNTRY)B
ON F.COUNTRY=B.COUNTRY
ORDER BY IFNULL(B.G,0) DESC,IFNULL(B.S,0) DESC,
IFNULL(B.B,0) DESC,IFNULL(B.G+B.S+B.B,0) DESC,F.COUNTRY
I have table prod with structure
Id, name, keyword, yearborn.
It is necessary to bring the number of keywords grouped encountered in the field yearborn , for example
SELECT yearborn, COUNT(Id) cnt1 FROM prod WHERE keyword='admiral'
GROUP BY yearborn
and
SELECT yearborn, COUNT(Id) cnt2 FROM prod WHERE keyword='captain' GROUP BY yearborn
How do I combine these two queries to get the output result table with the structure
yearborn, cnt1, cnt2?
I have tried UNION
SELECT yearborn, COUNT(Id) cnt1 FROM prod WHERE keyword='admiral' GROUP BY yearborn
UNION
SELECT yearborn, COUNT(Id) cnt2 FROM prod WHERE keyword='captain' GROUP BY yearborn
But result is:
yearborn, cnt1
Tell me please how to get out of this situation.
SELECT yearborn,
SUM(IF(keyword = 'admiral', 1, 0)) cnt1,
SUM(IF(keyword = 'captain', 1, 0)) cnt2
FROM prod
WHERE keyword = 'admiral'
OR keyword = 'captain'
GROUP BY yearborn
The result will be something like this (of course this is fictitious data):
+----------+------+------+
| yearborn | cnt1 | cnt2 |
+----------+------+------+
| 2004 | 1233 | 0 |
| 1999 | 0 | 15 |
+----------+------+------+
You can use a SUM() on your keyword conditions to do this, because the result of comparisons in MySQL can be treated as 0 (false) or 1 (true):
SELECT yearborn, SUM(keyword='admiral') cnt1, SUM(keyword='captain') cnt2 FROM prod GROUP BY yearborn
This also has the advantage of only using one table scan (it's faster than using unions and subqueries).
SELECT yearborn, COUNT(Id) cnt1, NULL cnt2 FROM prod WHERE keyword='admiral' GROUP BY yearborn
UNION
SELECT yearborn, NULL cnt1, COUNT(Id) cnt2 FROM prod WHERE keyword='captain' GROUP BY yearborn
How about:
SELECT yearborn,SUM(cnt1),SUM(cnt2) FROM
(
SELECT yearborn, COUNT(id) cnt1, 0 cnt2 FROM prod WHERE keyword='admiral'
UNION ALL
SELECT yearborn, 0 cnt1, COUNT(id) cnt2 FROM prod WHERE keyword='captain'
)
GROUP BY yearborn
Well, you can try this:
SELECT `yearborn`,
COUNT(`Id`) AS `cnt1`
FROM `prod`
WHERE `keyword` IN ('admiral','captain')
GROUP BY `yearborn`,`keyword`;
edit:
added the keyword in GROUP BY statment.