I have a table with several columns, and four of them are just generic integer values (num1, num2, num3, num4) and the source of data has these values in random order. In php I would fetch the row, assign the four ints to an array, sort the array, assign the sorted values back and save the row. I would like to do this with sql. So, with a table like
id, num1, num2, num3, num4
1 6 2 9 1
2 12 3 8 4
...
I would like the results to be
id, num1, num2, num3, num4
1 1 2 6 9
2 3 4 8 12
...
I have Googled everything I can think of and scoured the Mysql ref manual but can not seem to find a way to do this. Any thoughts or should I just write a php script?
Well that's an unpivot, a sort and a pivot, but last I heard not in mysql so
maybe
Set #position = 0
CREATE TEMPORARY TABLE OrderedUnpivot AS (
Select Id,#position = #position + 1, #position % 4 as Position, Value From
(
Select Id,Position,Value From
(
select Id, 1 as Position, num1 as Value
Union
select Id, 2, num2
union
select Id, 3, num3
union
select Id, 4, num4
) depivot
Order by Id, Value
)
Select ID,V1.Value as Num1, V2.Value as Num2, V3.Value as Num3, V4.Value as Num4
From OrderedUnpivot v1
Inner join OrderedUnpivot v2 ON v1.Id = v2.ID
Inner join OrderedUnpivot v3 ON v1.Id = v3.ID
Inner join OrderedUnpivot v4 ON v1.Id = v4.ID
Messy but food for thought, oh and not tested or even syntax checked.
Related
I have a table with 6 digit numbers that can range from 0-9 and I would match that against a number in 6 categories
first number match
first two number match
first three number match
first four number match
first five number match
all numbers match
But only the highest category per matching number should be selected. An example
Number: 123456
If one has the number [123]756 then this would fall into category first three number match
On number 023456 then this would be no match
I created a fiddle for it https://www.db-fiddle.com/f/TZCrFPnJpkw4fyxA5Q6mR/1
Here an example:
Numbers:
number
123456
123000
023456
123477
133456
Number to match against: 123456 should return
common_digits
number
6
123456
3
123000
0
023456
4
123477
1
133456
What would be an efficient method? The brute force solution would be a double loop I suppose starting with 6 matches, 5 matches, ...
SELECT #number tested_number, 7 - LENGTH(nums.num) common_digits, bids.*
FROM bids
JOIN (SELECT 1 num UNION
SELECT 10 UNION
SELECT 100 UNION
SELECT 1000 UNION
SELECT 10000 UNION
SELECT 100000) nums
WHERE #number DIV nums.num = bids.ticketNumber DIV nums.num
ORDER BY nums.num LIMIT 1;
https://www.db-fiddle.com/f/TZCrFPnJpkw4fyxA5Q6mR/4
You can do:
select *
from (
select 6 as score, b.* from bids b where ticketNumber like '123456%'
union all select 5, b.* from bids b where ticketNumber like '12345%'
union all select 4, b.* from bids b where ticketNumber like '1234%'
union all select 3, b.* from bids b where ticketNumber like '123%'
union all select 2, b.* from bids b where ticketNumber like '12%'
union all select 1, b.* from bids b where ticketNumber like '1%'
) x
order by score desc
limit 1
Result:
score id roundId address ticketNumber
------ --- -------- -------- ------------
6 1 1 12345 123456
See example at DB Fiddle.
Alternatively you can use a recursive CTE, but that's not available in MySQL 5.7 (as your fiddle implies).
I have the following tabel structure:
Id Num1 Num2 Type Num3
1 2 2 1 4
1 3 1 2 5
1 1 1 3 2
2 2 1 1 3
2 0 1 2 2
2 4 3 3 6
I need a query with group by 'Id', sum of 'Num1', sum of 'Num2', max of 'Num3' and the 'Type' related to the MAX of 'Num3'.
So, the desired output is:
Id Sum(Num1) Sum(Num2) type Max(Num3)
1 6 4 2 5
2 6 4 3 6
Without this related 'Type' the query below works fine:
SELECT
Id,
SUM(Num1),
SUM(Num2),
MAX(Num3)
GROUP BY
Id
I tried different methods of subquery but can't make it work yet.
Your problem is a bit of a spin on the greatest value per group problem. In this case, we can use a subquery to find the max Num3 value for each Id. But, in the same subquery we also compute the sum aggregates.
SELECT
t1.Id,
t2.s1,
t2.s2,
t1.Type,
t1.Num3
FROM yourTable t1
INNER JOIN
(
SELECT Id, SUM(Num1) AS s1, SUM(Num2) AS s2, MAX(Num3) AS m3
FROM yourTable
GROUP BY Id
) t2
ON t1.Id = t2.Id AND t1.Num3 = t2.m3;
As a hat tip to MySQL 8+, and to ward off evil spirits, we can also write a query using analytic functions:
SELECT Id, s1, s2, Type, Num3
FROM
(
SELECT
Id,
SUM(Num1) OVER (PARTITION BY Id) s1,
SUM(Num2) OVER (PARTITION BY Id) s2,
Type,
Num3,
MAX(Num3) OVER (PARTITION BY Id) m3
FROM yourTable
) t
WHERE Num3 = m3;
Why the following query giving value 2 instead of my expectation 1?
SELECT SUM(1) FROM (
SELECT '0' as R FROM dual
UNION
SELECT '1' as R FROM dual
)
But this query satisfying the expectation?
SELECT SUM(R) FROM (
SELECT '0' as R FROM dual
UNION
SELECT '1' as R FROM dual
)
Because in the first query you're summing 1 for each occurrence of any row in the selection, yet for query 1 you're summing whatever the value of 'R' is for each row in the selection.
SUM(1) in the first query is equivalent to COUNT(*):
SELECT COUNT(*) FROM ...
because it adds 1 to the total for each row of the FROM table, regardless of the content of that row.
The second query pays attention to the value of R, so it adds 1 to 0, and arrives at zero.
When you do SELECT 1 you are extracting the hardcoded value 1 and not, as you likely expected, the first column; your query is
SQL> SELECT 1
2 FROM (SELECT '0' AS R FROM DUAL
3 UNION
4 SELECT '1' AS R FROM DUAL
5 );
1
----------
1
1
that is two rows containing 1; the SUM of these rows gives 2:
SQL> SELECT SUM(1)
2 FROM (SELECT '0' AS R FROM DUAL
3 UNION
4 SELECT '1' AS R FROM DUAL
5 );
SUM(1)
----------
2
For example, you can try
SQL> select 100
2 from ( select 1 from dual);
100
----------
100
this clarifies that select 100 does not look for the 100th column of the internal query, but simply gives the value 100.
In the following query you are extracting a variable value in R ( I edited the strings into numbers); in your query you have two rows like the following, where R is 0 in a row and 1 in the other one:
SQL> SELECT R
2 FROM (SELECT 0 AS R FROM DUAL
3 UNION
4 SELECT 1 AS R FROM DUAL
5 );
R
----------
0
1
The SUM of these two values is 0 + 1 = 1:
SQL> SELECT SUM(R)
2 FROM (SELECT 0 AS R FROM DUAL
3 UNION
4 SELECT 1 AS R FROM DUAL
5 );
SUM(R)
----------
1
SQL>
The first query is summing 1 for each record exists in the inner query. That is because you summed by a constant value : SUM(1) which will basically return the same result as COUNT(*)/COUNT(1) .
The second query is summing the values of R columns -> 0 and 1 which is equal to 1.
I need to create a table function that produces a parameter up to a specified number in column 1 always starting from 1. In column 2, if column 1 is divisible by 5 it will say 'Div5' otherwise NULL.
So as an example. I specify column 1 will stop at 5 the end result will look as follows;
1 NULL
2 NULL
3 NULL
4 NULL
5 Div5
I can create the function, but I'm not sure how to create the conditional first column, or how to say if column 2 divided by 5 is an integer then 'Div5' if it's a decimal then NULL;
create function MyFunction ()
Returns #Division Table
(Ind int ,
Div5 varchar(30))
AS
begin
Insert Into #Division (Ind, Div5)
select ???,???
Return;
End;
I hope this gives enough detail?
Thank you :)
This should do the trick:
DECLARE #divisor INT = 10, #limit INT = 100;
WITH
L0 AS(SELECT 1 AS C UNION ALL SELECT 1 AS O),
L1 AS(SELECT 1 AS C FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS C FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS C FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS C FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS C FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS N FROM L5)
SELECT N, CASE WHEN N % #divisor = 0 THEN 'Div' + CAST(#divisor AS VARCHAR(100)) ELSE NULL END AS Col2 FROM Nums
WHERE N <= #limit
The 2 variables determine what number you're looking for the first column to be divisible by, the second for how far you want to go, the next bit is just a CTE to generate the numbers for the first column (numbers tables are really useful for loads of stuff like this). Then it's just selecting all the numbers from the numbers table up to your limit and a case expression to check whether it's divisible by the number you specify (remainder 0) and a bit of string concatenation for the DivX bit.
You should easily be able to integrate this logic into your function.
You are looking for the Modulo operator that basically returns the remainder of a division problem.
DECLARE #SOMETBL TABLE (ROWNUM INT, DIVSTATUS CHAR(4))
INSERT #SOMETBL
(ROWNUM)
SELECT 1
UNION
SELECT 5
UNION
SELECT 2
UNION
SELECT 10
UPDATE #SOMETBL
SET DIVSTATUS = CASE WHEN ROWNUM%5 > 0 THEN NULL ELSE 'DIV5' END
SELECT * FROM #SOMETBL
Say I have the following table, named data:
ID foo1 foo2 foo3
1 11 22 33
2 22 17 92
3 31 33 53
4 53 22 11
5 43 23 9
I want to select all rows where either foo1, foo2 or foo3 match either of these columns in the first row. That is, I want all rows where at least one of the foos appears also in the first row. In the example above, I want to select rows 1, 2, 3 and 4. I thought that I could use something like
SELECT * FROM data WHERE foo1 IN (SELECT foo1,foo2,foo3 FROM data WHERE ID=1)
OR foo2 IN (SELECT foo1,foo2,foo3 FROM data WHERE ID=1)
OR foo3 IN (SELECT foo1,foo2,foo3 FROM data WHERE ID=1)
but this does not seem to work. I can, of course, use
WHERE foo1=(SELECT foo1 FROM data WHERE ID=1)
OR foo1=(SELECT foo2 FROM data WHERE ID=1)
OR ...
but that would invlove many lines, and in my real data set there are actually 16 columns, so it will really be a pain in the lower back. Is there a more sophisticated way to do so?
Also, what should I do if I want to count also the number of hits (in the example above, get 4 for row 1, 2 for row 4, and 1 for rows 2,3)?
SELECT data.*,
(data.foo1 IN (t.foo1, t.foo2, t.foo3))
+ (data.foo2 IN (t.foo1, t.foo2, t.foo3))
+ (data.foo3 IN (t.foo1, t.foo2, t.foo3)) AS number_of_hits
FROM data JOIN data t ON t.id = 1
WHERE data.foo1 IN (t.foo1, t.foo2, t.foo3)
OR data.foo2 IN (t.foo1, t.foo2, t.foo3)
OR data.foo3 IN (t.foo1, t.foo2, t.foo3)
See it on sqlfiddle.
Actually, on reflection, you might consider normalising your data:
CREATE TABLE data_new (
ID BIGINT UNSIGNED NOT NULL,
foo_number TINYINT UNSIGNED NOT NULL,
val INT,
PRIMARY KEY (ID, foo_number),
INDEX (val)
);
INSERT INTO data_new
(ID, foo_number, val)
SELECT ID, 1, foo1 FROM data
UNION ALL SELECT ID, 2, foo2 FROM data
UNION ALL SELECT ID, 3, foo3 FROM data;
DROP TABLE data;
Then you can do:
SELECT ID,
MAX(IF(foo_number=1,val,NULL)) AS foo1,
MAX(IF(foo_number=2,val,NULL)) AS foo2,
MAX(IF(foo_number=3,val,NULL)) AS foo3,
number_of_hits
FROM data_new JOIN (
SELECT d1.ID, COUNT(*) AS number_of_hits
FROM data_new d1 JOIN data_new d2 USING (val)
WHERE d2.ID = 1
GROUP BY d1.ID
) t USING (ID)
GROUP BY ID
See it on sqlfiddle.
As you can see from the execution plan, this will be considerably more efficient for large data sets.
There are several ways to get the result set.
Here's one approach, (if you don't care about which fooN gets matched with with fooN, and also want to return that "first" row).
SELECT DISTINCT d.*
JOIN ( SELECT foo1 AS foo FROM data WHERE id = 1
UNION ALL
SELECT foo2 FROM data WHERE id = 1
UNION ALL
SELECT foo3 FROM data WHERE id = 1
) f
JOIN data d
ON f.foo IN (d.foo1, d.foo2, d.foo3)
That ON clause could also be written like this:
ON d.foo1 = f.foo
OR d.foo2 = f.foo
OR d.foo2 = f.foo
To get a "count" of the hits...
SELECT d.id
, d.foo1
, d.foo2
, d.foo3
, SUM( IFNULL(d.foo1=f.foo,0)
+IFNULL(d.foo2=f.foo,0)
+IFNULL(d.foo3=f.foo,0)
) AS count_of_hits
JOIN ( SELECT foo1 AS foo FROM data WHERE id = 1
UNION ALL
SELECT foo2 FROM data WHERE id = 1
UNION ALL
SELECT foo3 FROM data WHERE id = 1
) f
JOIN data d
ON f.foo IN (d.foo1, d.foo2, d.foo3)
GROUP
BY d.id
, d.foo1
, d.foo2
, d.foo3
eggyal is right, as usual. Getting the count of hits is actually much simpler: we can just use a SUM(1) or COUNT(1) aggregate, no need to run all those comparisons, we've already done all the necessary comparisons.
SELECT d.id
, d.foo1
, d.foo2
, d.foo3
, COUNT(1) AS count_of_hits
JOIN ( SELECT foo1 AS foo FROM data WHERE id = 1
UNION ALL
SELECT foo2 FROM data WHERE id = 1
UNION ALL
SELECT foo3 FROM data WHERE id = 1
) f
JOIN data d
ON f.foo IN (d.foo1, d.foo2, d.foo3)
GROUP
BY d.id
, d.foo1
, d.foo2
, d.foo3