Changing sql to get rid of windowed functions - mysql

I'm trying to create a view on a remote mysql database and unfortunately it appears that the version installed (5.7) does not support window functions. Everything worked on my local database but now I'm a little stuck.
Here's previous code:
create or replace view my_view as
(
with a as
(select a.*, DENSE_RANK() over (partition by SHOP order by TIMESTAMP(LOAD_TIME) DESC) rn
from my_table as a)
select row_number() OVER () as id, SHOP, LOAD_TIME from a WHERE a.rn = 1
);
Mysql 5.7 doesnt support CTE either, but that isn't a big problem.
Any hints how to solve this?

Replacing the dense_rank() is pretty easy. However, replacing the row_number() is more difficult. MySQL does not allow variables in views. Unfortunately, that leaves you with an inefficient subquery for the row number as well:
select (select count(distinct shop)
from mytable t2
where t2.shop <= t.shop
) as id,
shop, load_time
from mytable t
where t.load_time = (select max(t2.load_time) from mytable t2 where t2.shop = t.shop);
Or, if these are the only two columns you have, use aggregation:
select (select count(distinct shop)
from mytable t2
where t2.shop <= t.shop
) as id,
shop, max(load_time) as load_time
from mytable t
group by shop;
This is not efficient. In a simple query, you could use variables:
select (#rn := #rn + 1) as id,
shop, load_time
from mytable t cross join
(select #rn := 0) params
where t.load_time = (select max(t1.load_time) from mytable t1 where t1.shop = t.shop);
If performance is an issue, then you may want to create a table rather than a view and keep it up-to-date using triggers.

You can handle the filtering part with a correlated subquery:
create or replace view my_view as
select shop, load_time
from mytable t
where t.load_time = (select max(t1.load_time) from mytable t1 where t1.shop = t.shop)

Related

Retrieve the data from database based on type

Basically I have data in my DB as mentioned below :
And I am expecting the output as below :
So What I meant was, if a person fails/Passes in all subjects I need to display only 1st row or else I need to display all the data of that particular person.
I can able to display the records if person pass and fail(George), but could not able to display if he fails/passes in all subjects (for Sam and John)
I tried by add some subqueries and group by and using LAG and LEAD functions but I could not able to achieve the result. Can any one of you suggest in doing so..
You can use window functions:
select t.*
from (select t.*,
count(*) over (partition by name) as cnt,
count(*) over (partition by name, status) as cnt_status,
row_number() over (partition by name order by no) as seqnum
from t
) t
where seqnum = 1 or
cnt <> cnt_status;
You could also do this without window functions:
select t.*
from t
where exists (select 1
from t t2
where t2.name = t.name and t2.status <> t.status
) or
t.no = (select min(t2.no) from t t2 where t2.name = t.name);

MySql Query to find out first 50% of records from a Table

I am trying to fetch first 50% of records from a MySQL Table User. I know we can use limit or top for finding them but the total number of records are not fixed so hard coding the actual number in the limit or top doesn't gives me first 50% of records. How can I achieve this?
If you are running MySQL 8.0, you can use window functions for this: ntile() does exactly what you ask for. Assuming that your ordering column is id:
select *
from (select t.*, ntile(2) over(order by id) nt from mytable) t
where nt = 1
In earlier versions, one option is a user variable and a join with an aggregate query:
select *
from (
select t.*, #rn := #rn + 1 rn
fom (select * from mytable order by id) t
cross join (select #rn := 0) x
cross join (select count(*) cnt from mytable) c
) t
where rn <= cnt / 2
Mysql directly not supports this. You can try with two queries or use subqueries
Something like this.
find the count of total records/2
that value has to be applied in the limit clause.
SET #count = (SELECT COUNT(*)/2 FROM table);
SET #sql = CONCAT('SELECT * FROM table LIMIT ', #count);
SELECT * FROM table name LIMIT (select COUNT(*)/2 from table name);

How can I update a surrogate key based on an older entry of a table

I have a table called player_stage.
I am trying to prepare my data so I can put it into a data warehouse.
I currently have a unreliable work-around that involves a duplicates view and handpicking the values from the duplicates.
I need to create a query that gives duplicates the same surrogate key(sk).
Any idea how I can do this? I've been stuck on t
his for 3 days.
If you are using MySQL 8+, then DENSE_RANK can work here:
SELECT
PLAYER_ID,
PLAYER_NAME,
DB_SOURCE,
DENSE_RANK() OVER (ORDER BY PLAYER_NAME) SK
FROM yourTable;
The above call to DENSE_RANK would assign the same SK value to all records belonging to the same player name.
If you are using a version of MySQL earlier than 8+, then we can simulate the dense rank with user variables, e.g.
SELECT t1.PLAYER_ID, t1.PLAYER_NAME, t1.DB_SOURCE, t2.rn AS SK
FROM yourTable t1
INNER JOIN
(
SELECT PLAYER_NAME, #rn := #rn + 1 AS rn
FROM (SELECT DISTINCT PLAYER_NAME FROM yourTable) t, (SELECT #rn := 0) r
ORDER BY PLAYER_NAME
) t2
ON t1.PLAYER_NAME = t2.PLAYER_NAME
ORDER BY
t1.PLAYER_ID;
Demo

Union n amount of tables in mariadb and find the distinct value based on timestamp

I need to make a search on multiple database tables. Since all tables have the same structure(same columns, they are actually archived data for the same table) I can use UNION ALL to combine them all.
However, the challenge is I need to get the distinct value for Username Column and for every Username value I need to find the earliest Time_stamp after using UNION ALL for all archived data.
I went through the documentation and googled for a quite some time and couldn't find a satisfying solution withing MySQL.
The only way I can think of is to write some external code to search the values with UNION ALL for each Username. And run the query 1 by 1. Knowing that I have 100k distinct Username listed in the database, it will take quite some time. I have already started this script, however, it feels really inefficient and waste of resources.
SELECT * FROM table1 WHERE `USERNAME` LIKE 'USERNAMEXXX'
UNION ALL
SELECT * FROM table2 WHERE `USERNAME` LIKE 'USERNAMEXXX'
UNION ALL
SELECT * FROM table3 WHERE `USERNAME` LIKE 'USERNAMEXXX'
UNION ALL
SELECT * FROM table4 WHERE `USERNAME` LIKE 'USERNAMEXXX'
ORDER BY TIME_STAMP ASC
LIMIT 1
Above SQL query gives me what I want for every username but I need to iterate it for each distinct Username.
Just to add, I have another table that holds all the distinct Username values. I use that table to populate the search on my external code solution.
Is there any way to achieve this using native SQL, without external scripting? Something that combines distinct, union all and order by timestamp asc limit 1.
In MySQL 8+, you can use window functions:
SELECT t.*
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY username ORDER BY timestamp) as seqnum
FROM ((SELECT t.* FROM table1 t) UNION ALL
(SELECT t.* FROM table2 t) UNION ALL
(SELECT t.* FROM table3 t) UNION ALL
(SELECT t.* FROM table4 t)
) t
) t
WHERE seqnum = 1;
In earlier versions, you can use variables:
SELECT t.*
FROM (SELECT t.*,
(#rn := IF(#u = username, #rn + 1,
IF(#u := username, 1, 1)
)
) as seqnum
ROW_NUMBER() OVER (PARTITION BY username ORDER BY timestamp) as seqnum
FROM ((SELECT t.* FROM table1 t) UNION ALL
(SELECT t.* FROM table2 t) UNION ALL
(SELECT t.* FROM table3 t) UNION ALL
(SELECT t.* FROM table4 t)
ORDER BY username, timestamp
) t CROSS JOIN
(SELECT #u := '', #rn := 0) params
) t
WHERE seqnum = 1;
The above assume that you want all the columns. If you just want the minimum timestamp and username, use aggregation:
SELECT username, MIN(timestamp),
FROM ((SELECT t.* FROM table1 t) UNION ALL
(SELECT t.* FROM table2 t) UNION ALL
(SELECT t.* FROM table3 t) UNION ALL
(SELECT t.* FROM table4 t)
) t
GROUP BY username;
If I understand right your question
You could use min for earliest timestam min(my_time_stamp) and UNION (for return distinct )
select username, min(my_time_stamp)
from table1
group by username
union
select username, min(my_time_stamp)
from table2
group by username
union
select username, min(my_time_stamp)
from table3
group by username
....
union
select username, min(my_time_stamp)
from tableN
group by username
order by username

SQL - calculate MODE without sorting

I would like to calculate the MODE of a single column in SQL. This is done easily enough with:
SELECT v AS Mode
FROM Data
GROUP BY v HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM Data GROUP BY v);
However, I would like to do this without sorting, i.e. without using GROUP BY or any similar construct. Is there a quick and easy way to do this?
group by doesn't do sorting. It does partitioning. So instead of 1 aggregate result, you get 1 result per group in which all values that you group by are the same.
For MySQL the best I could come up with is this:
select distinct v from(
select v,
#cnt := (select count(*) from Data d1 where d1.v=d.v) as cnt_,
case when #cnt>=#max then #max:=#cnt end as max_
from Data d,
(select #max:=1, #cnt:=1) c) a
where cnt_ = #max
For SQL Server, Oracle or Postgres you can use a window function:
with a as (
v, select row_count() OVER(PARTITION BY v) rn
from Data
)
select v as Mode
FROM a
where rn = (select max(rn) from a)