MySQL: Insert records virtually in SELECT statement - mysql

I get a set of result as follows
C1 C2 C3
10 2 T
10 3 E
10 6 S
I want my SELECT query in such a way that resultant records may look like
C1 C2 C3
10 2 T
10 3 E
10 4
10 5
10 6 S
where there is a blank line for the missing records. Couldn't figure out the same.
Original query: select C1, C2,C3 from Table

If your mysql version upper than 8.0, you can try to use cte RECURSIVE make a calendar table then do outer join
Schema (MySQL v8.0)
CREATE TABLE T(
C1 int,
C2 int,
C3 varchar(5)
);
INSERT INTO T VALUES (10,2,'T');
INSERT INTO T VALUES (10,3,'E');
INSERT INTO T VALUES (10,6,'S');
Query #1
WITH RECURSIVE CTE AS (
SELECT C1,MIN(C2) minC2,MAX(C2) maxC2
FROM T
GROUP BY C1
UNION ALL
SELECT C1,minC2 +1,maxC2
FROM CTE
WHERE minC2+1 <= maxC2
)
SELECT t1.C1,t1.minC2,t2.C3
FROM CTE t1 LEFT JOIN T t2 on t1.minC2 = t2.C2
ORDER BY C1,minC2;
| C1 | minC2 | C3 |
| --- | ----- | --- |
| 10 | 2 | T |
| 10 | 3 | E |
| 10 | 4 | |
| 10 | 5 | |
| 10 | 6 | S |
View on DB Fiddle

You can create a table of sequential numbers in your database, and then use an outer join to fill in the missing row values for C2.
It will be very useful for other queries as well, and takes very little space.
CREATE TABLE Numbers (Number INTEGER PRIMARY KEY);
INSERT INTO Numbers (Number) VALUES (1),(2),(3),(4),(5),(6) ...
And then:
SELECT T.C1, N.Number AS C2, T.C3
FROM Numbers AS N LEFT OUTER JOIN T ON T.C2 = N.Number
WHERE N.Number BETWEEN (SELECT MIN(C2) FROM T) AND (SELECT MAX(C2) FROM T)
ORDER BY C2;
HTH

Related

Get the latest record of datetime field by date value

I have a table like
id | start | Value | Value2 | Value3
1 | 2019-01-01 22:15:02 | A | P | C
2 | 2019-01-01 22:35:23 | B | O | G
4 | 2019-01-02 22:35:36 | C | D | H
5 | 2019-01-02 22:37:15 | D | C | F
7 | 2019-01-03 17:26:36 | C | K | M
10 | 2019-01-03 12:05:15 | D | J | L
I have a lot of records for the same day, but different time.
I need to select the latest of each day from a DateTime field.
It should return the records of IDs:
id: 2 for Jan 1
id: 5 for Jan 2nd
id: 7 for January 3rd
Tried without success:
SELECT value, value2, value3
FROM myTable AS mt
INNER JOIN (
SELECT id, MAX(start)
FROM myTable
GROUP BY start
) AS b ON mt.id = b.id
I get no errors, but the data are mixed up. It shows the latest dateTime value, but the rest of the fields (Value, value2, value3) are wrong. They don't match with the latest row.
There are several possible solutions:
SELECT mt.<columns>
FROM myTable AS mt
INNER JOIN (
SELECT DATE(start) as start_date, MAX(start) AS start
FROM myTable
GROUP BY DATE(start)
) AS b ON mt.start = b.start;
I like to use an exclusion join. Look for another row with a greater start datetime on the same date. The no such row exists, then mt must have the greatest time for a given date.
SELECT mt.<columns>
FROM myTable AS mt
LEFT OUTER JOIN myTable AS mt2
ON DATE(mt.start) = DATE(mt2.start) AND mt.start < mt2.start
WHERE mt2.start IS NULL;
You can also use a window function if you're using MySQL 8.0:
SELECT * FROM (
SELECT mt.<columns>,
ROW_NUMBER() OVER (PARTITION BY DATE(start) ORDER BY start DESC) AS rownum
FROM myTable AS mt
) AS b
WHERE b.rownum = 1;

MySQL distinct field count based on customer

