I have a table, called gcrunarrays with the columns {run_id, time_id, co2}. I have several thousand runs with distinct run_id's, each with 58 time-dependent values for co2. I have a procedure to obtain the median co2 of all runs at a given time. Now, I need a select statement that will obtain the medians for each time. So far, I have
select DISTINCT A.time_id, M.co2 from gcrunarrays A, call getMedian(1, A.time_id) M
GO
Which gets a syntax error. I am fairly new at SQL and I'm working in MYSQL. I've tried about a dozen different ways of wording this statement but now I'm at the point where I feel like I've done something inherently wrong. I think it might work better if median were a function but I'm not sure even how to get the median without using a select statement. Any suggestions are greatly appreciated.
For greater clarification:
Table gcrunarrays
run_id | time_id | co2
1 | 1 |
1 | 2 |
...
1 | 58 |
2 | 1 |
...
2 | 58 |
3 ...
Median Procedure
CREATE PROCEDURE getMedian (IN e INT, t INT)
BEGIN
SELECT count(*), x.co2
FROM (SELECT B.exp_id, A.* FROM gcRunArrays A JOIN gcRuns B ON A.run_id=B.run_id WHERE B.exp_id=e and A.time_id=t) x,
(SELECT B.exp_id, A.* FROM gcRunArrays A JOIN gcRuns B ON A.run_id=B.run_id WHERE B.exp_id=e and A.time_id=t) y
GROUP BY x.co2
HAVING SUM(SIGN(1-SIGN(y.co2-x.co2))) = CEILING((COUNT(*)+1)/2);
END
GO
Use a temp table can easily solve the issue.
And it is not possible to use procedure results as table directly.
Can a stored procedure/function return a table?
Related
I'm wanting to optimize a query using a union as a sub query.
Im not really sure how to construct the query though.
I'm using MYSQL 8.0.12
Here is the original query:
---------------
| c1 | c2 |
---------------
| 18182 | 0 |
| 18015 | 0 |
---------------
2 rows in set (0.35 sec)
I'm sorry but the question doesn't stored if I paste the sql query as text and format using ctrl+k
Output expected
---------------
| c1 | c2 |
---------------
| 18182 | 167 |
| 18015 | 0 |
---------------
As a output I would like to have the difference of rows between the two tables in UNION ALL.
I processed this question using the wizard https://stackoverflow.com/questions/ask
Since a parenthesized SELECT can be used almost anywhere a expression can go:
SELECT
ABS( (SELECT COUNT(*) FROM tbl_aaa) -
(SELECT COUNT(*) FROM tbl_bbb) ) AS diff;
Also, MySQL is happy to allow a SELECT without a FROM.
There are several ways to go for this, including UNION, but I wouldn't recommend it, as it is IMO a bit 'hacky'. Instead, I suggest you use subqueries or use CTEs.
With subqueries
SELECT
ABS(c_tbl_aaa.size - c_tbl_bbb.size) as diff
FROM (
SELECT
COUNT(*) as size
FROM tbl_aaa
) c_tbl_aaa
CROSS JOIN (
SELECT
COUNT(*) as size
FROM tbl_bbb
) c_tbl_bbb
With CTEs, also known as WITHs
WITH c_tbl_aaa AS (
SELECT
COUNT(*) as size
FROM tbl_aaa
), c_tbl_bbb AS (
SELECT
COUNT(*) as size
FROM tbl_bbb
)
SELECT
ABS(c_tbl_aaa.size - c_tbl_bbb.size) as diff
FROM c_tbl_aaa
CROSS JOIN c_tbl_bbb
In a practical sense, they are the same. Depending on the needs, you might want to define and join the results though, and in said cases, you could use a single number as a "pseudo id" in the select statement.
Since you only want to know the differences, I used the ABS function, which returns the absolute value of a number.
Let me know if you want a solution with UNIONs anyway.
Edit: As #Rick James pointed out, COUNT(*) should be used in the subqueries to count the number of rows, as COUNT(id_***) will only count the rows with non-null values in that field.
How to retrieve odd rows from the table?
In the Base table always Cr_id is duplicated 2 times.
Base table
I want a SELECT statement that retrieves only those c_id =1 where Cr_id is always first as shown in the output table.
Output table
Just see the base table and output table you should automatically know what I want, Thanx.
Just testing min date should be enough
drop table if exists t;
create table t(c_id int,cr_id int,dt date);
insert into t values
(1,56,'2020-12-17'),(56,56,'2020-12-17'),
(1,8,'2020-12-17'),(56,8,'2020-12-17'),
(123,78,'2020-12-17'),(1,78,'2020-12-18');
select c_id,cr_id,dt
from t
where c_id = 1 and
dt = (select min(dt) from t t1 where t1.cr_id = t.cr_id);
+------+-------+------------+
| c_id | cr_id | dt |
+------+-------+------------+
| 1 | 56 | 2020-12-17 |
| 1 | 8 | 2020-12-17 |
+------+-------+------------+
2 rows in set (0.002 sec)
What you're looking for could be "partition by", at least if you're working on mssql.
(In the future, please include more background, SQL is not just SQL)
https://codingsight.com/grouping-data-using-the-over-and-partition-by-functions/
I have an old query lying around, that is able to put a sorting index on data who lacks this, although the underlying reason is 99.9% sure to be a bad data design.
Typically I use this query to remove bad data, but you may rewrite it to become a join instead, so that you can identify the data you need.
The reason why I'm not putting that answer here, is to point out, bad data design results in more work when reading it afterwards, whom seems to be the real root cause here.
DELETE t
FROM
(
SELECT ROW_NUMBER () OVER (PARTITION BY column_1 ,column_2, column_3 ORDER BY column_1,column_2 ,column_3 ) AS Seq
FROM Table
)t
WHERE Seq > 1
I want to create a PROCEDURE in MySQL that always returns x rows from a table, even when the table has less than x entries.
Like so:
+----+-------+
| id | value | CALL myProcedure USING(4);
+----+-------+ returns → a b c a
| 1 | a |
| 2 | b |
| 3 | c |
+----+-------+
Internally I store the last returned row (in this case it would be 'a') and on the next call the procedure should continue from there:
1st: CALL myProcedure USING(4) → a b c a
2nd: CALL myProcedure USING(3) → b c a
3rd: CALL myProcedure USING(7) → b c a b c a b
4th: CALL myProcedure USING(2) → c a
I tried it with UNION - this is what the 3rd call with x=7 would look like:
(
SELECT `value`
FROM `table`
LIMIT 1,7
)
UNION
(
SELECT `value`
FROM `table`
LIMIT 4
)
"give me as much as you can (up to 7) rows and start after row 1.
Then start over and give me the rest (7 - number of previous rows = 4)."
The first select returns b c and the second select returns a b c. Both selects together return b c a.
Now I am facing these problems:
1)UNION does not return the same row twice (all I would get from above call would be b c a)
2) At best I can "loop over my table" twice because there is only one union and I have no way of dynamically adding more unions. So even if I could get duplicate rows, it would only result in b c a b c and the remaining a b I expect will be missing.
How can I "loop" over my table multiple times? Is there anything better than UNION I could use?
EDIT (after solving my problem)
I followed cf_en's proposition to loop through the result set outside the database if the returned number of rows is less than expected. All other cases (those that only need one iteration) are covered by the procedure (using a simple UNION).
You can use UNION ALL to stop the union from removing duplicates. However, I wonder if the database is the best place to do the looping around. Could you return the distinct rows from the database and implement the looping logic in the calling code instead?
I have a table like this:
CREATE TABLE rows(
UniqueID VARCHAR(225),
Previous VARCHAR(225),
Next VARCHAR(225)
);
With content, that looks like this:
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 890 | 219 | undefined |
| 219 | 676 | 890 |
+----------+-----------+-----------+
As you can see, the rows have UID's, which the Previous and Next columns refer to.
What I now want, is to write a SELECT * statement, that would order all the results, by the Previous and Next fields. The undefined values mark the end elements. How could I achieve that? In the case of the table showed above, the order I'd want is what's shown there, with the last 2 row positions swapped, so Next of row X Points to a UID of row Y, that has a Previous that points to the UID of the row X. etc.
What you're trying to create is a recursive query. Unfortunately, MySQL does not make this easy. There are relatively simple solutions if the parents always have an index greater than the children, but that is not the case here. There are several questions discussing this type of problem. The following question has answers that explore the different ways to attempt this type of query including using stored procedures.
How to do the Recursive SELECT query in MySQL?
Going with the stored procedure idea, you could try something like:
CREATE PROCEDURE getInOrder()
BEGIN
DECLARE child_id VARCHAR(256);
DECLARE prev_id VARCHAR(256);
SELECT UniqueID INTO prev_id FROM rows WHERE Previous = 'undefined';
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table AS (SELECT * FROM rows WHERE 1=0);
TRUNCATE TABLE temp_table;
WHILE child_id <> 'undefined' DO
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SET prev_id = child_id;
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
END WHILE;
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SELECT * FROM temp_table;
END;
You can then call the stored procedure to retrieve the table in order.
Working example: http://sqlfiddle.com/#!9/085dec/2
ORDER BY IFNULL(prev, ''), -- some value lower than the rest
IFNULL(next, 'zzzzz') -- some value higher than all values
(Technically, the first part could be simply prev, without the IFNULL.)
If the ids are really numbers, you should use a numeric datatype such as INT UNSIGNED. If they are really strings, do you need 225?
This assumes that prev < next -- Is that necessarily the case? It seems like arbitrary links might not maintain that. If you need to look at next to load the next row based on UniqueId, the code is much more complex.
I think this request lacks on details.
But, you want the final result to be like this?
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 219 | 676 | 890 |
| 890 | 219 | undefined |
+----------+-----------+-----------+
If I'm right, you can achieve it with (I named the table as demo):
SELECT d.* FROM (
SELECT UniqueID, IF(Previous IS NULL, -1, Previous) AS Previous, IF(Next IS NULL, 999999999999, Next) as Next
FROM demo
)t
JOIN demo d ON d.UniqueID = t.UniqueID
ORDER BY t.Next, t.Previous
;
So, when Previous is NULL you put it with -1 to ensure he's is the first on the list and when Next is NULL you put it with a very high value to ensure it will be the last on the list... then you just have to order the query by Previous and Next.
I must stress that this solution is focused on presented data.
I am trying rewrite this subquery into a join. I have read the other questions on SO but cant get this one working.
create table job (
emplid int,
effdt date,
title varchar(100),
primary key (emplid, effdt)
);
insert into job set emplid=1, effdt='2010-01-01', title='Programmer';
insert into job set emplid=1, effdt='2011-01-01', title='Programmer I';
insert into job set emplid=1, effdt='2012-01-01', title='Programmer II';
insert into job set emplid=2, effdt='2010-01-01', title='Analyst';
insert into job set emplid=2, effdt='2011-01-01', title='Analyst I';
insert into job set emplid=2, effdt='2012-01-01', title='Analyst II';
#Get each employees current job:
select *
from job a
where a.effdt=
(select max(b.effdt)
from job b
where b.emplid=a.emplid);
Results:
+--------+------------+---------------+
| emplid | effdt | title |
+--------+------------+---------------+
| 1 | 2012-01-01 | Programmer II |
| 2 | 2012-01-01 | Analyst II |
+--------+------------+---------------+
I would like to rewrite the query into a join, without a subquery. Is this possible?
Writing this as a join is perhaps a bit counterintuitive. The idea is to use a left outer join and include in the condition that b.effdt > a.effdt. This condition will match rows except when a.effdt takes on the maximum value. The query can then filter for these using a where:
select a.*
from job a left outer join
job b
on b.emplid = a.emplid and
b.effdt > a.effdt
where b.effdt is NULL;
Have you considered rewriting your schema?
If you are able to, it might be better to have a history or log table that has entries for when the effective date was changed, for which employee ID and what the previous title was. That way you would just query the actual table and get the results that you want.
This can be achieved by using triggers for whenever a row in the database is changed, then everything is handled at the database level.