Merge two select statement without ordering - mysql

There is a table TableX (id, user_id, sticky_date, created_at)
Few of them entries having sticky dates.
I am trying to get the result as,
result will contain, all the entries having sticky_date order desc and then all remaining entries order by created_at.
I tried with following query,
(SELECT * FROM TableX ORDER BY sticky_expiry_date)
UNION
( select * from TableX order by created_at desc)
But after union created_at is not working as expected. Is there any way to do this stuff in single query.

We can do this using computed columns:
SELECT *
FROM
(
SELECT *, 0 AS position, UNIX_TIMESTAMP(sticky_expiry_date) AS odr
FROM TableX
UNION -- UNION ALL ?
SELECT *, 1, -UNIX_TIMESTAMP(created_at)
FROM TableX
) t
ORDER BY
position, odr;
The idea here is that the position alias keeps track of which half of the union should come first. Then, the odr alias tracks the ordering within each half of the union. The top half uses sticky_expiry_date for the odr alias, while the second half uses created_at. To deal with the issue of ascending/descending date ordering, we can wrap the date columns in UNIX_TIMESTAMP and order by that quantity (ascending), or by that quantity negated (descending).

Related

Max(created_at) showing the right column but not the rest of the data SQL

I want to fetch the latest entry to the database
I have this data
When I run this query
select id, parent_id, amount, max(created_at) from table group by parent_id
it correctly returns the latest entry but not the rest of the column
what I want is
how do I achieve that?
Sorry that I posted image instead of table, the table won't work for some reason
You can fetch the desired output using subquery. In the subquery fetch the max created_at of each parent_id which will return the row with max created_at for each parent_id. Please try the below query.
SELECT * FROM yourtable t WHERE t.created_at =
(SELECT MAX(created_at) FROM yourtable WHERE parent_id = t.parent_id);
If the id column in your table is AUTO_INCREMENT field then you can fetch the latest entry with the help of id column too.
SELECT * FROM yourtable t WHERE t.id =
(SELECT MAX(id) FROM yourtable WHERE parent_id = t.parent_id);
That's a good use case for a window function like RANK as a subquery:
SELECT id, parent_id, amount, created_at
FROM (
SELECT id, parent_id, amount, created_at,
RANK() OVER (PARTITION BY parent_id ORDER BY created_at DESC) parentID_rank
FROM yourtable) groupedData
WHERE parentID_rank = 1;
or with ORDER BY clause for the outer query if necessary:
SELECT id, parent_id, amount, created_at
FROM (
SELECT id, parent_id, amount, created_at,
RANK() OVER (PARTITION BY parent_id ORDER BY created_at DESC) parentID_rank
FROM yourtable) groupedData
WHERE parentID_rank = 1
ORDER BY id;
To explain the intention:
The PARTITION BY clause groups your data by the parent_id.
The ORDER BY clause sorts it starting with the latest date.
The WHERE clause just takes the entry with the latest date per parent id only.
The main point here is that your query is invalid. The DBMS should raise an error, but you work in a cheat mode that MySQL offers that allows you to write such queries without being warned.
My advice: When working in MySQL make sure you have always
SET sql_mode = 'ONLY_FULL_GROUP_BY';
As to the query: You are using MAX. Thus you aggregate your data. In your GROUP BY clause you say you want one result row per parent_id. You select the parent_id's maximum created_at. You also select the parent_id's ID, the parent_id itself, and the parent_id's amount. The parent_id's ID??? Is there only one ID per parent_id in your table? The amount? Is there only one amount per parent_id in the table? You must tell the DBMS which ID to show and which amount. You haven't done so, and this makes your query invalid according to standard SQL.
You are running MySQL in cheat mode,however, and so MySQL silently applies ANY_VALUE to all non-aggregated columns. This is what your query is turned into internally:
select
any_value(id),
parent_id,
any_value(amount),
max(created_at)
from table
group by parent_id;
ANY_VALUE means the DBMS is free to pick the attribute from whatever row it likes; you don't care.
What you want instead is not to aggregate your rows, but to filter them. You want to select only those rows with the maximum created_at per parent_id.
There exist several ways to get this result. Here are some options.
Get the maximum created_at per parent_id. Then select the matching rows:
select *
from table
where (parent_id, created_at) in
(
select parent_id, max(created_at)
from table
group by parent_id
);
Select the rows for which no newer created_at exists for the parent_id:
select *
from table t
where not exists
(
select null
from table newer
where newer.parent_id = t.parent_id
and newer.created_at > t.created_at
);
Get the maximum created_at on-the-fly. Then compare the dates:
select id, parent_id, amount, created_at
from
(
select t.*, max(created_at) over (partition by parent_id) as max_created_at
from table t
) with_max_created_at
where created_at = max_created_at;
select id, parent_id, amount, max(created_at)
from table
group by parent_id
order by max(created_at) desc
limit 1

Filtering values according to a different value for each user