I have the below MySQL table,
id customer Field_Name
1 C1 A
2 C1 B
3 C1 C
4 C1 D
5 C2 A
6 C2 D
7 C2 E
9 C3 B
10 C3 F
Customer "C1" has most number of fields (4) - A,B,C,D,
"C2" has 3 fields - A,D,E and
"C3" has 2 fields - B,F
Since customer "C1" has more fields, it should be taken first for comparing the customers
"C2" has A and D - "C1" has these two fields already and E is the only unique in "C2"
"C3" has B - "C1" has this field and F is only unique.
Similarly, it goes on...
I need to select distinct fields based on customers but based on customer with more number of fields.
Expected Result:
id customer Field_Name
1 C1 A
2 C1 B
3 C1 C
4 C1 D
7 C2 E
10 C3 F
If you are using MySQL 8+, then the problem is fairly tractable:
WITH cte1 AS (
SELECT *, COUNT(*) OVER (PARTITION BY customer) cnt
FROM customers
),
cte2 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Field_Name ORDER BY cnt DESC) rn
FROM cte1
)
SELECT id, customer, Field_Name
FROM cte2
WHERE rn = 1;
Demo
In earlier versions of MySQL, it should be possible to achieve the same logic, but in general simulating ROW_NUMBER can be a pain.
Without window functions, for earlier versions of MySql, you can use NOT EXISTS:
select * from tablename t
where not exists (
select 1 from tablename tt
where tt.customer <> t.customer and tt.field_name = t.field_name and
(select count(*) from tablename where customer = tt.customer) >
(select count(*) from tablename where customer = t.customer)
)
See the demo.
Results:
| id | customer | field_name |
| --- | -------- | ---------- |
| 1 | C1 | A |
| 2 | C1 | B |
| 3 | C1 | C |
| 4 | C1 | D |
| 7 | C2 | E |
| 10 | C3 | F |

Display row of another table as column of current table

Consider there are two tables:
Table1:
**Result Total**
Pass 102
Fail 3
Undetermined 1
Table 2:
**Pass% Fail% Undetermined%**
96.23 2.83 0.94
Result Needed:
**Result Total Percentage**
Pass 102 96.23
Fail 3 2.83
Undetermined 1 0.94
How to convert the table 2 rows as column in table 1 to obtain the result ?
first, You can try to do unpivot on Table2, then JOIN with Table1.
Your sql-server version is 2008, you can use unpivot by UNION ALL.
CREATE TABLE T1(
Result VARCHAR(50),
Total int
);
CREATE TABLE T2(
Pass FLOAT,
Fail FLOAT,
Undetermined FLOAT
);
insert into T2 VALUES (96.23,2.83,0.94)
INSERT INTO T1 VALUES ('Pass',102);
INSERT INTO T1 VALUES ('Fail',3);
INSERT INTO T1 VALUES ('Undetermined',1);
Query 1:
SELECT t1.*,s.val
FROM (
SELECT Pass val,'PASS' Name
FROM T2
UNION ALL
SELECT Fail val,'Fail' Name
FROM T2
UNION ALL
SELECT Undetermined val,'Undetermined' Name
FROM T2
) s inner join T1 t1 on t1.Result = s.Name
Results:
| Result | Total | val |
|--------------|-------|-------|
| Pass | 102 | 96.23 |
| Fail | 3 | 2.83 |
| Undetermined | 1 | 0.94 |
If you can use CROSS APPLY with VALUE you can try this.
Query:
SELECT t1.*,s.val
FROM (
SELECT v.* FROM T2
CROSS APPLY(VALUES
(Pass,'PASS'),
(Fail,'Fail'),
(Undetermined,'Undetermined')
) v(val,Name)
) s inner join T1 t1 on t1.Result = s.Name
Results:
| Result | Total | val |
|--------------|-------|-------|
| Pass | 102 | 96.23 |
| Fail | 3 | 2.83 |
| Undetermined | 1 | 0.94 |

Copy columns and edit one column with different entries in a table

