Merge column of two tables without relationship - mysql

I need to merge columns of two tables with different rows. It should put NULL value into table with less rows.
Example :
Assume these two simple tables :
table_one
+----+-----------+---------------------+
| id | title | date |
+----+-----------+---------------------+
| 10 | Good | 2014-10-08 05:13:00 |
| 11 | NotBad | 2014-10-24 00:00:00 |
| 12 | Excellent | 2014-10-26 14:00:00 |
| 13 | Bad | 2014-10-11 19:31:23 |
+----+-----------+---------------------+
table_two
+----+------+
| id | name |
+----+------+
| 1 | Sara |
| 2 | Alex |
+----+------+
What output I need :
+----+-----------+---------------------+------+------+
| id | title | date | id | name |
+----+-----------+---------------------+------+------+
| 10 | Good | 2014-10-08 05:13:00 | 1 | Sara |
| 11 | NotBad | 2014-10-24 00:00:00 | 2 | Alex |
| 12 | Excellent | 2014-10-26 14:00:00 | NULL | NULL |
| 13 | Bad | 2014-10-11 19:31:23 | NULL | NULL |
+----+-----------+---------------------+----+------+
What I've tried so far :
SELECT table_one.*, table_two.* FROM table_one, table_two
But that's not my desire! It will return Cartesian Product
P.S :
These two tables has not any relationship between each other.

