display two tables into one using select sql - mysql

I have Table A
============
| id | val |
=====+======
| 1 | abc |
| 1 | def |
| 2 | ghi |
| 2 | jkl |
============
I have Table B
============
| id | val2 |
=====+======
| 1 | rty |
| 1 | vbn |
| 2 | uio |
| 2 | zxc |
============
I want to display the two tables like this..
===================
| id | val | val2 |
=====+=====+=======
| 1 | abc | rty |
| 1 | def | vbn |
| 2 | ghi | uio |
| 2 | jkl | zxc |
===================
my problem is i am having redundancy..

Yes, you have a problem because you don't have a proper join key. You can do this by using variables to create one. Something like this will work for the data you provide:
select min(id), max(aval), max(bval)
from ((select id, val as aval, NULL as bval, #rna := #rna + 1 as seqnum
from tablea a cross join (select #rna := 0)
) union all
(select id, NULL val, #rnb := #rnb + 1 as seqnum
from tableb b cross join (select #rnb := 0)
)
) ab
group by seqnum;

I like Gordon's approach, since it doesn't assume the same number of rows in each table, but here's a JOIN version:
SELECT a.id,a.val,b.val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val
FROM Table1 a cross join (select #row_number := 0) b
)a
JOIN (SELECT #row_number2:=#row_number2+1 AS row_number2
,id,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)b
ON a.Row_Number = b.Row_Number2
AND a.id = b.id
Here's a working version of his UNION version:
SELECT Row_Number,ID,MAX(Val) AS Val,MAX(Val2) AS Val2
FROM (SELECT #row_number:=#row_number+1 AS row_number
,id,val,NULL as Val2
FROM Table1 a cross join (select #row_number := 0) b
UNION ALL
SELECT #row_number2:=#row_number2+1 AS row_number
,id,NULL,val2
FROM Table2 a cross join (select #row_number2 := 0) b
)sub
GROUP BY Row_Number,ID
Demo of both: SQL Fiddle

You can simply do this using INNER JOIN. See my query below:
SELECT A.id,val,val2 FROM
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val
FROM TableA, (SELECT #row_number:=0) AS t ORDER BY val) AS A
INNER JOIN
(SELECT
#row_number:=#row_number+1 AS RowNumber,
id,
val2
FROM TableB, (SELECT #row_number:=0) AS t ORDER BY val2) AS B
ON A.RowNumber=B.RowNumber

Related

How do you revert the cartesian product of two columns in SQL in order to get the columns before computing the cross join (cartesian product)?

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 |
+---------+---------+

MySQL - group by certain value in the list of rows

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

Points on highest record in Mysql

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

MySQL calculate score by rank in percentage

I am using MYSQL to create a rating system to implement my database. What I want to do is to rate each attribute by its percentage with some calculation. Here is the example database:
| ID | VALUE1 | VALUE2|
-----------------------
| 2 | 5 | 20 |
| 4 | 5 | 30 |
| 1 | 3 | 5 |
| 3 | 2 | 8 |
Here is the ideal output I need:
| ID | VALUE1 | RANK1 | Score1 | VALUE2 | RANK2 | Score2 |
---------------------------------------------------------
| 2 | 5 | 1 | 10 | 20 | 2| 8.3|
| 4 | 5 | 1 | 10 | 30 | 1| 10|
| 1 | 3 | 2 | 7.5| 5 | 4| 5|
| 3 | 2 | 3 | 5 | 8 | 3| 6.6|
The formula for score calculation is
5+5*(MaxRank-rank)/(MaxRank-MinRank)
How to generate multiple ranking like the table? I have tried
SELECT
#min_rank := 1 AS min_rank
, #max_rank1 := (SELECT COUNT(DISTINCT value1) FROM table) AS max_rank1
, #max_rank2 := (SELECT COUNT(DISTINCT value2) FROM table) AS max_rank2
;
SELECT
ID
, R1
, TRUNCATE(5.0+5.0 * (#max_rank1 - R1) / (#max_rank1 - #min_rank), 2) AS Score1
, R2
, TRUNCATE(5.0+5.0 * (#max_rank2 - R2) / (#max_rank2 - #min_rank), 2) AS Score2
FROM (
SELECT
ID
, value1
, FIND_IN_SET( `value1`, (SELECT GROUP_CONCAT(DISTINCT `value1` ORDER BY `value1` DESC) FROM table)) AS R1
, value2
, FIND_IN_SET( `value2`, (SELECT GROUP_CONCAT(DISTINCT `value2` ORDER BY `value2` DESC) FROM table)) AS R2
FROM table
) ranked_table;
It works fine with ranking below 170. My database has approximate 200+ ranking for some values and ranks larger then 170 will be seen as 0 when it returns. In that case, the scores with ranks >170 will be miscalculated. Thank you guys.
That looks nasty to calculate.
Something like this might do it
SELECT a.ID, a.VALUE1, Sub1.Rank1, (5.0+5.0 * (Sub3.MaxRank1 - Sub1.Rank1) / (Sub3.MaxRank1 - 1)) AS Score1, a.VALUE2, Sub2.Rank2, (5.0+5.0 * (Sub4.MaxRank2 - Sub2.Rank2) / (Sub4.MaxRank2 - 1)) AS Score2
FROM TestTable a
INNER JOIN (SELECT DISTINCT z.VALUE1, (SELECT ((COUNT(DISTINCT VALUE1) + 1)) FROM TestTable y WHERE z.VALUE1 < y.VALUE1) AS RANK1
FROM TestTable z
) Sub1 ON a.VALUE1 = Sub1.VALUE1
INNER JOIN (SELECT DISTINCT z.VALUE2, (SELECT ((COUNT(DISTINCT VALUE2) + 1)) FROM TestTable y WHERE z.VALUE2 < y.VALUE2) AS RANK2
FROM TestTable z
) Sub2 ON a.VALUE2 = Sub2.VALUE2
CROSS JOIN (SELECT COUNT(*) + 1 AS MaxRank1 FROM TestTable CROSS JOIN (SELECT MAX(VALUE1) AS MaxValue1 FROM TestTable) Sub3a WHERE VALUE1 < MaxValue1) Sub3
CROSS JOIN (SELECT COUNT(*) + 1 AS MaxRank2 FROM TestTable CROSS JOIN (SELECT MAX(VALUE2) AS MaxValue2 FROM TestTable) Sub4a WHERE VALUE2 < MaxValue2) Sub4
Note I am not sure on your score calculation. The equation you give doesn't appear to me to give the results in your example. But I might just be misreading it.

How to combine near same item by SQL?

I have some data in database:
id user
1 zhangsan
2 zhangsan
3 zhangsan
4 lisi
5 lisi
6 lisi
7 zhangsan
8 zhangsan
I want keep order, and combine near same user items, how to do it?
When I use shell script, I will(data in file test.):
cat test|cut -d " " -f2|uniq -c
this will get result as:
3 zhangsan
3 lisi
2 zhangsan
But how to do it use sql?
If you try:
SET #name:='',#num:=0;
SELECT id,
#num:= if(#name = user, #num, #num + 1) as number,
#name := user as user
FROM foo
ORDER BY id ASC;
This gives:
+------+--------+------+
| id | number | user |
+------+--------+------+
| 1 | 1 | a |
| 2 | 1 | a |
| 3 | 1 | a |
| 4 | 2 | b |
| 5 | 2 | b |
| 6 | 2 | b |
| 7 | 3 | a |
| 8 | 3 | a |
+------+--------+------+
So then you can try:
SET #name:='',#num:=0;
SELECT COUNT(*) as count, user
FROM (
SELECT #num:= if(#name = user, #num, #num + 1) as number,
#name := user as user
FROM foo
ORDER BY id ASC
) x
GROUP BY number;
Which gives
+-------+------+
| count | user |
+-------+------+
| 3 | a |
| 3 | b |
| 2 | a |
+-------+------+
(I called my table foo and also just used names a and b because I was too lazy to write zhangsan and lisi over and over).
if in oracle, you can do like below.
SELECT NAME,
num - lagnum
FROM (SELECT lagname,
NAME,
num,
nvl(lag(num) over(ORDER BY num), 0) lagnum
FROM (SELECT id,
lag(NAME) over(ORDER BY ID) lagname,
NAME,
lead(NAME) over(ORDER BY ID) leadname,
ROWNUM num
FROM (SELECT * FROM test ORDER BY ID))
WHERE (lagname = NAME AND (NAME <> leadname OR leadname IS NULL))
OR (lagname IS NULL AND NAME <> leadname)
OR (lagname <> NAME AND leadname IS NULL)
ORDER BY ID);
if in sql server, oracle, db2...
with x as(
select c.*, rn = row_number() over (order by c.id)
from test c
left join test n
on c.[user] = n.[user]
and c.[id] + 1 = n.[id]
where n.id is null
)
select a.[user], a.id - coalesce(b.id, 0)
from x a
left join x b
on a.rn = b.rn + 1
I think what you are looking for is to COUNT(ID):
SELECT COUNT(ID) FROM table GROUP BY user
You cannot do this in sql without doing some sort of sequential (iterative) analysis. Remember sql is set operation language.
A little improvement to the selected answer would be not to have to define those variables. So this query can be solved in just a single statement:
SELECT COUNT(*) cnt, user
FROM (
SELECT #num := #num + (#name != user) as number,
#name := user as user
FROM t, (select #num := 0, #name := '') as s
ORDER BY id
) x
GROUP BY number
Output:
| CNT | USER |
|-----|----------|
| 3 | zhangsan |
| 3 | lisi |
| 2 | zhangsan |
Fiddle here