I am trying to understand how to do in mySQL what I usually do in python.
I have a sales table, with sale_date, user_id and price_USD as columns. Each row is an order made by the user.
I want to get a new table that has all of the orders which cost more than the last order the user made (so in this picture, just the yellow rows).
I know how to get a table with the last order for each user, but I cannot save it on the database.
How do I compare each row's price to a different value by the user_id and get just the larger ones in one 'swoop'?
Thanks
If you are running MysL 8.0, you can do this with window functions:
select t.*
from (
select
t.*,
first_value(price_usd)
over(partition by user_id order by sale_date desc) last_price_usd
from mytable t
) t
where lag_price_usd is null or price > last_price_usd
In earlier versions, you could use a correlated subquery:
select t.*
from mytable t
where t.price_usd > (
select price_usd
from mytable t1
where t1.user_id = t.user_id
order by sale_date desc
limit 1
)

Collect dates of 2 columns within the same table

I have two date columns in my table and I need a list of all dates which are in the table.
I tried it with the following statement already
SELECT GREATEST(
IFNULL(DATE(record_date), 0),
IFNULL(DATE(edit_date), 0)
) as 'date_val'
FROM TBL_EXAMPLE
GROUP BY date_val
ORDER BY date_val;
With this SQL statement some of the dates getting lost and I don't know why. I just want a complete list of all dates which are in this table (DISTINCT, descending)
This is a job for UNION.
SELECT record_date AS dt FROM TBL_EXAMPLE
UNION
SELECT edit_date AS dt FROM TBL_EXAMPLE
ORDER BY dt DESC;
Union eliminates nulls and duplicates.

mysql select from 2 different tables and return either highest price

I have a mysql query that I'm trying to figure out.
Basically I have table 1 cols: estate agent, price, location, bungalow, cottage
and I have table 2 cols: estate agent, price, location, penthouse, duplex
As you can see these tables are very different.
I need a query to select all cols from table 1 or 2 depending on which has the highest price. For example:
SELECT * FROM table1, table2 WHERE table1.price = table2.price ORDER BY price DESC LIMIT 1,1;
If the tables are so similar, you could UNION them, then sort the result descending and get the higher price.
(SELECT * FROM table1)
UNION ALL
(SELECT * FROM table2)
ORDER BY price DESC
LIMIT 1
You would need to specify the columns explicitly if you want to give them aliases.
Re your followup question:
If you have a few columns different between the two tables, and you want to preserve them, you need to move away from SELECT * and name all the columns explicitly.
(SELECT estate agent, price, location, bungalow, cottage, NULL AS penthouse, NULL AS duplex
FROM table1)
UNION ALL
(SELECT estate agent, price, location, NULL, NULL, penthouse, duplex
FROM table2)
ORDER BY price DESC
LIMIT 1
You don't need to give aliases in the second subquery because column names are always determined by the first query of a UNION. Even if you do declare column aliases in the second query, they'll be ignored.
try this:
SELECT * FROM (
SELECT * FROM table1
UNION
SELECT * FROM table2
ORDER BY price desc)x
LIMIT 1;

MySQL Query with first 20 items ordered by one field and rest ordered by name ASC

I have a database table that has two fields , date and name.
I want to have my query pull the first 20 by newest date first, then the rest of the query to pull the other elements by name alphabetically.
So that way the top 20 newest products would show first, then the rest would be ordered by name.
It's a bit ugly, but you can do it in one query:
SELECT name,
`date`
FROM ( SELECT #rank := #rank + 1 AS rank,
name,
`date`
FROM (SELECT #rank := 0) dummy
JOIN products
ORDER BY `date` DESC, name) dateranked
ORDER BY IF(rank <= 20, rank, 21), name;
The innermost query, dummy, initializes our #rank variable. The next derived table, dateranked, ranks all rows by recency (breaking ties by name). The outermost query then simply re-orders the rows by our computed rank, treating ranks greater than 20 as rank #21, and then by name.
UPDATE: This query version is more compact, puts the conditional ranking logic in the outermost ORDER BY, uses IF() rather than CASE/END.
I'm afraid this has to be done by adding a special column to your table or creating a temporary table, TPup. If you let me know whether you are interested in those options, I'll tell you more.
The two queries option like the following might be a possibility, but my version of MySQL tells me LIMIT isn't available in sub-queries.
SELECT `date`, `name` from `table` ORDER BY `date` LIMIT 0, 20;
SELECT `date`, `name` from `table` WHERE `id` NOT IN (SELECT `id` from `table` ORDER BY `date` LIMIT 0, 20) ORDER BY `name`;
Use sql UNION operator to combine result of two SELECT queries.
According to MySQL docs:
use of ORDER BY for individual SELECT statements implies nothing
about the order in which the rows appear in the final result because
UNION by default produces an unordered set of rows.
...
To use an ORDER BY or LIMIT clause to sort or limit the entire UNION
result, parenthesize the individual SELECT statements and place the
ORDER BY or LIMIT after the last one. The following example uses both clauses:
(SELECT a FROM t1 WHERE a=10 AND B=1)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;
Edit:
I missed the part that explain OP needs to sort one set of the result on the date and the other set of the result alphabetically. I think you need to create a temporary field for the sorting purpose. And SQL query would be something similar to this.
(SELECT *, 'firstset' as set_id FROM t1 ORDER BY date LIMIT 0, 20)
UNION
(SELECT *, 'secondset' as set_id FROM t1 ORDER BY date LIMIT 20, 18446744073709551615)
ORDER BY
CASE
WHEN set_id='firstset' THEN date
WHEN set_id='secondset' THEN name
END DESC ;