I want to share some SQL queries that I use for paging when a joined table and ordering is needed.
In these examples, the result set is limited to row numbers 2-3.
MySql 5.1:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT DISTINCT o.id FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
)
ORDER BY outerO.Value DESC, outerO.Id ASC
LIMIT 2
OFFSET 1
This one shows best what I want to achieve. Oracle 11 and DB2 are much more complicated.
Oracle 11:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT id FROM(
SELECT row_.id, rownum rownum_
FROM (
SELECT id
FROM (
SELECT DISTINCT o.Value, o.Id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
)
) row_ WHERE rownum <= 3
) WHERE rownum_ > 1
)
ORDER BY outerO.Value DESC, outerO.Id ASC
I found no way to do this with fewer subselects.
DB2 10.5:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT inner1_.id
FROM (
SELECT inner2_.id, rownumber()
OVER(ORDER BY ORDER OF inner2_) AS rownumber_
FROM (
SELECT id
FROM (
SELECT DISTINCT o.Value, o.Id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
)
FETCH FIRST 3 ROWS ONLY
) AS inner2_
) AS inner1_
WHERE inner1_.rownumber_ > 1 ORDER BY inner1_.rownumber_
)
ORDER BY outerO.Value DESC, outerO.Id ASC
I found no way to do this with fewer subselects.
Microsoft SQL-Server 2016:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT DISTINCT o.id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
)
ORDER BY outerO.Value DESC, outerO.Id ASC
OFFSET 1 ROWS
FETCH NEXT 2 ROWS ONLY
These queries do work for me, but perhaps there are better solutions available?
This is how I would do it in DB2
SELECT ID, VALUE
FROM (
SELECT ID, VALUE,
ROW_NUMBER() OVER (ORDER BY o.Value DESC, o.Id ASC) AS RN
FROM MyTable
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
) X
WHERE RN > 1 AND RN <= 3
Related
I am working in MYSQL for the first and I am having issues with the following query
SELECT
t.id,
t.name,
t.description,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_skill_rate s
INNER JOIN hard_skills hs ON s.hard_skill_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS skills,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_knowledge_rate s
INNER JOIN knowledge hs ON s.knowledge_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS knowledge,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_abilities_rate s
INNER JOIN ability hs ON s.ability_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS knowledge
FROM
occupations t
The occupation table contains 1033 rows occupation_skill_rate contains 34160 rows and it takes more than 1 minute to execute this query. Please let me know if you need further clarification for helping me.
Thanks for your help
Ajai
The occupation_%_rate tables seem to be many-to-many, correct? They need these indexes and no id:
PRIMARY KEY(occupation_id, xxx_id)
INDEX(xxx_id, occupation_id)
But, it seems like the ORDER BY and LIMIT when used with GROUP_CONCAT(). Please describe what the query's intention is; we may be able to help in rewriting it.
GROUP_CONCAT allows an ORDER BY clause but not a LIMIT. Can you do without the LIMITs?
Example
Instead of
( SELECT GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM occupation_skill_rate s
INNER JOIN hard_skills hs ON s.hard_skill_id = hs.id
WHERE s.occupation_id = t.id
ORDER BY s.rate DESC
LIMIT 15
) AS skills;
Do
( SELECT CONCAT( name, '|', rate ORDER BY rate DESC )
FROM (
SELECT hs.name, s.rate
FROM occupation_skill_rate s
INNER JOIN hard_skills hs
ON s.hard_skill_id = hs.id
AND s.occupation_id = t.id
ORDER BY s.rate DESC
LIMIT 15
) AS a
) AS skills
But I suspect t is not visible that deeply nested.
If that is the case, rearrange things thus:
SELECT t.id, t.name, t.description, s3.skills, ...
FROM occupations AS t
JOIN (
SELECT s2.occupation_id,
CONCAT( s2.name, '|', s2.rate ORDER BY rate DESC )
AS skills
FROM (
SELECT hs.name, s1.rate, s1.occupation_id
FROM occupation_skill_rate s1
INNER JOIN hard_skills hs
ON s1.hard_skill_id = hs.id
ORDER BY s.rate DESC
LIMIT 15
) AS s2
GROUP BY s2.occupation_id
ORDER BY s2.rate DESC
) AS s3 ON s2.occupation_id = t.id
JOIN ...
JOIN ... ;
Another
There is also a way to build the long GROUP_CONCAT, then chop to 15 items by using SUBSTRING_INDEX(...).
For some reason MYSQL doesn't support LIMIT inside a subquery:
SELECT m.*
FROM `my_table` m
WHERE m.`id` IN (
SELECT o.`id`
FROM (SELECT DISTINCT i.`id`, i.`label`, i.`client`, i.`place`
FROM `my_table` i
ORDER BY i.`label`, -i.`client` DESC, -i.`place` DESC) o
WHERE m.`label` = o.`label` LIMIT 1
);
I've tried using join from this link: INNER JOIN INSTEAD OF IN(LIMIT error) but not succeeded. Has anyone any clues for it? Thanks.
Since the subquery returns only 1 row with 1 column there is no need for IN.
You can use =:
SELECT m.*
FROM `my_table` m
WHERE m.`id` = (
SELECT o.`id`
FROM (
SELECT DISTINCT i.`id`, i.`label`, i.`client`, i.`place`
FROM `my_table` i
ORDER BY i.`label`, -i.`client` DESC, -i.`place` DESC) o
WHERE m.`label` = o.`label` LIMIT 1
);
But as it is written, your query uses LIMIT without ORDER BY (you do use ORDER BY in the inner subquery where it is useless).
Do you mean to do something like this:
SELECT m.*
FROM `my_table` m
WHERE m.`id` = (
SELECT o.`id`
FROM (
SELECT DISTINCT i.`id`, i.`label`, i.`client`, i.`place`
FROM `my_table` i
) o
WHERE m.`label` = o.`label`
ORDER BY o.`label`, -o.`client` DESC, -o.`place` DESC
LIMIT 1
);
Also ordering by the negative value of a column descending is equivalent to ordering just ascending, so the ORDER BY clause can be simplified to:
ORDER BY o.`label`, o.`client`, o.`place`
Is it possible to convert this subquery to join?
SELECT `news`.`newsId`,
(SELECT `comments`.`text`
FROM `comments`
WHERE `comments`.`newsId` = `news`.`newsId`
order by `comments`.`date` desc
limit 1)
FROM `news` , `comments`
where `news`.`newsId` = `comments`.`newsId`
GROUP BY `news`.`newsId`
order by news.date desc;
I assume newsId is unique.
SELECT `news`.`newsId`,
`comments`.`text`
FROM `news`
CROSS APPLY (SELECT `comments`.`text`
FROM `comments`
WHERE `comments`.`newsId` = `news`.`newsId`
order by `comments`.`date` desc
limit 1) cm
order by news.date desc;
I think that what you're trying to do is:
SELECT n.newsId FROM news n
INNER JOIN comments c ON c.newsId = n.newsId
ORDER BY c.date DESC, n.date
LIMIT 1
The GROUP BY is not necessary as you are not using any aggregation function. You can have unique entries with DISTINCT
how to change this MYSQL query to sql server
SELECT
id.value AS ICDCode,
items.itemName AS ProblemListDescription,
COUNT(*) as UsageCount
FROM problemlist pl
INNER JOIN items
ON pl.asmtId = items.itemId
LEFT OUTER JOIN itemdetail id
ON (items.itemId=id.itemId AND id.propId=13 )
WHERE (pl.SNOMED='' OR pl.SNOMED IS NULL) AND pl.deleteflag=0
group by id.value, items.itemName
ORDER BY UsageCount DESC, ICDCode ASC
LIMIT 0,10 ;
I have tried this for sql server but its throwing error
select * from
(
SELECT
id.value AS ICDCode,
items.itemName AS ProblemListDescription,
COUNT(*)as UsageCount ,
row_number() over (ORDER BY UsageCount DESC, ICDCode ASC ) as rownum
FROM problemlist pl
INNER JOIN items
ON pl.asmtId=items.itemId
LEFT OUTER JOIN itemdetail id
ON (items.itemId=id.itemId AND id.propId=13 )
WHERE (pl.SNOMED='' OR pl.SNOMED IS NULL) AND pl.deleteflag=0
group by id.value, items.itemName
) sno
WHERE rownum BETWEEN 0 AND 10 ;
error message is
column usagecount is invalid
column icdcode is invalid
what is the mistake or i have to do it in another way ? guide me
I would probably just use TOP here:
SELECT TOP 10
id.value AS ICDCode,
items.itemName AS ProblemListDescription,
COUNT(*) as UsageCount
FROM problemlist pl
INNER JOIN items
ON pl.asmtId = items.itemId
LEFT OUTER JOIN itemdetail id
ON (items.itemId=id.itemId AND id.propId=13 )
WHERE (pl.SNOMED='' OR pl.SNOMED IS NULL) AND pl.deleteflag=0
GROUP BY
id.value,
items.itemName
ORDER BY
UsageCount DESC, ICDCode
By the way, the error in your query is that you were referring to an alias in the ROW_NUMBER function, but the alias is not yet available at that point in the query. You could use the following instead:
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC, ICDCode) AS rownum
In the following query:
SELECT
(SELECT nick FROM nicks n WHERE n.pid=p.id LIMIT 1 ORDER BY id DESC) as nick
, (
(
( SELECT COUNT(*) FROM kills k WHERE k.pid = p.id )
+
( SELECT COUNT(*) FROM votos v WHERE v.pid = p.id )
)
- (SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id )
) as score
, (SELECT COUNT(*) FROM kills k WHERE k.pid = p.id ) as kills
, (SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id ) as deaths
, (SELECT COUNT(*) FROM headshots h WHERE h.pid = p.id ) as headshots
, (SELECT COUNT(*) FROM votos v WHERE v.pid = p.id ) as reputation
FROM players p
WHERE p.uuid='STEAM_x:x:xxxxxx'
GROUP BY kills
This query works fine... but i think there exists a better way to do this.
Can anyone help me optimize this query?
Here is a somewhat better way to write the query:
SELECT p.*, (kills + reputation - deaths) as score
FROM (SELECT (SELECT nick FROM nicks n WHERE n.pid = p.id ORDER BY id DESC LIMIT 1
) as nick,
(SELECT COUNT(*) FROM kills k WHERE k.pid = p.id ) as kills,
(SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id ) as deaths,
(SELECT COUNT(*) FROM headshots h WHERE h.pid = p.id ) as headshots,
(SELECT COUNT(*) FROM votos v WHERE v.pid = p.id ) as reputation
FROM players p
WHERE p.uuid = 'STEAM_x:x:xxxxxx'
) p
GROUP BY kills;
Note: I don't understand what the GROUP BY is doing. You are only aggregating by one column, so the rest of the columns have indeterminate values. Perhaps you intend ORDER BY.
I am guessing that the overhead for materializing the subquery before the group by is slightly less than the additional subqueries. But your version may have very comparable performance.
For either version, you want the following indexes:
players(uuid)
kills(pid)
deaths(pid)
headshots(pid)
votos(pid)