Let's say I have 2 simple tables
Table t1 Table t2
+------+ +------+
| i | | j |
+------+ +------+
| 42 | | a |
| 1 | | b |
| 5 | | c |
+------+ +------+
How can I have an output of the 2 tables, joined without any condition except the row number?
I would like to avoid the creation of another index if possible.
I am using MySQL 5.7
With this example, the output would be :
Table output
+------+------+
| i | j |
+------+------+
| 42 | a |
| 1 | b |
| 5 | c |
+------+------+
What you ask can be done, assuming that your comment is true;
"Even if table i and j are subqueries (containing order by)?"
Schema (MySQL v5.7)
CREATE TABLE table_1 ( i INT );
CREATE TABLE table_2 ( j VARCHAR(4) );
INSERT INTO table_1
VALUES (3),(5),(1);
INSERT INTO table_2
VALUES ('c'), ('b'),('a');
Query
SELECT t1.i, t2.j
FROM (SELECT t1.i
, #rownum1 := #rownum1 + 1 AS rownum
FROM (SELECT table_1.i
FROM table_1
ORDER BY ?) t1
CROSS JOIN (SELECT #rownum1 := 0) v) t1
JOIN (SELECT t2.j
, #rownum2 := #rownum2 + 1 AS rownum
FROM (SELECT table_2.j
FROM table_2
ORDER BY ?) t2
CROSS JOIN (SELECT #rownum2 := 0) v) t2 ON t2.rownum = t1.rownum;
However, this approach is a) not efficient, and b) indicative of questionable design. You probably want to look for something that actually relates your two tables or, if nothing exists, create something. If there is really nothing that relates the two tables, then you'll have trouble with the ORDER BY clauses anyway.
If the tables do not necessarily have the same number of rows, then use union all and group by -- along with variables:
select max(t.i) as i, max(t.j) as j
from ((select (#rn1 := #rn1 + 1) as seqnum, t1.i
from t1 cross join
(select #rn1 := 0) params
) union all
(select (#rn2 := #rn2 + 1) as seqnum, t2.j
from t2 cross join
(select #rn2 := 0) params
)
) t
group by seqnum;
Note: The results in each column are in an arbitrary and indeterminate order. The order might vary on different runs on the query.
You don't provide enough information to ensure the ordering.
you can try this code
select t1.i,t2.j
from
(SELECT i,#row_num:=#row_num+1 as row_num FROM t1, (SELECT #row_num:= 0) AS sl) t1
join
(SELECT j,#row_num:=#row_num+1 as row_num FROM t2, (SELECT #row_num:= 0) AS sl) t2
on t1.row_num=t2.row_num
Related
I am trying to update position for my player in present in table.
This table consists of name , id, points and position.
Default value of points is 0 then position will be Unranked.
If two users have same points then there positions will be same.
Demo table
id | name | points | position
1 | a | 0 | Unranked
2 | b | 120 | 2
3 | c | 130 | 3
4 | d | 120 | 1
Required result should be
id | name | points | position
1 | a | 0 | Unranked
2 | b | 120 | 2
3 | c | 130 | 1
4 | d | 120 | 2
Query will be like for unranked update mytable set position = 'Unranked' Where points = 0
How will i use points and position set query ?
There's no need to hold the computed column position in the table. The following works for all versions :
create table tab ( id int, name varchar(1), points int );
insert into tab values
(1,'a', 0),
(2,'b',120),
(3,'c',130),
(4,'d',120);
select t.id, t.name, t.points,
( case when points = 0 then 'Unranked' else t.rnk end ) as position
from
(
select t1.*,
#rnk := if(#pnt = points,#rnk,#rnk + 1) rnk,
#pnt := points
from tab t1
cross join (select #rnk := 0, #pnt := 0 ) t2
order by points desc
) t
order by t.id;
id name points position
-- ---- ------ --------
1 a 0 Unranked
2 b 120 2
3 c 130 1
4 d 120 2
If you want to hold the column position in your table, then you can use the following update statement by binding through primary column id :
update tab tt
set position = ( select
( case when points = 0 then 'Unranked' else t.rnk end ) as position
from
(
select t1.*,
#rnk := if(#pnt = points,#rnk,#rnk + 1) rnk,
#pnt := points
from tab t1
cross join (select #rnk := 0, #pnt := 0 ) t2
order by points desc
) t
where t.id = tt.id );
Rextester Demo
This is a pain. You can get the results you want with a subquery, but that doesn't quite work in an update clause. In a select, you can do:
select t.*,
(select 1 + count(*)
from t t2
where t2.points > 0 and t2.points > t.points
) as rank
from t;
You can now incorporate this into an update:
update t join
(select t.*,
(select 1 + count(*)
from t t2
where t2.points > 0 and t2.points > t.points
) as new_position
from t;
) tt
on t.id = tt.id
set t.position = tt.new_position
where t.points > 0;
If your version of MySQl (MySQL 8.x) supports window function then following is possible:
SELECT name,
RANK() OVER (
ORDER BY points DESC
) position
FROM mytable
where points != 0
Selected data can be then joined for the update like in the answer from Gordon Linoff.
Table 1
ID | NAME | WARD_ID|
1 A 1
2 B 1
3 C 2
4 D 2
Table 2
ID | MONTH1 | MONTH2 | WARD_ID|
1 9 10 1
2 6 11 1
3 5 12 2
4 13 14 2
I want to join this two table and produce the following output:
ID | NAME | MONTH1 | MONTH2 | WARD_ID|
1 A 9 10 1
2 B 6 11 1
3 C 5 12 2
4 D 13 14 2
In the ON condition of the query I have to keep WARD_ID equal for both the tables. I could not able to figure out the solution. Anyone have any experience with a query like this?
I think you want something like this:
select t1.*, t2.*
from (select t1.*,
(#rn1 := if(#w1 = ward_id, #rn1 + 1,
if#w1 := ward_id, 1, 1)
)
) as rn
from (select t1.* from table1 t1 order by ward_id, id ) t1 cross join
(select #w1 := -1, #rn1 := -1) params
) t1 join
(select t2.*,
(#rn2 := if(#w2 = ward_id, #rn2 + 1,
if#w2 := ward_id, 1, 1)
)
) as rn
from (select t2.* from table2 t2 order by ward_id, id ) t2 cross join
(select #w2 := -1, #rn1 := -1) params
) t1
on t2.ward_id = t1.ward_id and t2.rn = t1.rn;
The subqueries enumerate the rows in each table. The join then uses the enumeration.
This is much simpler in MySQL 8.0, using row_number().
I'm assuming here that ID is intended to be the same from both tables. If so, I think you can do a multi-condition join:
select * from table1 t1
inner join table2 t2
on t1.ID=t2.ID and t1.WARD_ID=t2.WARD_ID
You can do something like:
SET #rn:=0;
SET #rn2:=0;
SELECT *
FROM (
SELECT #rn:=#rn+1 AS rn1, t1.ID, t1.NAME, t1.WARD_ID
FROM t1
GROUP BY t1.WARD_ID, t1.NAME
ORDER BY t1.WARD_ID, t1.NAME
) s1
INNER JOIN (
SELECT #rn2:=#rn2+1 AS rn2, t2.ID, t2.MONTH1, t2.MONTH2, t2.WARD_ID
FROM t2
GROUP BY t2.WARD_ID, t2.MONTH1,t2.MONTH2
ORDER BY t2.WARD_ID, t2.MONTH1,t2.MONTH2
) s2 ON s1.WARD_ID = s2.WARD_ID
AND s1.rn1 = s2.rn2
But it really doesn't reliably sort the tables to join the same rows every time. I still think there isn't a reliable/repeatable way to join the two tables the same every time.
============================================================
http://sqlfiddle.com/#!9/aa2db0/1 <<<< If ID can be used to reliably sort the two tables, you can use it in the ORDER BYs. I've added it in this Fiddle, and included rows in the setup that would fall before the existing records and potentially change the sorting. This also includes more records in Table 2 than there are in Table 1, so would possibly result in duplicated rows. These new rows are ignored since they can't be matched between tables.
Question
Please consider the following table:
+--------------+--------+--------+
| transactionID | Sgroup | Rgroup |
+--------------+--------+--------+
| 1 | A | I |
| 1 | A | J |
| 2 | B | B |
| 2 | B | K |
+--------------+--------+--------+
For each transactionID (2 rows are associated with ID 1, two rows with ID 2) I want to select the row for which Sgroup = Rgroup, if any row within a transactionID satisfies the condition. Otherwise, I want to select a row at random. For each transactionID at most one row satisfies Sgroup = Rgroup. How can I do this?
Attempted Solution
I know how to select rows for which the condition Sgroup = Rgroup is fulfilled as follows:
SELECT *
FROM Transaction
WHERE Sgroup = Rgroup;
+---------------+--------+--------+
| transactionID | Sgroup | Rgroup |
+---------------+--------+--------+
| 2 | B | B |
+---------------+--------+--------+
I also know how to chose a row randomly (thanks to this question) if the condition is not fulfilled as follows:
SELECT * FROM
(SELECT *
FROM Transaction
WHERE NOT transactionID IN
(SELECT transactionID
FROM Transaction
WHERE Sgroup = Rgroup)
ORDER BY RAND()) AS temp
GROUP BY temp.transactionID;
+---------------+--------+--------+
| transactionID | Sgroup | Rgroup |
+---------------+--------+--------+
| 1 | A | I |
+---------------+--------+--------+
How can I combine these two expressions into one? I tried working with a CASE expression I didn't get far. Can somebody kindly suggest a solution?
Example Code Here is the code to generate the table:
CREATE DATABASE MinimalExample;
USE MinimalExample;
CREATE TABLE Transaction (
transactionID int,
Sgroup nvarchar(1),
Rgroup nvarchar(1)
);
INSERT INTO Transaction VALUES
(1,'A','I'),
(1,'A','J'),
(2,'B','B'),
(2,'B','K');
I think variables might be the simplest solution if you really mean "random":
select t.*
from (select t.*,
(#rn := if(#i = transactionID, #rn + 1,
if(#i := transactionID, 1, 1)
)
) as rn
from (select t.*
from t
order by transactionID, (sgroup = rgroup) desc, rand()
) t cross join
(select #i := -1, #rn := 0) params
) t
where rn = 1;
If by "random" you mean "arbitrary", you can use this quick-and-dirty trick:
(select t.*
from t
where sgroup = rgroup
)
union all
(select t.*
from t
where not exists (select 1 from t t2 where t2.id = t.id and t2.sgroup = t2.rgroup)
group by transactionID
);
This uses the dreaded select * with group by, something which I strongly discourage using under almost all circumstances. However, in this case, you are specifically trying to reduce each group to an indeterminate row, so it doesn't seem quite so bad. I will note that MySQL does not guarantee that the columns in the result set all come from the same row, although in practice they do.
Finally, if you had a unique primary key on each row, you could use probably the simplest solution:
select t.*
from t
where t.id = (select t2.id
from t t2
where t2.transactionID = t.transactionID
order by (rgroup = sgroup) desc, rand()
);
I have tried several methods to select multiple columns in a table for unique or distinct data from a table, including queries like:
SELECT
(SELECT GROUP_CONCAT(DISTINCT a) FROM TableName),
(SELECT GROUP_CONCAT(DISTINCT b) FROM TableName),
(SELECT GROUP_CONCAT(DISTINCT c) FROM TableName);
SELECT(a, b, c) FROM TableNamegroup by 'a' order by a asc;
SELECT DISTINCT a FROM TableName
UNION
SELECT DISTINCT b FROM TableName
UNION
SELECT DISTINCT c FROM TableName;
But they either don't work or return the information in a format that I can't use. What I need is a format like this:
+--------------------+
| a | b | c |
|--------------------|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
etc......
Short of doing individual queries, is there a way to do this?
If you need three columns, then your select needs three columns. If you want unique combinations:
select distinct a, b, c
from TableName;
Is this what you want?
I suspect that you want lists of the unique ids in three columns. You can do this using variables in MySQL:
select rn, max(a) as a, max(b) as b, max(c) as c
from ((select #rna := #rna + 1 as rn, a, null as b, null as c
from (select distinct a from TableName) t cross join
(select #rna := 0) const
) union all
(select #rnb := #rnb + 1 as rn, null, b, null
from (select distinct b from TableName) t cross join
(select #rnb := 0) const
) union all
(select #rnc := #rnc + 1 as rn, null, null, c
from (select distinct c from TableName) t cross join
(select #rnc := 0) const
group by c
)
) abc
group by rn
order by rn;
Here is an example of it working in SQL Fiddle.
Here's a tough one,
How would I delete all but the last, say 3 rows, for each unique value in a different field?
Here's a visual of the problem:
id | otherfield
---------------
1 | apple <- DELETE
2 | banana <- KEEP
3 | apple <- DELETE
4 | apple <- KEEP
5 | carrot <- KEEP
6 | apple <- KEEP
7 | apple <- KEEP
8 | banana <- KEEP
How would I accomplish this in SQL?
Non tested, but something along these lines might work:
DELETE t.*
FROM table t JOIN (
SELECT id
#rowNum := IF(#otherfield <> otherfield, 1, #rowNum + 1) rn,
#otherfield := otherfield otherfield
FROM (
SELECT id, otherfield
FROM table
ORDER BY otherfield, id DESC
) t, (SELECT #otherfield := NULL, #rowNum := -1) dm
) rs ON t.id = rs.id
WHERE rs.rn > 3
Delete MyTable
Where Id In (
Select Id
From (
Select Id
, (Select COUNT(*)
From MyTable As T2
Where T2.OtherField = T.OtherField
And T2.Id > T.Id) As Rnk
From MyTable As T
) As Z
Where Z.Rnk > 2
)
Another version which might be a bit faster:
Delete MyTable
Where Id In (
Select T.Id
From MyTable As T
Left Join MyTable As T2
On T2.OtherField = T.OtherField
And T2.Id > T.Id
Group By T.Id
Having Count(T2.Id) > 2
)