MySQL Sub Query difficulty - mysql

I'm trying to get a count of how many times a user has triggered the following query. And I've concluded that a Sub Query is required.
The below (admittedly indelicate) query works, as far as it goes, without the Sub Query. And the Sub Query works as a standalone query. But after three days of trying, I cannot get the two to work combined. I don't know if I have a glaring syntax error, or whether I'm getting it all wrong in principle. I need help!
SELECT id, status, FirstName, LastName, Track, KeyChange, Version,
DATE_FORMAT(CONVERT_TZ(Created,'+00:00','+1:00'), '%l:%i %p') AS Created_formatted,
TIME_FORMAT(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, pinknoise.Created, CURRENT_TIMESTAMP() - INTERVAL '0' HOUR)),'%Hh %im') AS elapsed,
(SELECT `FirstName`, Count(*) AS 'CountRequests' FROM `pinknoise` GROUP by `FirstName`)
FROM pinknoise
WHERE status = 'incoming'
ORDER BY Created DESC

I don't really understand what your query should achieve, but well formatted it looks like:
SELECT
id,
status,
FirstName,
LastName,
Track,
KeyChange,
Version,
DATE_FORMAT(
CONVERT_TZ(
Created,
'+00:00',
'+1:00'
),
'%l:%i %p'
) AS Created_formatted,
TIME_FORMAT(
SEC_TO_TIME(
TIMESTAMPDIFF(
SECOND,
pinknoise.Created,
CURRENT_TIMESTAMP() - INTERVAL '0' HOUR
)
),
'%Hh %im'
) AS elapsed
(
SELECT
`FirstName`,
Count(*) AS 'CountRequests'
FROM
`pinknoise`
GROUP by
`FirstName`
)
FROM
pinknoise
WHERE
status = 'incoming'
ORDER BY
Created DESC
What I imagine: you want the number of total entries for this particular firstname in the same table. The dirty way would be:
SELECT
id,
status,
FirstName,
LastName,
Track,
KeyChange,
Version,
DATE_FORMAT(
CONVERT_TZ(
Created,
'+00:00',
'+1:00'
),
'%l:%i %p'
) AS Created_formatted,
TIME_FORMAT(
SEC_TO_TIME(
TIMESTAMPDIFF(
SECOND,
pinknoise.Created,
CURRENT_TIMESTAMP() - INTERVAL '0' HOUR
)
),
'%Hh %im'
) AS elapsed,
(
SELECT
Count(*)
FROM
`pinknoise` AS tb
WHERE
tb.FirstName = pinknoise.FirstName
) AS CountRequests
FROM
pinknoise
WHERE
status = 'incoming'
ORDER BY
Created DESC
A much better performance would have a join:
SELECT
pinknoise.id,
pinknoise.status,
pinknoise.FirstName,
pinknoise.LastName,
pinknoise.Track,
pinknoise.KeyChange,
pinknoise.Version,
DATE_FORMAT(
CONVERT_TZ(
pinknoise.Created,
'+00:00',
'+1:00'
),
'%l:%i %p'
) AS Created_formatted,
TIME_FORMAT(
SEC_TO_TIME(
TIMESTAMPDIFF(
SECOND,
pinknoise.Created,
CURRENT_TIMESTAMP() - INTERVAL '0' HOUR
)
),
'%Hh %im'
) AS elapsed,
tabA.CountRequests
FROM
pinknoise
INNER JOIN
(
SELECT
Count(*) AS 'CountRequests',
FirstName
FROM
`pinknoise`
GROUP BY
FirstName
) tabA
ON
pinknoise.FirstName = tabA.FirstName
WHERE
status = 'incoming'
ORDER BY
Created DESC

Your subselect is returning 2 values in the select portion where it only expects one value. I'm guessing you are getting the FirstName with the intent of doing a join. If so, then try this:
SELECT
p.id,
p.status,
p.FirstName,
p.LastName,
p.Track,
p.KeyChange,
p.Version,
DATE_FORMAT(CONVERT_TZ(p.Created,'+00:00','+1:00'), '%l:%i %p') AS Created_formatted,
TIME_FORMAT(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, p.Created, CURRENT_TIMESTAMP() - INTERVAL '0' HOUR)),'%Hh %im') AS elapsed,
cnt.CountRequests
FROM
pinknoise p
inner join (SELECT p.FirstName, Count(*) AS CountRequests FROM pinknoise p GROUP by p.FirstName) cnt on p.FirstName = cnt.FirstName
WHERE
p.status = 'incoming'
ORDER BY
p.Created DESC;

Related

Reformat sql to show rows where main query returns null

