Table Structure
Table 1
-------------
code | name
-------------
1 | abc
2 | xyz
Table 2
-------------------------------
code | table1_code | data
-------------------------------
1 | 1 | a
2 | 1 | b
3 | 1 | c
4 | 1 | d
5 | 1 | e
6 | 2 | f
7 | 2 | g
Now Expected Result:
Result
table1_code | name | data_col1 | data_col2 | data_col3
------------------------------------------------------------------
1 abc a b c
2 xyz f g -
What i have tried so far
SELECT a.code AS table1_code,
a.NAME,
b.data_col
FROM table1 AS a
LEFT OUTER JOIN table2 AS b
ON a.code = b.table1_code
kindly help me to alter the query to get above output,
i want data upto 3 columns only.
To do this you need a way to determine what the first three items in each group should be. The way to do this is to add a row number that restarts for each new group. This would be easy in any database that supports window function, but MySQL doesn't so it gets a bit more complicated as you have to use user defined variables to do the ranking.
In the query below ranking of data is done in the derived table used as source and then a conditional aggregation is performed so that you get one row per group:
SELECT
code,
name,
MAX(CASE WHEN rank = 1 THEN data END) data_col1,
MAX(CASE WHEN rank = 2 THEN data END) data_col2,
MAX(CASE WHEN rank = 3 THEN data END) data_col3
FROM (
SELECT
a.code,
a.name,
b.data,
(
CASE a.name
WHEN #grp THEN #row := #row + 1
ELSE #row := 1 AND #grp := a.name
END
) + 1 AS rank
FROM table1 AS a
LEFT OUTER JOIN table2 AS b ON a.code = b.table1_code
, (SELECT #row := 0, #grp := '') r
ORDER BY a.code, a.name asc
) src
GROUP BY code, name;
Sample SQL Fiddle
Related
I want to find a user's position in a leaderboard and return the 4 users above and 4 users below their position.
My table, 'predictions', looks something like this:
+----+---------+--------+-------+---------+
| id | userId | score | rank | gameId |
+----+---------+--------+-------+---------+
| 1 | 12 | 11 | 1 | 18 |
| 2 | 1 | 6 | 4 | 18 |
| 3 | 43 | 7 | 3 | 12 |
| 4 | 4 | 9 | 2 | 18 |
| 5 | 98 | 2 | 5 | 19 |
| 6 | 3 | 0 | 6 | 18 |
+----+---------+--------+-------+---------+
Obviously this isn't properly ordered, so I run this:
SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC
which gets me a nice table with each entry numbered.
I then want to search this generated table, find the row_number where userId = X, and then return the values 'around' that result.
I think I have the logic of the query down, I just can't work out how to reference the table 'generated' by the above query.
It would be something like this:
SELECT *
FROM (
SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC) generated_ordered_table
WHERE row_number < (SELECT row_number FROM generated_ordered_table WHERE userId = 1)
ORDER BY row_number DESC
LIMIT 0,5
This fails. What I'm trying to do is to generate my first table with the correct query, give it an alias of generated_ordered_table, and then reference this 'table' later on in this query.
How do I do this?
MySQL version 8+ could have allowed the usage of Window functions, and Common Table Expressions (CTEs); which would have simplified the query quite a bit.
Now, in the older versions (your case), the "Generated Rank Table" (Derived Table) cannot be referenced again in a subquery inside the WHERE clause. One way would be to do the same thing twice (select clause to get generated table) again inside the subquery, but that would be relatively inefficient.
So, another approach can be to use Temporary Tables. We create a temp table first storing the ranks. And, then reference that temp table to get results accordingly:
CREATE TEMPORARY TABLE IF NOT EXISTS gen_rank_tbl AS
(SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC)
Now, you can reference this temp table to get the desired results:
SELECT *
FROM gen_rank_tbl
WHERE row_number < (SELECT row_number FROM gen_rank_tbl WHERE userId = 1)
ORDER BY row_number DESC
LIMIT 0,5
You could use a bunch of unions
select userid,rank,'eq'
from t where gameid = 18 and userid = 1
union
(
select userid,rank,'lt'
from t
where gameid = 18 and rank < (select rank from t t1 where t1.userid = 1 and t1.gameid = t.gameid)
order by rank desc limit 4
)
union
(
select userid,rank,'gt'
from t
where gameid = 18 and rank > (select rank from t t1 where t1.userid = 1 and t1.gameid = t.gameid)
order by rank desc limit 4
);
+--------+------+----+
| userid | rank | eq |
+--------+------+----+
| 1 | 4 | eq |
| 4 | 2 | lt |
| 12 | 1 | lt |
| 3 | 6 | gt |
+--------+------+----+
4 rows in set (0.04 sec)
But it's not pretty
You can use two derived tables:
SELECT p.*,
(#user_curRow = CASE WHEN user_id = #x THEN rn END) as user_rn
FROM (SELECT p.*, #curRow := #curRow + 1 AS rn
FROM (SELECT p.*
FROM predictions p
WHERE p.gameId = 18
ORDER BY rank ASC
) p CROSS JOIN
(SELECT #curRow := 0, #user_curRow := -1) params
) p
HAVING rn BETWEEN #user_curRow - 4 AND #user_currow + 4;
Let's assume I have two columns: letters and numbers in a table called tbl;
letters numbers
a 1
b 2
c 3
d 4
Doing a cartesian product will lead to :
a 1
a 2
a 3
a 4
b 1
b 2
b 3
b 4
c 1
c 2
c 3
c 4
d 1
d 2
d 3
d 4
Write a query that reverts the cartesian product of these two columns back to the original table.
I tried multiple methods from using ROWNUM to selecting distinct values and joining them (which leads me back to the cartesian product)
SELECT DISTINCT *
FROM (SELECT DISTINCT NUMBERS
FROM TBL
ORDER BY NUMBERS) AS NB
JOIN (SELECT DISTINCT LETTERS
FROM TBL
ORDER BY LETTERS) AS LT1
which led me back to the cartesian product....
This is a version that works with 5.7.
SELECT `numbers`,`letters` FROM
(SELECT `numbers`,
#curRank := #curRank + 1 AS rank
FROM Table1 t, (SELECT #curRank := 0) r
GROUP By `numbers`
ORDER BY `numbers`) NB1
INNER JOIN
(SELECT `letters`,
#curRank1 := #curRank1 + 1 AS rank
FROM (
Select `letters` FROM Table1 t
GROUP By `letters`) t2, (SELECT #curRank1 := 0) r
ORDER BY `letters`) LT1 ON NB1.rank = LT1.rank;
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=cc17c2cfeff049edc73e437e5e4fd892
As Raymond and Ankit pointed out you have to know which order have the letters and even the order of the numbers has to be defined prior or else you never get a correct answer.
Another way of writing this:
SELECT numbers
, letters
FROM
( SELECT DISTINCT numbers
, #curRank := #curRank + 1 rank
FROM Table1 t
, (SELECT #curRank := 0) r
ORDER
BY numbers
) NB1
JOIN
( SELECT letters
, #curRank1 := #curRank1 + 1 rank
FROM
( SELECT DISTINCT letters
FROM Table1 t
) t2
, (SELECT #curRank1 := 0) r
ORDER
BY letters
) LT1
ON NB1.rank = LT1.rank;
If you are sure that the order will never be destroyed and is deterministic, You can use dense_rank() analytic function to achieve it back -
SELECT LT1.LETTERS, NB.NUMBERS
FROM (SELECT DISTINCT NUMBERS
FROM TBL
ORDER BY NUMBERS) AS NB
JOIN (SELECT DISTINCT LETTERS, RN
FROM (SELECT LETTERS, DENSE_RANK() OVER (ORDER BY LETTERS) RN
FROM TBL
ORDER BY LETTERS) T) AS LT1
ON NB.NUMBERS = LT1.RN
Here is the fiddle
Perhaps this is oversimplifying the problem, but it should be seen that this, or some variation of it, would suffice...
SELECT * FROM my_table;
+---------+---------+
| letters | numbers |
+---------+---------+
| a | 1 |
| a | 2 |
| a | 3 |
| a | 4 |
| b | 1 |
| b | 2 |
| b | 3 |
| b | 4 |
| c | 1 |
| c | 2 |
| c | 3 |
| c | 4 |
| d | 1 |
| d | 2 |
| d | 3 |
| d | 4 |
+---------+---------+
16 rows in set (0.00 sec)
SELECT x.*
, #i:=#i+1 numbers
FROM
( SELECT DISTINCT letters
FROM my_table
) x
, (SELECT #i:=0) vars
ORDER
BY letters;
+---------+---------+
| letters | numbers |
+---------+---------+
| a | 1 |
| b | 2 |
| c | 3 |
| d | 4 |
+---------+---------+
I have a list of rows and want to group them by ID and a certain start-value. (Say value=1 in the below-given example)
Group-by value
Set of rows - MySQL & Presto
-----------
ID | value
-----------
A | 1
A | 2
B | 1
B | 2
B | 5
B | 1
B | 2
C | 1
C | 3
C | 4
C | 1
D | 1
D | 8
D | 1
-----------
Expected Output :
-----------
ID | Value
-----------
A | 1,2
B | 1,2,5
B | 1,2
C | 1,3,4
C | 1
D | 1,8
D | 1
-----------
Actual Output :
-----------
ID | Value
-----------
A | 1,2
B | 1,2,5,1,2
C | 1,3,4,1
D | 1,8,1
-----------
Without a real id is really hard. So I introduce rowNumber:
SELECT (#row := #row + 1) AS rowNumber, ID,value
FROM myTable
CROSS JOIN (SELECT #row := 0) AS dummy
Then i add 2 columns with max Value:
SELECT a.*
FROM customTable as a
LEFT OUTER JOIN myTable b ON a.id = b.id AND a.value < b.value
WHERE b.id IS NULL
After this i need to use group_concat(value) and group by.
Group by has 2 condition, id and another custom boolean field:
CASE
WHEN l1.rowNumber <= l2.rowNumber THEN 0
ELSE 1
END
FINAL QUERY:
SELECT ct1.id, group_concat(ct1.value) as Value
FROM (
SELECT (#cnt := #cnt + 1) AS rowNumber, ID, value
FROM myTable
CROSS JOIN (SELECT #cnt := 0) AS dummy
) AS ct1
JOIN (
SELECT a.*
FROM (
SELECT (#row := #row + 1) AS rowNumber, ID, value
FROM myTable
CROSS JOIN (SELECT #row := 0) AS dummy
) AS a
LEFT OUTER JOIN myTable b ON a.id = b.id AND a.value < b.value
WHERE b.id IS NULL
) AS ct2 ON ct2.ID = ct1.id
GROUP BY ct1.id,
CASE
WHEN ct1.rowNumber <= ct2.rowNumber THEN 0
ELSE 1
END
You can test Here.
This only works with MySQL 5.6 or above
I have this table register:
id quantity type
1 | 10 | in
2 | 5 | in
1 | 3 | out
1 | 2 | out
2 | 5 | out
3 | 2 | in
3 | 1 | out
I want the balance of each stock *sum of type='in' - sum of type= 'out'*.
Desired output would be:
1 | 5
2 | 0
3 | 1
I also have another table item:
id | name
1 | A
2 | B
3 | C
Is it possible to view the output with the item name instead of the id?
So the final result is like:
A | 5
B | 0
C | 1
The basic idea is conditional aggregation --case inside of sum(). You also need a join to get the name:
select i.name,
sum(case when r.type = 'in' then quantity
when r.type = 'out' then - quantity
else 0
end) as balance
from register r join
item i
on r.id = i.id
group by i.name;
Acccording to description as mentioned in above question,as a solution to it please try executing following SQL query
SELECT i.name,
#in_total:= (select sum(quantity) from register where type = 'in'
and id = r.id group by id),
#out_total:= (select sum(quantity) from register where type = 'out'
and id = r.id group by id),
#balance:= (#in_total - #out_total) as balance
FROM `register`
as r join item i on r.id = i.id group by r.id
CROSS JOIN (SELECT #in_total := 0,
#out_total := 0,
#balance := 0) AS user_init_vars
How to achieve this by joins and group by or any other alternative
Tab 1:
id | data
1 | aaa
2 | bbb
3 | ccc
tab 2:
id | tab1ID | status
101 | 1 | Y
102 | 2 | Y
103 | 1 | X
104 | 2 | X
105 | 3 | X
106 | 1 | Z
107 | 2 | Z
required output:
id | data | status
1 | aaa | Z
2 | bbb | Z
3 | ccc | X
Record with the highest priority status has to come up in the result Z > Y > X
I want to avoid creating a separate table to store the priority order
Edit 1: change in sample data
First, give a row number to the second table based on the columns tablID and the priority of status. Then join it with the first table to get the the columns id and data and select only the rows having row number is 1.
Query
select t1.`id`, t1.`data`, t2.`status`
from `tab1` t1
left join(
select `id`, `tab1ID`, `status`,
(
case `tab1ID` when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := `tab1ID` end
) as rn
from `tab2`,
(select #curRow := 0, #curA := '') r
order by `tab1ID`, case `status` when 'Z' then 1
when 'Y' then 2 when 'X' then 3 else 4 end
)t2
on t1.`id` = t2.`tab1ID`
where t2.rn = 1;
SQL Fiddle Demo
If you want the most recent status, then one method is a correlated subquery:
select t1.*,
(select t2.status
from tab2 t2
where t2.tab1id = t1.id
order by t2.id desc
limit 1
) as status
from tab1 t1;
EDIT:
If you just want the highest status, use JOIN and GROUP BY:
select t1.*, max(t2.status)
from tab1 t1 left join
tab2 t2
on t2.tab1id = t1.id
group by t1.id;
Note: The use of select t1.* is permitted and even supported by the ANSI standard, assuming that t1.id is unique (a reasonable assumption).