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).
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;
I have a table like this:
id col1 col2 col3
10 1 3
9 1 2 3
8 2 3
7 2 3
6 1 2
5 3
Each column has one value only or null. Eg. Col1 has 1 or empty. Col2 has 2 or empty.
I'd like to get the sum of repeating values only between two successive rows.
so the result would look like this:
I need to get the sum of total repeating values in each row.
id col1 col2 col3 Count
10 1 3 2 (shows the repeating values between id10 & id9 rows)
9 1 2 3 2 (shows the repeating values between id9 & id8 rows)
8 2 3 1
7 2 1
6 1 2 0
5 3
I googled and tried some queries I found on the web but couldn't get the right result. Thanks in advance for your help.
To further clarify, for example:
id10 row has (1,,3) and id9 row has (1,2,3). so there is two values repeating. so count is 2.
If the ids are consecutive and there are no gaps, you can do it with a self join:
select
t.*,
coalesce((t.col1 = tt.col1), 0) +
coalesce((t.col2 = tt.col2), 0) +
coalesce((t.col3 = tt.col3), 0) count
from tablename t left join tablename tt
on tt.id = t.id - 1
See the demo.
Results:
| id | col1 | col2 | col3 | count |
| --- | ---- | ---- | ---- | ----- |
| 10 | 1 | | 3 | 2 |
| 9 | 1 | 2 | 3 | 2 |
| 8 | | 2 | 3 | 1 |
| 7 | | 2 | | 1 |
| 6 | 1 | 2 | | 0 |
| 5 | | | 3 | 0 |
And if there are gaps...
SELECT a.id
, a.col1
, a.col2
, a.col3
, COALESCE(a.col1 = b.col1,0) + COALESCE(a.col2 = b.col2,0) + COALESCE(a.col3 = b.col3,0) n
FROM
( SELECT x.*
, MIN(y.id) y_id
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
) a
LEFT
JOIN my_table b
ON b.id = a.y_id;
Were you to restructure your schema, then you could do something like this instead...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,val)
);
INSERT INTO my_table VALUES
(10,1),
(10,3),
( 9,1),
( 9,2),
( 9,3),
( 8,2),
( 8,3),
( 7,2),
( 7,3),
( 6,1),
( 6,2),
( 5,3);
SELECT a.id
, COUNT(b.id) total
FROM
( SELECT x.*
, MIN(y.id) next
FROM my_table x
JOIN my_table y
ON y.id > x.id
GROUP
BY x.id
, x.val
) a
LEFT
JOIN my_table b
ON b.id = a.next
AND b.val = a.val
GROUP
BY a.id;
+----+-------+
| id | total |
+----+-------+
| 5 | 0 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
+----+-------+
You can use :
select t1_ID, t1_col1,t1_col2,t1_col3, count
from
(
select t1.id as t1_ID, t1.col1 as t1_col1,t1.col2 as t1_col2,t1.col3 as t1_col3, t2.*,
case when t1.col1 = t2.col1 then 1 else 0 end +
case when t1.col2 = t2.col2 then 1 else 0 end +
case when t1.col3 = t2.col3 then 1 else 0 end as count
from tab t1
left join tab t2
on t1.id = t2.id + 1
order by t1.id
) t3
order by t1_ID desc;
Demo
If there are gaps between id values for the next row, you could have user defined variables to explicitly assign values to rows in their natural ordering in the table. Rest logic remains the same as already answered. You would do an inner join between current row number and next row number to get the col1,col2 and col3 values and use coalesce for computation of count.
select derived_1.*,
coalesce((derived_1.col1 = derived_2.col1), 0) +
coalesce((derived_1.col2 = derived_2.col2), 0) +
coalesce((derived_1.col3 = derived_2.col3), 0) count
from (
select #row := #row + 1 as row_number,t1.*
from tablename t1,(select #row := 0) d1
) derived_1
left join (
select *
from (
select #row2 := #row2 + 1 as row_number,t2.*
from tablename t2,(select #row2 := 0) d2
) d3
) derived_2
on derived_1.row_number + 1 = derived_2.row_number;
Demo: https://www.db-fiddle.com/f/wAzb67zSEfbZKg5RywQvC8/1
is there a way to reverse an entire column?
Example:
ID ColX ColY ColZ
0 001 010 100
1 002 020 200
2 003 030 300
shall be:
ID ColX ColY ColZ
0 003 030 300
1 002 020 200
2 001 010 100
So the Column ID shall be reversed, the record with the last ID shall be the first, the second last the second first and so far.
The newest value has ID = 0 and the oldest ID = n, and this must be exactly reversed, else I cannot insert new records.
You can do it using variables:
SELECT t2.ID, ColX, ColY, ColZ
FROM (SELECT ID, ColX, ColY, ColZ,
#row_number := #row_number + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #row_number := 0) AS var
ORDER BY ID) AS t1
INNER JOIN (
SELECT ID, #rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) AS var
ORDER BY ID DESC) AS t2
ON t1.rn = t2.rn
ORDER BY t2.ID
Demo here
If you want to UPDATE then you can use the above query in an UPDATE statement like this:
UPDATE mytable AS t
INNER JOIN(
SELECT ID, ColX, ColY, ColZ,
#row_number := #row_number + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #row_number := 0) AS var
ORDER BY ID) AS t1 ON t.ID = t1.ID
INNER JOIN (
SELECT ID, #rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) AS var
ORDER BY ID DESC) AS t2 ON t1.rn = t2.rn
SET t.ID = t2.ID
Demo here
The above will work irrespective of the values of ID column.
If you don't have gaps in your ID, then you can use this select query:
SELECT
max_id-ID AS ID,
ColX,
ColY,
ColZ
FROM
mytable CROSS JOIN (SELECT MAX(ID) AS max_id FROM mytable) m
ORDER BY
ID
or this update query (but it will work only if ID is not a primary key):
UPDATE
mytable m1 CROSS JOIN (SELECT MAX(ID) as max_id FROM mytable) m
SET
m1.ID = m.max_id - m1.ID
if it's a primary key you could use this:
UPDATE
mytable m1 CROSS JOIN (SELECT MAX(ID) as max_id FROM mytable) m
INNER JOIN mytable m2 ON m1.ID = m.max_id - m2.ID
SET
m1.ColX = m2.ColX,
m1.ColY = m2.ColY,
m1.ColZ = m2.ColZ
(please see it here)
but if your ID column has gaps (e.g. 0, 1, 2, 5, 6) you need a different approach.
SELECT
#x - t.id AS new_id,
t.*
FROM tab t,
( SELECT #x:= max(id+1) FROM tab ) m;
MariaDB [tmp]> SELECT
-> #x - t.id AS new_id,
-> t.*
-> FROM tab t,
-> ( SELECT #x:= max(id+1) FROM tab ) m;
+--------+----+--------+---------------------+
| new_id | id | action | date |
+--------+----+--------+---------------------+
| 7 | 1 | 2 | 2015-09-24 15:28:30 |
| 6 | 2 | 4 | 2015-09-24 15:29:26 |
| 5 | 3 | 2 | 2015-09-24 15:30:01 |
| 4 | 4 | 3 | 2015-09-24 15:30:55 |
| 3 | 5 | 1 | 2015-09-24 16:07:25 |
| 2 | 6 | 5 | 2015-09-24 16:10:25 |
| 1 | 7 | 4 | 2015-09-24 16:29:26 |
+--------+----+--------+---------------------+
7 rows in set (0.00 sec)
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
My table
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | (null) |
| S2 | 55 | (null) |
| S3 | 56 | (null) |
| S4 | 55 | (null) |
| S5 | 52 | (null) |
| S6 | 51 | (null) |
| S7 | 53 | (null) |
+------+-------+--------+
Refer : http://www.sqlfiddle.com/#!2/5d046/1
I would like to add 3,2,1 points to the highest Marks. Here S3 goes to 3 points, S2,S4 goes to 2 points and S1,S7 goes to 1 points.
Final outputs looks,
+------+-------+--------+
| NAME | MARKS | POINTS |
+------+-------+--------+
| S1 | 53 | 1 |
| S2 | 55 | 2 |
| S3 | 56 | 3 |
| S4 | 55 | 2 |
| S5 | 52 | 0 |
| S6 | 51 | 0 |
| S7 | 53 | 1 |
+------+-------+--------+
Plz help
My suggestion is that you first calculate the ranking of each mark, and then use that in a case statement in an update.
The following query shows one way to calculate the ranking:
select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc;
(As a note: I am a bit uncomfortable with this method, because MySQL does not guarantee the order of evaluation of the two expressions with constants. If #marks were set before #rn, then it wouldn't work. In practice, that does not seem to happen. And, this is more efficient that the equivalent with a correlated subquery.)
You can then put this into an update using join:
update myTable join
(select t.*,
#rn := if(#marks = marks, #rn, #rn + 1) as ranking,
#marks := marks
from myTable t cross join
(select #rn := 0, #marks := -1) const
order by t.marks desc
) mr
on myTable.Name = mr.Name
set myTable.Points := (case when mr.ranking = 1 then 3
when mr.ranking = 2 then 2
when mr.ranking = 3 then 1
else 0
end);
This has been tested on your SQL Fiddle.
You can do it via variables (see samples in other answers), or via case:
select
myTable.*,
case
when max1.marks is not null then 3
when max2.marks is not null then 2
when max3.marks is not null then 1
else 0
end as score
from
myTable
LEFT JOIN
(select marks from myTable order by marks desc limit 1) AS max1
ON myTable.marks=max1.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 2,1) AS max2
ON myTable.marks=max2.marks
LEFT JOIN
(select marks from myTable order by marks desc limit 3,1) AS max3
ON myTable.marks=max3.marks;
the demo can be found here.
UPDATE myTable t1
INNER JOIN
(
SELECT #row:=#row-1 AS RowPoints, Marks
FROM (
SELECT Marks
FROM myTable
GROUP BY Marks
ORDER BY Marks DESC
LIMIT 3
) AS TopMarks
INNER JOIN (SELECT #row:=4) AS RowInit
) AS AddPoints ON t1.Marks = AddPoints.Marks
SET Points = COALESCE(Points, 0) + AddPoints.RowPoints;
This should work just fine. You should and and index on the Marks column.
The simplest way to do this:
SELECT t.Name Name, t.Marks Marks,
(CASE WHEN Marks = (Select max(marks) from mytable) THEN 3 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 2) a) THEN 2 ELSE 0 END+
CASE WHEN Marks = (Select min(marks) from (Select distinct marks
from mytable order by marks desc limit 3) b) THEN 1 ELSE 0 END)
AS `Points`
FROM mytable t;
SQL Fiddle