Consider this sql:
SELECT DATE_FORMAT( Orders.Timestamp, '%Y%m' ) AS Period,
SUM(Price) AS 'Ordersum per month and organisation', Orders.Organisation,
(
SELECT SUM(Amount) AS Returns
FROM Returns
WHERE DATE_FORMAT( Returns.Timestamp, '%Y%m' ) = Period
AND Returns.Organisation = Orders.Organisation
) Returns
FROM Orders
GROUP BY Period, Organisation
Whenever there are rows in the subquery that doesn't have an equivalent period in the main query, the row isn't displayed. The reason is that the query takes its period from the orders table, and when the period of the subquery doesn't match a period in the orders table, it simply doesn't match the query.
Is there a way to reformat this query to achieve what I want?
Sqlfiddle here http://sqlfiddle.com/#!9/ace715/1
You can use left and right join with UNION like this:
SELECT
ifnull(DATE_FORMAT( Orders.Timestamp,'%Y%m' ),DATE_FORMAT(Returns.Timestamp,'%Y%m' )) AS Period,
SUM(Price) AS 'Ordersum per month and organisation',
ifnull(Orders.Organisation,Returns.Organisation) as 'Organisation',
SUM(Amount) AS 'Returns'
FROM Orders
left JOIN Returns
on DATE_FORMAT( Orders.Timestamp,'%Y%m' ) = DATE_FORMAT(Returns.Timestamp, '%Y%m' )
and Returns.Organisation = Orders.Organisation
GROUP BY Period, Returns.Organisation, Orders.Organisation
union
select ifnull(DATE_FORMAT( Orders.Timestamp, '%Y%m' ),DATE_FORMAT(Returns.Timestamp,'%Y%m' )) AS Period,
SUM(Price) AS 'Ordersum per month and organisation',
ifnull(Orders.Organisation,Returns.Organisation),
SUM(Amount) AS 'Returns'
FROM Orders
right JOIN Returns
on DATE_FORMAT( Orders.Timestamp, '%Y%m' ) = DATE_FORMAT(Returns.Timestamp, '%Y%m' )
and Returns.Organisation = Orders.Organisation
GROUP BY Period, Returns.Organisation, Orders.Organisation

Refine a SQL query for getting count(*)

I have the following query that gets some results I would like to group by attribute 'state'.
I tried different subquery but they didn't work and I'm a bit blocked.
The SQL is:
SELECT state, id_candidate_basic, MAX( DATE ) FROM `candidate_state`
WHERE `date` <= '2013-09-06 00:00:00' GROUP BY id_candidate_basic
ORDER BY `candidate_state`.`id_candidate_basic` DESC
This returns currently:
I would get a count(*) for each state. Example:
F, 14
I, 10
O, 9
SELECT state,
id_candidate_basic,
MAX( DATE ),
COALESCE(totalCount, 0) totalCount
FROM `candidate_state`
LEFT JOIN
(
SELECT state, COUNT(*) totalCount
FROM candidate_state
WHERE `date` <= '2013-09-06 00:00:00'
GROUP BY state
) ON candidate_state.state = b.state
WHERE `date` <= '2013-09-06 00:00:00'
GROUP BY id_candidate_basic
ORDER BY `candidate_state`.`id_candidate_basic` DESC

How to optimize this query with some subqueries in it