If you assume the first table has more rows, you can do this by generating a key using variables:
select t1.*, t2.*
from (select t1i.*, (#rn1 := #rn1 + 1) as rn
from table_one t1i cross join (select #rn1 := 0) vars
) t1 left join
(select t2i.*, (#rn2 := #rn2 + 1) as rn
from table_two t2i cross join (select #rn2 := 0) vars
) t2
on t1.rn = t2.rn;
You will have two surplus columns called rn, if you don't need that, list your columns instead of select t1.*, t2.*.

Related

Get greatest rows based on multiple columns in MySQL

I have a table that looks like:
id | title | value | language
---+-------+-------+---------
1 | a | 1800 | NULL
2 | a | 1900 | NULL
3 | b | 1700 | NULL
4 | b | 1750 | NULL
5 | b | 1790 | 1
6 | c | 1892 | NULL
7 | c | 1900 | 1
8 | c | 1910 | 2
9 | d | 3020 | NULL
Would like to have the following result:
id | title | value | language
---+-------+-------+---------
2 | a | 1900 | NULL
4 | b | 1750 | NULL
5 | b | 1790 | 1
6 | c | 1892 | NULL
7 | c | 1900 | 1
8 | c | 1910 | 2
9 | d | 3020 | NULL
The point is to select the greatest value in value column of every language of every title - greatest being the latest. Secondly, would like to avoid Aggregate functions like MAX, DISTINCT or GROUP-BY as I am building a MySQL View using the MERGE algorithm, and don't want to end up creating a temporary table (See the bottom section of https://dev.mysql.com/doc/refman/5.6/en/view-algorithms.html).
So far this works, but only returns greatest row per title:
SELECT t1.title
FROM table t1
LEFT OUTER JOIN table t2
ON t1.title = t2.title
AND t1.value < t2.value
WHERE t2.title IS NULL
How can I create one that takes language into account like the results above? Thanx.
You can do it with NOT EXISTS:
select t.*
from tablename t
where not exists (
select 1 from tablename
where
title = t.title and
coalesce(language, 0) = coalesce(t.language, 0) and
value > t.value
)
See the demo.
Results:
| id | title | value | language |
| --- | ----- | ----- | -------- |
| 2 | a | 1900 | NULL |
| 4 | b | 1750 | NULL |
| 5 | b | 1790 | 1 |
| 6 | c | 1892 | NULL |
| 7 | c | 1900 | 1 |
| 8 | c | 1910 | 2 |
| 9 | d | 3020 | NULL |
This answer assumes that you are using MySQL 8+, in which your query becomes very easy. MySQL 8 and later version support analytic functions, which were added with the intention to solve problems such as this.
We can try using ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY title, language ORDER BY value DESC) rn
FROM yourTable
)
SELECT id, title, value, language
FROM cte
WHERE rn = 1;
Demo
There is a way to handle this with earlier versions of MySQL, but it requires user variables, and tends to be very ugly. So maybe consider upgrading if you expect to have many queries similar to this one.
This should give you what you want.
SELECT t1.title, t1.value, t1.language
FROM [Table] t1
LEFT OUTER JOIN [Table] t2 ON
t1.title = t2.title AND
(IFNULL(t1.language, '') = IFNULL(t2.language, ''))
WHERE
t1.value > t2.value;

Sequentially update mysql table

Given the following table, how can I sequentially reorder position from 1 to N using a single query after one or more rows have been deleted and still preserve the order of position?
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 18 | 3 | 123 |
| 5 | 4 | 123 |
| 3 | 5 | 123 |
+---------+----------+-----+
For instance, if position=1 (id=4) was deleted, the desired final records are:
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 2 | 1 | 123 |
| 18 | 2 | 123 |
| 5 | 3 | 123 |
| 3 | 4 | 123 |
+---------+----------+-----+
and if position=3 (id=18) was deleted, the desired final records are:
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 5 | 3 | 123 |
| 3 | 4 | 123 |
+---------+----------+-----+
I can do something like the following if only row was deleted but not for multiple rows.
DELETE FROM mytable WHERE fk=123 AND position = 4;
UPDATE mytable SET position=position-1 WHERE fk=123 AND position > 4;
User-defined variables to the rescue if you're not already using MySQL 8, which provides window functions like ROW_NUMBER():
UPDATE t
JOIN (
SELECT
t.*
, #n := #n + 1 as n
FROM t
, (SELECT #n := 0) var_init
ORDER BY position
) sq ON t.id = sq.id
SET t.position = sq.n;
see it working live in an sqlfiddle
BONUS:
It gets slightly more complicated, when you have multiple groups.
For example, for sample data like this
| id | position | fk |
|-----|----------|-----|
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 5 | 4 | 123 |
| 3 | 5 | 123 |
| 40 | 1 | 234 |
| 20 | 2 | 234 |
| 180 | 3 | 234 |
| 30 | 5 | 234 |
the query would be
UPDATE t
JOIN (
SELECT
t.*
, #n := if(#prev_fk != fk, 1, #n + 1) as n
, #prev_fk := fk
FROM t
, (SELECT #n := 0, #prev_fk := NULL) var_init
ORDER BY fk, position
) sq ON t.id = sq.id
SET t.position = sq.n;
Here you just save the current fk in another variable. When the next row is processed, the variable still holds the value of the "previous row". Then you reset the #n variable, when the value changes.
see it working live in an sqlfiddle
UPDATE:
In MySQL 8 you can use the window function row_number() like this:
update t join (
select t.*, row_number() over (partition by fk order by position) as new_pos
from t
) sq using (id) set t.position = sq.new_pos;
You can use update and the ROW_NUMBER() function. If you order by position you it should be ok.
UPDATE [1]
SET POSITION = [2].RN
FROM t [1]
JOIN (
SELECT
t.ID
, ROW_NUMBER() OVER (ORDER BY POSITION DESC) AS RN
FROM t
) [2]
ON [1].id = [2].id
As people have mentioned this is not applicable for MySql. Sorry for the missinformation as i didnt saw the tag.

mysql, update foreach category

it is a really simple question. it is only 1 table!
i have a table of books which each book have a category and a number.
SO I WANNA TRANSFORM THIS
+--------------------------+
| book_id | category | num |
+--------------------------+
| 1 | a | 7 |
| 2 | a | 5 |
| 3 | a | 3 |
| 4 | b | 9 |
| 5 | b | 8 |
| 6 | b | 1 |
+--------------------------+
INTO THIS,
+--------------------------+
| book_id | category | num |
+--------------------------+
| 3 | a | 3 |
| 2 | a | 5 |
| 1 | a | 7 |
| 6 | b | 1 |
| 5 | b | 8 |
| 4 | b | 9 |
+--------------------------+
AND THEN THIS!
+--------------------------+
| book_id | category | num |
+--------------------------+
| 3 | a | 1 |
| 2 | a | 2 |
| 1 | a | 3 |
| 6 | b | 1 |
| 5 | b | 2 |
| 4 | b | 3 |
+--------------------------+
BUT HOW?!?!?!?!
script to create table...
drop table if exists books;
CREATE TABLE books(
id int AUTO_INCREMENT,
category varchar(30),
num int,
PRIMARY KEY (id)
);
insert into books (category,num)
values
('a',7),
('a',5),
('a',3),
('b',9),
('b',8),
('b',1);
You can use user variables to generate the sequence numbers within each category in the order of increasing id.
If you just want to query the table, use:
select
b.id,
b.category,
#rn := if(#category = category, #rn + 1, if (#category := category, 1, 1)) num
from books b, (select #category := null, #rn := 0) t2
order by b.category, b.id
If you want to update your table, use:
update books b1
join (
select
b.id,
#rn := if(#category = category, #rn + 1, if (#category := category, 1, 1)) num
from books b, (select #category := null, #rn := 0) t2
order by b.category, b.id
) b2 on b1.id = b2.id
set b1.num = b2.num;
Demo
EDIT:
As per the edited question, you can use order by b.category, b.num instead.

How to calculate the number of total votes when there is two Posts tables?

Here is the structure of my tables:
// table1
+----+-------+--------------------+
| id | color | content |
+----+-------+--------------------+
| 1 | blue | it is a bad color |
| 2 | red | it is a good color |
| 3 | green | I don't like this |
+----+-------+--------------------+
// table2
+----+-------+---------------------+
| id | site | description |
+----+-------+---------------------+
| 1 | stack | help to programmers |
| 2 | google| everything you like |
+----+-------+---------------------+
// votes
+----+-----------+---------+-------+
| id | table_code| post_id | value |
+----+-----------+---------+-------+
| 1 | 1 | 1 | 1 | // table1, post1, +1upvote (blue)
| 2 | 1 | 2 | -1 | // table1, post2, -1downvote (red)
| 3 | 2 | 1 | 1 | // table2, post1, +1upvote (stack)
+----+-----------+---------+-------+
Also here is my query:
select t3.*, (select sum(value) from votes v where t3.id = v.post_id) total_votes
from (
select * from table1
union all
select * from table2
) t3
Here is my output:
+----+-------+---------------------+-------------+
| id | color | content | total_votes |
+----+-------+---------------------+-------------+
| 1 | blue | it is a bad color | 2 | // Problem (it should be 1)
| 2 | red | it is a good color | -1 |
| 3 | green | I don't like this | 0 |
| 1 | stack | help to programmers | 2 | // Problem (it should be 1)
| 2 | google| everything you like | 0 |
+----+-------+---------------------+-------------+
As you see in the this ^ table, the calculation of total_votesis wrong. How can I fix it?
Note: According to reality, I can not combine table1 and table2. So, please don't tell me your structure is crazy.
You have to also specify table_code in the UNION:
select t3.*,
(select sum(value)
from votes v
where t3.id = v.post_id and
v.table_code = t3.table_code) total_votes
from (
select *, 1 table_code from table1
union all
select *, 2 from table2
) t3
Using table_code in the correlated sub-query we can pick the correct values from votes table.

Is It Possible To Join Two Unrelated Tables In MySQL?

I have two unrelated tables but i want to join them into one query, is that possible?
This is how I did it using cross join but it did not work
table 1
| ID | Amount |
| 1 | 20 |
| 2 | 10 |
| 3 | 21 |
| 4 | 50 |
table 2
| ID | Paid Value |
| 011 | 5 |
| 052 | 2 |
//My tried Query
SELECT
a.`Amount`,
b.`Paid Value`
FROM
`table 1` a
CROSS JOIN
`table 2` b
This is what i get in return using the above query
| ID | Amount | Paid Value |
| 1 | 20 | 5 |
| 2 | 10 | 2 |
| 3 | 21 | 5 |
| 4 | 50 | 2 |
However this is my expected results
| ID | Amount | Paid Value |
| 1 | 20 | 5 |
| 2 | 10 | 2 |
| 3 | 21 | 0 |
| 4 | 50 | 0 |
You want to join by some implicit row number. Let me assume that this is based on the ordering of the ids. You can use variables to calculate the row number and then use that for the join:
select t1.id, t1.amount, coalesce(t2.paidvalue, 0)
from (select t1.*, (#rn := #rn + 1) as rn
from table1 t1 cross join
(select #rn := 0) vars
order by id
) t1 left join
(select t2.*, (#rn2 := #rn2 + 1) as rn
from table1 t2 cross join
(select #rn2 := 0) vars
order by id
) t2
on t1.rn = t2.rn;