Join two tables with a lesser than or equals condition in join - mysql

Below two tables I have with sample data. Table A contains dollar rate (into Indian rupee) as per year, and Table B contains amount per year. I want to convert
dollar into rupee as per year.
Table A
Rate Year
47 2001
49 2003
55 2004
Table B
Amt Year
25$ 2001
34$ 2002
Question: for first record (year 2001) we have entry in both tables so we can do this easily by using below query
sel A.Rate * B.Amt
from A,
B
where B.year = A.year
But for second record (i.e. year 2002) we do not have entry in table A (which is rate table), so for these kind cases I want to use rate value from previous year (i.e.: 47 rupee from year 2001.)

Here is the solution :
select A.rate*B.amt
from A,B
where B.Year = (select max(year) from B where B.year <= A.year);

Oracle : use the LEAD analytic function so you can work out the validity period of each rate.
documentation for LEAD
This is my code :
SELECT
trx.*
,rates.rate_start_date
,rates.rate_end_date
,rates.rate
,trx.amount * rates.rate rup_amount
FROM
xxcjp_forex_trx trx
--this inline view works out the validity period of each rate by ordering all
--the rates and working out the start date of the next row. It uses analytic
--function LEAD
,(SELECT
xfr.rate_date rate_start_date
,xfr.rate
,xfr.currency
,(LEAD(xfr.rate_date) OVER (ORDER BY xfr.currency, xfr.rate_date))-1 rate_end_date
FROM
xxcjp_forex_rates xfr
) rates
WHERE 1=1
AND trx.trx_date BETWEEN rates.rate_start_date AND rates.rate_end_date
AND rates.currency = 'RUP'
ORDER BY
trx.trx_date
;
Based on this data :
CREATE TABLE XXCJP_FOREX_RATES
(rate_date DATE
,currency VARCHAR2(20)
,rate NUMBER
)
;
CREATE TABLE XXCJP_FOREX_TRX
(trx_date DATE
,currency VARCHAR2(20)
,amount NUMBER
)
;
INSERT INTO XXCJP_FOREX_RATES VALUES (TO_DATE('01/03/2016','DD/MM/YYYY'),'RUP',47) ;
INSERT INTO XXCJP_FOREX_RATES VALUES (TO_DATE('03/03/2016','DD/MM/YYYY'),'RUP',49) ;
INSERT INTO XXCJP_FOREX_RATES VALUES (TO_DATE('10/03/2016','DD/MM/YYYY'),'RUP',55) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('01/03/2016','DD/MM/YYYY'),'USD',10) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('02/03/2016','DD/MM/YYYY'),'USD',20) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('03/03/2016','DD/MM/YYYY'),'USD',30) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('04/03/2016','DD/MM/YYYY'),'USD',40) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('05/03/2016','DD/MM/YYYY'),'USD',50) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('06/03/2016','DD/MM/YYYY'),'USD',60) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('07/03/2016','DD/MM/YYYY'),'USD',70) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('08/03/2016','DD/MM/YYYY'),'USD',80) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('09/03/2016','DD/MM/YYYY'),'USD',90) ;
INSERT INTO XXCJP_FOREX_TRX VALUES (TO_DATE('10/03/2016','DD/MM/YYYY'),'USD',100) ;

Related

How to SELECT from table, when the condition is depending on rows