My query is like this:
SELECT date_format( created_at, '%Y-%m-%d' ) AS the_date,
COUNT(s.id) AS total,
(SELECT COUNT(ks.id) FROM kc_shares ks WHERE site = 'facebook' AND date_format( created_at, '%Y-%m-%d' ) = the_date ) AS total_facebook,
(SELECT COUNT(ks.id) FROM kc_shares ks WHERE site = 'twitter' AND date_format( created_at, '%Y-%m-%d' ) = the_date ) AS total_twitter
FROM `kc_shares` s
GROUP BY `the_date`
What I want to get is the number of daily shares with the specification of total, total shares to facebook (thus site = 'facebook') and total shares to twitter. That's why I need the GROUP BY.
When it had, like, a few thousands rows, there's no problem. But the table currently has almost 200,000 rows, and the query is very slow, taking about 20-30 seconds, even more I guess.
I've tried adding indices to site and created_at fields but to no avail.
Thanks
I think the sub queries are eating up performace. So maybe you can do something like this:
SELECT
date_format( created_at, '%Y-%m-%d' ) AS the_date,
COUNT(s.id) AS total,
SUM(CASE WHEN s.site='facebook' THEN 1 ELSE 0 END) AS total_facebook,
SUM(CASE WHEN s.site='twitter' THEN 1 ELSE 0 END) AS total_twitter
FROM
`kc_shares` s
GROUP BY
`the_date
`
Move the subselects so you join against them, rather that doing a subselect for every returned row.
Something like this (untested):-
SELECT date_format( created_at, '%Y-%m-%d' ) AS the_date,
COUNT(s.id) AS total,
Sub1.total_facebook, Sub2.total_twitter
FROM `kc_shares` s
LEFT OUTER JOIN (SELECT date_format( created_at, '%Y-%m-%d' ) AS sub_date, COUNT(ks.id) AS total_facebook FROM kc_shares ks WHERE site = 'facebook' GROUP BY sub_date ) Sub1 ON date_format( created_at, '%Y-%m-%d' ) = Sub1.sub_date
LEFT OUTER JOIN (SELECT date_format( created_at, '%Y-%m-%d' ) AS sub_date, COUNT(ks.id) AS total_twitter FROM kc_shares ks WHERE site = 'twitter' GROUP BY sub_date ) Sub2 ON date_format( created_at, '%Y-%m-%d' ) = Sub2.sub_date
GROUP BY `the_date`
Although finding a way to do a join on a non derived column (ie the date part of the date / time) would also help. Possibly a good case here for a bit or denormalisation, adding a field for just the date in addtion to the date / time currently stored.
An alternative would be to change the way the query works. The following would provide rows for each day/site rather than having the two sites on the same row.
SELECT
date_format( created_at, '%Y-%m-%d' ) AS the_date,site,
count(id)
FROM
kc_shares s
where
(site="facebook" or site="twitter") )
group by
created_at, site
I'm assuming that created_at is a date field.
This should provide the same data (I think, I haven't tried it) but in a different format.
Try an index on (created_at,site).

Group by count()

I'm trying to make the following query work:
SELECT
DATE_FORMAT( date, '%Y %m' ) AS `Month`,
COUNT( schedule_id ) AS `Shifts`,
COUNT(user_id) AS `Users`
FROM
schedule
GROUP BY
`Month`, `Shifts`
It should give a frequency table stating how many users work a certain amount of shifts, per month (e.g. in Dec. there were 10 users working 20 shifts, 12 users working 15 shifts etc).
MySQL can't group on a COUNT() though, so the query breaks. How can I make this work?
Try this:
SELECT
`Month`, `Shifts`, COUNT(`User`) `Users`
FROM (
SELECT -- select nr of shifts per user
DATE_FORMAT( date, '%Y %m' ) AS `Month`,
user_id AS `User`,
COUNT( schedule_id ) AS `Shifts`
FROM
schedule
GROUP BY
`Month`, `User`
) s
GROUP BY `Month`, `Shifts`
Inner query returns month, user and shifts count. In outer query you can group by shifts.
Use subquery to get counts per some idetifier ( column id in example ), then join it with original query
SELECT ... FROM schedule sh JOIN ( SELECT id, COUNT( schedule_id ) AS Shifts FROM schedule ) AS cnt ON cnt.id = sh.id GROUP BY ..., cnt.Shifts
SELECT
y
, m
, Shifts
, COUNT(*) AS Users
FROM
( SELECT
YEAR(date) AS y
, MONTH(date) AS m
, user_id
, COUNT(*) AS Shifts
FROM
schedule
GROUP BY
YEAR(date), MONTH(date), user_id
) AS grp
GROUP BY
y
, m
, Shifts

Using results from parent queries in nested select

I'm sure this is a fairly trivial problem, but I'm not sure what to google to find the solution.
I have a table that looks like this:
CREATE TABLE IF NOT EXISTS `transactions` (
`name` text collate utf8_swedish_ci NOT NULL,
`value` decimal(65,2) NOT NULL,
`date` date NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci ROW_FORMAT=COMPACT;
I populate this by cutting and pasting data from my internet banking service.
Value can be a negative or positive value, what both date and name contain should be fairly obvious ;)
I have constructed a query to let me see my bottom line for each month:
SELECT sum(`value`) as 'change', DATE_FORMAT(`date`, '%M %Y') as 'month'
FROM `transactions`
WHERE 1
GROUP BY year(`date`), month(`date`)
Now I would like to add the total accumulated money in the account at the end of the month as an additional column.
SELECT sum(`value`) as 'change', DATE_FORMAT(`date`, '%M %Y') as 'month',
(SELECT sum(`value`) FROM `transactions` WHERE `date` <= 123) as 'accumulated'
FROM `transactions`
WHERE 1
GROUP BY year(`date`), month(`date`)
123 is not exactly what I want in there, but I do not understand how to get at the result from my DATE_FORMAT inside that subquery.
Is this even the proper way to approach the problem?
This is mostly a personal exercise (running on a very small dataset) so I'm not very concerned about performance, readable SQL is far more important.
I am running a InnoDB table on MySQL 5.0.45
SELECT change,
CONCAT(mymonth, ' ', myyear) AS 'month',
(
SELECT SUM(`value`)
FROM `transactions`
WHERE `date` < DATE_ADD(STR_TO_DATE(CONCAT('01.', mymonth, '.', myyear, '%D.%M.%Y'), INTERVAL 1 MONTH))
)
FROM (
SELECT sum(`value`) as 'change', YEAR(date) AS myyear, MONTH(date) AS mymonth
FROM `transactions`
WHERE 1
GROUP BY
YEAR(`date`), MONTH(`date`)
) q
You wrote that you don't cate for performance, but this syntax is not much more complex but will be more efficient (just in case):
SELECT SUM(value) AS change,
CONCAT(MONTH(`date`), ' ', YEAR(`date`)) AS 'month',
#r : = #r + SUM(value) AS cumulative
FROM (
SELECT #r := 0
) AS vars,
transactions
WHERE 1
GROUP BY
YEAR(`date`), MONTH(`date`)
ORDER BY
YEAR(`date`), MONTH(`date`)
This one will count cumulative SUM's as well, but it will count each month only once.