Copy 1st 2 rows in same table and insert it with edited column as shown below.
Table 1 (ID is auto increment)
ID | CL1 | CL2 | CL3
1 | A | text1 | NULL
2 | B | text2 | NULL
Table 2
ID | CL3
21 | 45
24 | 63
Converted Table 1
ID | CL1 | CL2 | CL3
1 | A | text1 | NULL
2 | B | text2 | NULL
3 | A | text1 | 45
4 | B | text2 | 63
I know how to copy and insert all the rows with one column duplicated, but changing some column with different value is the problem.
Below is the query to copy all fields with 1 column changed:
INSERT INTO table1 (col1, col2, col3)
SELECT col1, col2, 1
FROM table1 LIMIT 2;
Ex: So now we have table2 which has table1 CL3's values. Now can we get the data from another table and insert them while copying?
Assuming you want the first 2 records from 1 table updated with the values from the 1st 2 rows from another table, then I think you will need to add a sequence number to each one and join based on that.
Something like as follows, but it won't be quick!
INSERT INTO table1 (ID, CL1, CL2, CL3)
SELECT NULL, a.CL1, a.CL2, b.CL3
FROM
(
SELECT CL1, CL2, #cnt1:=#cnt1 + 1 AS cnt
FROM table1
CROSS JOIN (SELECT #cnt1:=0) sub0
ORDER BY ID
LIMIT 2
) a
INNER JOIN
(
SELECT CL3, #cnt2:=#cnt2 + 1 AS cnt
FROM table2
CROSS JOIN (SELECT #cnt2:=0) sub0
ORDER BY ID
LIMIT 2
) b
ON a.cnt = b.cnt

Query for looping values in column

I need to make a query that moves values of only one column one row up ↑ at a time:
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 10 |
+------------+----------------+
So, the next time i run the query, it should look like this
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 5 | 6 |
| 6 | 7 |
| 7 | 8 |
| 8 | 9 |
| 9 | 10 |
| 10 | 1 |
+------------+----------------+
I need to loop the values of only one MYSQL COLUMN, as in move the values one ROW UP ↑ each time I run the query.
Notice: Tables provided are just illustrative, the data is different.
Here's how you can do it within a single UPDATE query:
UPDATE tbl a
INNER JOIN (
SELECT values_to_loop
FROM (SELECT * FROM tbl) c
ORDER BY anotherCOL
LIMIT 1
) b ON 1 = 1
SET a.values_to_loop =
IFNULL(
(SELECT values_to_loop
FROM (SELECT * FROM tbl) c
WHERE c.anotherCOL > a.anotherCOL
ORDER BY c.anotherCOL
LIMIT 1),
b.values_to_loop
)
It works as follows:
Updates all records from tbl
Joins with a temporary table to retrieve the top value of values_to_loop (the one that will go to the bottom)
Set the new value for values_to_loop to the corresponding value from the next row (c.anotherCOL > a.anotherCOL ... LIMIT 1)
Notes:
This works even if there are gaps in anotherCOL (eg: 1, 2, 3, 6, 9, 15)
It is required to use (SELECT * FROM tbl) instead of tbl because you're not allowed to use the table that you're updating in the update query
Faster query when there are no gaps in anotherCOL
If there are no gaps for values in anotherCOL you can use the query below that should work quite fast if you have an index on anotherCOL:
UPDATE tbl a
LEFT JOIN tbl b on b.anotherCOL = a.anotherCOL + 1
LEFT JOIN (
SELECT values_to_loop
FROM tbl
WHERE anotherCOL = (select min(anotherCOL) from tbl)
) c ON 1 = 1
SET a.values_to_loop = ifnull(
b.values_to_loop,
c.values_to_loop
)
I`ve created a sample table and added both a select to get the looped values and update to loop the values in the table. Also, using a #start_value variable to know the "1" which might be other. Try this:
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table
(other_col INT, loop_col int);
INSERT INTO temp_table (other_col, loop_col) VALUES (1,1);
INSERT INTO temp_table (other_col, loop_col) VALUES (2,2);
INSERT INTO temp_table (other_col, loop_col) VALUES (3,3);
INSERT INTO temp_table (other_col, loop_col) VALUES (4,4);
INSERT INTO temp_table (other_col, loop_col) VALUES (5,5);
DECLARE start_value INT;
SELECT start_value = MIN(loop_col) FROM temp_table;
SELECT T1.other_col, ISNULL(T2.loop_col, start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
UPDATE T1 SET
T1.loop_col = ISNULL(T2.loop_col, #start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
SELECT *
FROM temp_table;
Let me know if it works for you.
Step by step:
1 - created a temp_table with values 1 to 5
2 - declared a start_value which will keep the lowest value for the column you to need to loop through
3 - select all rows from temp_table self left join with same temp_table. join condition is on loop_col - 1 so it can shift the rows up
4 - the same self left join, but this time update the values in place too.
please note that in case i get a null value, it should be the start_value there, because it cannot match
Perhaps these are what you had in mind:
update T
set values_to_loop = mod(values_to_loop, 10) + 1
update T
set values_to_loop =
coalesce(
(
select min(t2.values_to_loop) from T t2
where t2.values_to_loop > T.values_to_loop
),
(
select min(values_to_loop) from T
)
)