Help needed please.
I have a trouble with a select.
I have a table containing probation periods with following columns:
CREATE TABLE probation (
pk_person int,
p_startdate datetime,
p_enddate datetime,
PRIMARY KEY (pk_person )
)
and a table containing persons absences with following columns:
CREATE TABLE absences (
pk_person int,
a_startdate datetime,
a_enddate datetime,
)
Now, i need to calculate the end of the probation period for person. The problem is that the absences effectively prolongs the probation period.
For example:
insert into probation (pk_person, p_startdate, p_enddate) values (1, 01-01-2001, 31-03-2001)
insert into absences (pk_person, a_startdate, a_enddate) values (1, 01-02-2001, 28-02-2001)
insert into absences (pk_person, a_startdate, a_enddate) values (1, 01-04-2001, 10-04-2001)
Now i need to select the complete probation period for person with the following result:
pk_person, startdate, enddate
1 01-01-2001 08-05-2001
I could use a stored procedure, but can't do it for reasons.
I'm using MSSQL 2008.
Thanks!
Select
Int
Big list.Start date
Big list. End date
Datediff(day, big list.start date, big list.end date) as original duration
Sum(datediff(day, small list.start date, small list.end date) as total extra time
DateAdd(day, big list.start date,
Sum(datediff(day, small list.start date, small list.end date)) as new end date
From
Big list
Left outer join small list on big list.int = small list.int
Group by
Big list.everything

Find the user who increased their hours streamed from the previous calendar month

This table is a "heartbeat" tracking event where one row is genereated each minute for each streamer while that streamer is live. If a streamer is live for 60 minutes, 60 rows would be generated in this table
Create Table minute_streamed
(
time_minute datetime ,
username varchar(50) ,
category varchar(50) ,
concurrent_viewers int
)
Insert into minute_streamed values ('2020-03-18 12:00:00', 'alex','Fornite',125) ;
Insert into minute_streamed values ('2020-03-18 12:01:00', 'alex','Fornite',130) ;
Insert into minute_streamed values ('2020-03-19 15:30:00', 'jamie','Just Chatting',13) ;
Insert into minute_streamed values ('2020-03-19 15:31:00', 'jamie','Food & Drink',15) ;
Insert into minute_streamed values ('2020-03-20 10:30:00', 'rick','Call of Duty: Black Ops',150) ;
Insert into minute_streamed values ('2020-03-20 10:31:00', 'rick','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'rick','Fornite',120) ;
Insert into minute_streamed values ('2020-04-20 10:31:00', 'rick','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'rick','Fornite',120) ;
Insert into minute_streamed values ('2020-04-20 10:31:00', 'jamie','Call of Duty: Modern Warfare',120) ;
Insert into minute_streamed values ('2020-04-21 09:30:00', 'jamie','Fornite',120) ;
Insert into minute_streamed values ('2020-04-18 12:00:00', 'alex','Fornite',125) ;
Insert into minute_streamed values ('2020-04-18 12:01:00', 'alex','Fornite',130) ;
Insert into minute_streamed values ('2020-06-18 14:00:00', 'alex','Fornite',120) ;
Alex has two entries in March. That means he streamed for 2 minutes. So, his hourly streamed for March will be 2/60.
I am trying to write a query: For each calendar month, output the list of streamers, who increased their hours streamed from the previous calendar month
For example, Alex has two entries for March, two entries for April, and one entry for June. So he streamed 2 minutes in March (because he has two entries), 2 minutes in April and 1 minute in June. I want to compare his last month, which is June and the previous calendar month. In this case, the previous calendar month is May, which Alex did not stream. So I need to say that he did not stream in May and he streamed in June. So, he increased his streaming compare to the previous calendar month
This my code below, but I want to compare the current streaming hour with the previous calendar month. Can you please help modify my query?
select
*
from(
select
*
,lag(total_monthly_hours,1) over(partition by username order by year,month) as prev_month
from(
select
username
,year(time_minute) as year
,month(time_minute) as month
,count(*)/60 as total_monthly_hours
from minute_streamed
group by year(time_minute), month(time_minute), username
order by month(time_minute) desc ) as temp ) as temp2
where total_monthly_hours > prev_month
If I understand correctly, you want to compare both the previous value and the previous time period. One way to do this is to calculate a "number of months" by multiplying the year by 12 and adding in the month number. Then you can see if the lag() is getting the value from the previous row:
select uym.*
from (select uym.*,
lag(total_monthly_hours) over (partition by username order by year, month) as prev_total_monthly_hours,
lag(month_cnt) over (partition by username order by year, month) as prev_month_cnt
from (select username,
year(time_minute) as year,
month(time_minute) as month,
year(time_minute) * 12 + month(time_minute) as month_cnt,
count(*)/60 as total_monthly_hours
from minute_streamed
group by year(time_minute), month(time_minute), username, month_cnt
) uym
) uym
where prev_month_cnt is null or
prev_month_cnt <> month_cnt - 1 or
(prev_month_cnt = month_cnt - 1 and prev_total_monthly_hours < total_monthly_hours);
Here is a db<>fiddle.

Mysql update for a range of data where some number is missing from the series

I have 1 table containing 2000 rows. I have these columns
roll_no entrance_date entrance_time entrance_center
The roll number is starting from 10011 to 10011704. I want to update entrance date,center, time for specific series of numbers. Suppose I want to give roll no 10011-100145 to exam center A, exam time-10.am and date 1.1.2015, where some roll numbers in the above series may missing. In that case how to write the update statement?
Use Where Clause !
update <tablename> set exam_center = A, exam_time = '10:00:00', date = '2015-01-01' where roll_no >= 10011 and roll_no <= 100145;
use a WHERE clause in your update statement
UPDATE table
SET exam_center = 'A',
exam_time = '10:00:00',
date = '2015-01-01'
WHERE roll_no >= 10011
AND roll_no <= 100145;

How do I get a list of numbers in MySQL?

I've got a database of movies, and I'd like a list of years where I don't have a movie for that year. So all I need is a list (1900 .. 2012) and then I can JOIN and IN and NOT IN on that all I want.
I've got:
CREATE PROCEDURE build_years(p1 SMALLINT)
BEGIN
CREATE TEMPORARY TABLE year (year SMALLINT(5) UNSIGNED);
label1: LOOP
INSERT INTO year VALUES (p1);
SET p1 = p1 + 1;
IF p1 > 2012 THEN LEAVE label1; END IF;
END LOOP;
END
But that seems so unSQL and only marginally less kludgy then running Python code to create the same table. I'd really like something that didn't use a stored procedure, didn't use looping and didn't use an actual table, in that order of concern.
This should work until you need more than 195 years , at which point you'll need to add a UNION ALL:
SELECT Year
FROM ( SELECT #i:= #i + 1 AS YEAR
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
( SELECT #i:= 1899) AS i
) As Y
WHERE Year BETWEEN 1900 AND 2012
ORDER BY Year;
Although I am assuming that the COLLATION_CHARACTER_SET_APPLICABILITY System table has a default size of 195 based on my trusty testing ground SQL Fiddle
I had similar problem a few years ago. My solution was:
1. Sequence table
I created a table filled with integer sequence from 0 to < as much as it will be required >:
CREATE TABLE numbers (n INT);
INSERT INTO numbers VALUES (0),(1),(2),(3),(4);
INSERT INTO numbers SELECT n+5 FROM numbers;
INSERT INTO numbers SELECT n+10 FROM numbers;
INSERT INTO numbers SELECT n+20 FROM numbers;
INSERT INTO numbers SELECT n+40 FROM numbers;
etc.
It is executed only once, so can be created from outside of your app, even by hand.
2. Select data of a needed type and range
For integers it is obvious - i.e. range 1..99:
SELECT n FROM numbers WHERE n BETWEEN 1 AND 99;
Dates - 2h intervals from now to +2 days:
SELECT date_add(now(),INTERVAL 2*n HOUR) FROM numbers WHERE n BETWEEN 0 AND 23;
So in your case it could be:
SELECT n+1900 AS n_year FROM numbers WHERE n BETWEEN 0 AND 112;
Then JOIN it on n_year.
This will return a list of 2012 to 1900 if you really want to keep it to a query..
SELECT
TO_CHAR (ADD_MONTHS (TRUNC (SYSDATE, 'YYYY'), ((rno - 1) * -12)), 'YYYY') AS "years"
FROM
(
SELECT
LEVEL rno
FROM DUAL
CONNECT BY LEVEL <=
(SELECT TO_CHAR (TRUNC (SYSDATE, 'YYYY'), 'YYYY')
- 1899
yearstobuild
FROM DUAL))
The only solution I can think of according to your wishes sucks also ...
SELECT years.year FROM
(
SELECT 1900 AS year
UNION SELECT 1901
...
UNION SELECT 2012
) AS years
LEFT OUTER JOIN yourmovietable USING (year)
WHERE yourmovietable.year IS NULL;
Using this generic query is faster:
INSERT INTO numbers SELECT n+(SELECT COUNT(*) FROM numbers) FROM numbers;
Each query execution duplicates:
INSERT INTO numbers VALUES (0),(1),(2),(3),(4);
INSERT INTO numbers SELECT n+(SELECT COUNT(*) FROM numbers) FROM numbers;
INSERT INTO numbers SELECT n+(SELECT COUNT(*) FROM numbers) FROM numbers;
INSERT INTO numbers SELECT n+(SELECT COUNT(*) FROM numbers) FROM numbers;
...
select year into temporary table blaa from (generate_series(1900,2000)) where year not in(select distinct(year) from films)
dont know if this will work but you get the drift.

SQL query that returns all dates not used in a table

So lets say I have some records that look like:
2011-01-01 Cat
2011-01-02 Dog
2011-01-04 Horse
2011-01-06 Lion
How can I construct a query that will return 2011-01-03 and 2011-01-05, ie the unused dates. I postdate blogs into the future and I want a query that will show me the days I don't have anything posted yet. It would look from the current date to 2 weeks into the future.
Update:
I am not too excited about building a permanent table of dates. After thinking about it though it seems like the solution might be to make a small stored procedure that creates a temp table. Something like:
CREATE PROCEDURE MISSING_DATES()
BEGIN
CREATE TABLE TEMPORARY DATES (FUTURE DATETIME NULL)
INSERT INTO DATES (FUTURE) VALUES (CURDATE())
INSERT INTO DATES (FUTURE) VALUES (ADDDATE(CURDATE(), INTERVAL 1 DAY))
...
INSERT INTO DATES (FUTURE) VALUES (ADDDATE(CURDATE(), INTERVAL 14 DAY))
SELECT FUTURE FROM DATES WHERE FUTURE NOT IN (SELECT POSTDATE FROM POSTS)
DROP TABLE TEMPORARY DATES
END
I guess it just isn't possible to select the absence of data.
You're right — SQL does not make it easy to identify missing data. The usual technique is to join your sequence (with gaps) against a complete sequence, and select those elements in the latter sequence without a corresponding partner in your data.
So, #BenHoffstein's suggestion to maintain a permanent date table is a good one.
Short of that, you can dynamically create that date range with an integers table. Assuming the integers table has a column i with numbers at least 0 – 13, and that your table has its date column named datestamp:
SELECT candidate_date AS missing
FROM (SELECT CURRENT_DATE + INTERVAL i DAY AS candidate_date
FROM integers
WHERE i < 14) AS next_two_weeks
LEFT JOIN my_table ON candidate_date = datestamp
WHERE datestamp is NULL;
One solution would be to create a separate table with one column to hold all dates from now until eternity (or whenever you expect to stop blogging). For example:
CREATE TABLE Dates (dt DATE);
INSERT INTO Dates VALUES ('2011-01-01');
INSERT INTO Dates VALUES ('2011-01-02');
...etc...
INSERT INTO Dates VALUES ('2099-12-31');
Once this reference table is set up, you can simply outer join to determine the unused dates like so:
SELECT d.dt
FROM Dates d LEFT JOIN Blogs b ON d.dt = b.dt
WHERE b.dt IS NULL
If you want to limit the search to two weeks in the future, you could add this to the WHERE clause:
AND d.dt BETWEEN NOW() AND ADDDATE(NOW(), INTERVAL 14 DAY)
The way to extract rows from the mysql database is via SELECT. Thus you cannot select rows that do not exist.
What I would do is fill my blog table with all possible dates (for a year, then repeat the process)
create table blog (
thedate date not null,
thetext text null,
primary key (thedate));
doing a loop to create all dates entries for 2011 (using a program, eg $mydate is the date you want to insert)
insert IGNORE into blog (thedate,thetext) values ($mydate, null);
(the IGNORE keyword to not create an error (thedate is a primary key) if thedate exists already).
Then you insert the values normally
insert into blog (thedate,thetext) values ($mydate, "newtext")
on duplicate key update thetext="newtext";
Finally to select empty entries, you just have to
select thedate from blog where thetext is null;
You probably not going to like this:
select '2011-01-03', count(*) from TABLE where postdate='2011-01-03'
having count(*)=0 union
select '2011-01-04', count(*) from TABLE where postdate='2011-01-04'
having count(*)=0 union
select '2011-01-05', count(*) from TABLE where postdate='2011-01-05'
having count(*)=0 union
... repeat for 2 weeks
OR
create a table with all days in 2011, then do a left join, like
select a.days_2011
from all_days_2011
left join TABLE on a.days_2011=TABLE.postdate
where a.days_2011 between date(now()) and date(date_add(now(), interval 2 week))
and TABLE.postdate is null;