mysql union Merge different columns - mysql

I want to remove the null value And move up the value from yesterday
But I don't know how to do it.
Full sql:
(SELECT
COUNT(1) toDay, NULL AS yesterDay
FROM
bas_user
WHERE UNIX_TIMESTAMP(user_datetime) BETWEEN UNIX_TIMESTAMP(
DATE_FORMAT(CURDATE(), '%Y-%m-%d %H:%i:%s')
)
AND UNIX_TIMESTAMP(NOW())
GROUP BY HOUR(user_datetime))
UNION
(SELECT
NULL AS toDay,COUNT(1) yesterDay
FROM
bas_user
WHERE UNIX_TIMESTAMP(user_datetime) BETWEEN UNIX_TIMESTAMP(
DATE_SUB(
DATE_FORMAT(CURDATE(), '%Y-%m-%d %H:%i:%s'),
INTERVAL 1 DAY
)
)
AND UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 1 DAY))
GROUP BY HOUR(user_datetime)
)

In order to merge the two result sets, you need a join key. For example, assume user_id is the join key of both.
-- Step 1
create table user_today (
user_id int,
today_count int);
create table user_yesterday (
user_id int,
yesterday_count int);
insert into user_today values (101, 10), (102, 20), (103, 30);
insert into user_yesterday values (102, 25), (103, 35), (104, 45);
-- Step 2
select COALESCE(t.user_id, y.user_id) as user_id,
t.today_count,
y.yesterday_count
from user_today t
left
join user_yesterday y
using (user_id)
union
select COALESCE(y.user_id, t.user_id) as user_id,
t.today_count,
y.yesterday_count
from user_yesterday y
left
join user_today t
using (user_id);
Result:
user_id|today_count|yesterday_count|
-------+-----------+---------------+
101| 10| |
102| 20| 25|
103| 30| 35|
104| | 45|

Related

Delete rows having exactly same values

Please consider following table
Table Name: mytable
model_id
event_name
time_of_event
9
CREATE
2016-01-01 00:00:00
9
UPDATE
2016-01-01 01:00:00
9
DELETE
2016-01-01 02:00:00
3
CREATE
2016-01-01 03:00:00       DUPLICATE
3
CREATE
2016-01-01 03:00:00       DUPLICATE delete this
3
DELETE
2016-01-01 04:00:00
How to delete 5th entry from above table i.e. delete row with exactly same value from table.
In above example no column is unique.
Please keep in mind that database could be huge and I don't want to recreate or republish data with distinct values into the table.
// Use below code to create above example
CREATE TABLE mytable(
model_id integer,
event_name varchar(7),
time_of_event timestamp
);
INSERT INTO mytable
(model_id, event_name, time_of_event)
VALUES
(9, 'CREATE', '2016-01-01 00:00:00'),
(9, 'UPDATE', '2016-01-01 01:00:00'),
(9, 'DELETE', '2016-01-01 02:00:00'),
(3, 'CREATE', '2016-01-01 03:00:00'),
(3, 'CREATE', '2016-01-01 03:00:00'),
(3, 'DELETE', '2016-01-01 04:00:00');
SELECT * FROM mytable;
Try with a helper table that contains the duplicates, but only one each:
With this scenario: ...
CREATE TABLE mytable(
model_id integer,event_name VARCHAR(8),time_of_event TIMESTAMP)
;
INSERT INTO mytable
-- your input data ...
SELECT 9,'CREATE',TIMESTAMP '2016-01-01 00:00:00'
UNION ALL SELECT 9,'UPDATE',TIMESTAMP '2016-01-01 01:00:00'
UNION ALL SELECT 9,'DELETE',TIMESTAMP '2016-01-01 02:00:00'
UNION ALL SELECT 3,'CREATE',TIMESTAMP '2016-01-01 03:00:00'
UNION ALL SELECT 3,'CREATE',TIMESTAMP '2016-01-01 03:00:00'
UNION ALL SELECT 3,'DELETE',TIMESTAMP '2016-01-01 04:00:00'
;
Create your helper table like so:
CREATE TABLE helper AS
SELECT
model_id
, event_name
, time_of_event
FROM mytable
GROUP BY
model_id
, event_name
, time_of_event
HAVING COUNT(*) > 1;
Then, use the helper table to delete ... you will delete all rows, not only one of the duplicates ...
DELETE FROM mytable
WHERE(model_id,event_name,time_of_event) IN (
SELECT model_id,event_name,time_of_event FROM helper
);
And finally, insert all the rows from the helper table back in again:
INSERT INTO mytable
SELECT * FROM helper;
COMMIT; -- if your connection is not auto-commit ...
But I'd like to add that, for most database systems, the other approach - to create a new table containing SELECT DISTINCT * FROM old_table is the faster alternative as soon as we are talking about around 20 to 25 % of the total row count.
Having two or more rows with identical values is a sign of very bad design. I suppose model_id is the table's primary key. I wonder how did you end up in this situation.
I don't want to recreate or republish data with distinct values into the table.
One possible solution is to add (not recreate/republish) a column with unique values to your table, then delete the duplicate rows you want.
ALTER TABLE mytable ADD COLUMN MyTableID INT FIRST;
You need to fill this column with unique values:
SET #i := 0;
UPDATE MyTable SET MyTableID = #i:=(#i+1) WHERE 1=1;
Next, you can write the following query:
SELECT
MT.MyTableID, MT.model_id, MT.event_name, MT.time_of_event
FROM
MyTable MT,
(SELECT model_id, event_name, time_of_event, COUNT(*)
FROM MyTable
GROUP BY model_id, event_name, time_of_event
HAVING COUNT(*) > 1
) TmpTable
WHERE
MT.model_id = TmpTable.model_id
AND MT.event_name = TmpTable.event_name
AND MT.time_of_event = TmpTable.time_of_event
;
Result:
MyTableID
model_id
event_name
time_of_event
4
3
CREATE
2016-01-01 03:00:00
5
3
CREATE
2016-01-01 03:00:00
You can now proceed with the deletion of duplicate rows:
DELETE FROM MyTable WHERE MyTableID IN (5 /*, the ones you wish */);
If you have too many duplicate values and you can't afford to delete them manually, you can do it like this:
DELETE FROM MyTable WHERE MyTableID IN (
SELECT
MT.MyTableID
FROM
(SELECT * FROM MyTable) AS MT,
(SELECT model_id, event_name, time_of_event, COUNT(*)
FROM MyTable
GROUP BY model_id, event_name, time_of_event
HAVING COUNT(*) > 1
) AS TmpTable
WHERE
MT.model_id = TmpTable.model_id
AND MT.event_name = TmpTable.event_name
AND MT.time_of_event = TmpTable.time_of_event
) LIMIT /* The number of duplicate rows - 1 */;
The -1 is to preserve one row of the duplicates. If you want to delete them all, remove the LIMIT clause.

Join table in Snowflake using between in where clause

I have Week table -
Week, Friday_dates, Start_date, End_date
1, 2021-07-16, 2021-07-12, 2021-07-18
2, 2021-07-23, 2021-07-19, 2021-07-25
and so on..
I have another table with list of details by date
Date, To_do_list
20220-01-02, Text
20220-01-03, Call
20220-01-03, Text
20220-01-04, Call
20220-01-05, Call
I want count no of to_do_list where it says Call and want join this table with date table for specific dates falling under start_date and end_date.
Can anyone help me please?
So given your data (I am using a CTE so I don't have to create the tables)
WITH Weeks(Week, Friday_dates, Start_date, End_date) as (
SELECT * FROM VALUES
(1, '2021-07-16'::date, '2021-07-12'::date, '2021-07-18'::date),
(2, '2021-07-23'::date, '2021-07-19'::date, '2021-07-25'::date)
), Activity(Date, To_do_list) as (
SELECT * FROM VALUES
('20220-01-02'::date, 'Text'),
('20220-01-03'::date, 'Call'),
('20220-01-03'::date, 'Text'),
('20220-01-04'::date, 'Call'),
('20220-01-05'::date, 'Call')
)
To join the activity to the weeks and limit the count to just the call's, which will be counted per week, you would:
SELECT w.Friday_dates
,count(*) as call_count
FROM Activity AS a
JOIN Weeks AS w
on a.DATE between w.Start_date and w.End_date
WHERE a.To_do_list = 'Call'
GROUP BY 1
ORDER BY 1;
which gives no results as none of your weeks overlap the activity time ranges.
So if we change the data to be overlapping:
WITH Weeks(Week, Friday_dates, Start_date, End_date) as (
SELECT * FROM VALUES
(1, '2021-07-16'::date, '2021-07-12'::date, '2021-07-18'::date),
(2, '2021-07-23'::date, '2021-07-19'::date, '2021-07-25'::date)
), Activity(Date, To_do_list) as (
SELECT * FROM VALUES
('2021-07-18'::date, 'Text'),
('2021-07-18'::date, 'Call'),
('2021-07-18'::date, 'Text'),
('2021-07-19'::date, 'Call'),
('2021-07-20'::date, 'Call')
)
we now get:
FRIDAY_DATES
CALL_COUNT
2021-07-16
1
2021-07-23
2
But if we take you SQL from the comment and make it valid:
select * from Weeks a
left join (
select Date,
count(*) from Activity
where To_do_list like '%Call%'
group by DATE
) b
on b.DATE between a.Start_date and a.End_date
ORDER BY 1;
we get..
WEEK
FRIDAY_DATES
START_DATE
END_DATE
DATE
COUNT(*)
1
2021-07-16
2021-07-12
2021-07-18
2021-07-18
1
2
2021-07-23
2021-07-19
2021-07-25
2021-07-19
1
2
2021-07-23
2021-07-19
2021-07-25
2021-07-20
1

Ordering within a MySQL group

I have two tables which are joined - one holds schedules and the other holds actual worked times.
This works fine if a given user only has a single schedule on a day but when they have more than one schedule I cannot get the query to match up the "right" slot to the right time.
I am beginning to think the only way to do this is to allocate the time to the schedule when the clock event happens but that is going to be a big rewrite so I am hoping there is a way in MySQL.
As this is inside a third party application, I am limited in what I can do to the query - I can modify the basics like from, group, joins etc and I can add aggregates to the fields (I have toyed with using min/max on the times). However, if the only way is to write a hugely complex query especially within the field selections then this system simply doesn't give me that option.
Schedule table:
CREATE TABLE `schedule` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`date` date NOT NULL,
`start_time` time NOT NULL,
`end_time` time NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `schedule`
--
INSERT INTO `schedule` (`id`, `user_id`, `date`, `start_time`, `end_time`) VALUES
(1, 1, '2019-07-07', '08:00:00', '12:00:00'),
(2, 1, '2019-07-07', '16:00:00', '22:00:00'),
(3, 1, '2019-07-06', '10:00:00', '18:00:00');
Time table
CREATE TABLE `time` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`date` date NOT NULL,
`start_time` time NOT NULL,
`end_time` time NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `time`
--
INSERT INTO `time` (`id`, `user_id`, `date`, `start_time`, `end_time`) VALUES
(1, 1, '2019-07-07', '08:00:00', '12:00:00'),
(2, 1, '2019-07-07', '16:00:00', '22:00:00'),
(3, 1, '2019-07-06', '10:00:00', '18:00:00');
Current query
select
t.date as date, t.user_id,
s.start_time as schedule_start,
s.end_time as schedule_end,
t.start_time as actual_start,
t.end_time as actual_end
from time t
left join schedule s on
t.user_id=s.user_id and t.date=s.date
group by t.date, t.start_time
Current output
== Dumping data for table s
|2019-07-06|1|10:00:00|18:00:00|10:00:00|18:00:00
|2019-07-07|1|08:00:00|12:00:00|08:00:00|12:00:00
|2019-07-07|1|08:00:00|12:00:00|16:00:00|22:00:00
Desired output
== Dumping data for table s
|2019-07-06|1|10:00:00|18:00:00|10:00:00|18:00:00
|2019-07-07|1|08:00:00|12:00:00|08:00:00|12:00:00
|2019-07-07|1|16:00:00|22:00:00|16:00:00|22:00:00
Is this possible to achieve?
I would try something like this.
I selected 15 min time limit that a shift should start
select
t.date as date, t.user_id,
s.start_time as schedule_start,
s.end_time as schedule_end,
t.start_time as actual_start,
t.end_time as actual_end
from time t
left join schedule s on
t.user_id=s.user_id and t.date=s.date
and s.start_time BETWEEN t.start_time - INTERVAL 15 MINUTE
AND t.start_time + INTERVAL 15 MINUTE
order by date,schedule_start;
Grouping would you do be add up time for every day and user day
You need a much more complicated query to distinguish the 2 shifts.
So you must execute 2 separate queries each for each shift and combine them with UNION:
select
s.date, s.user_id,
s.schedule_start,
s.schedule_end,
t.actual_start,
t.actual_end
from (
select s.date, s.user_id,
min(s.start_time) as schedule_start,
min(s.end_time) as schedule_end
from schedule s
group by s.date, s.user_id
) s left join (
select t.date, t.user_id,
min(t.start_time) as actual_start,
min(t.end_time) as actual_end
from time t
group by t.date, t.user_id
) t on t.user_id=s.user_id and t.date=s.date
union
select
s.date, s.user_id,
s.schedule_start,
s.schedule_end,
t.actual_start,
t.actual_end
from (
select s.date, s.user_id,
max(s.start_time) as schedule_start,
max(s.end_time) as schedule_end
from schedule s
group by s.date, s.user_id
) s left join (
select t.date, t.user_id,
max(t.start_time) as actual_start,
max(t.end_time) as actual_end
from time t
group by t.date, t.user_id
) t on t.user_id=s.user_id and t.date=s.date
See the demo.
Results:
> date | user_id | schedule_start | schedule_end | actual_start | actual_end
> :--------- | ------: | :------------- | :----------- | :----------- | :---------
> 2019-07-06 | 1 | 10:00:00 | 18:00:00 | 10:00:00 | 18:00:00
> 2019-07-07 | 1 | 08:00:00 | 12:00:00 | 08:00:00 | 12:00:00
> 2019-07-07 | 1 | 16:00:00 | 22:00:00 | 16:00:00 | 22:00:00

SQL Server: Select rows with dates that correspond to end of year and calculate price

I have a table of data:
ProductNum | ProductVariation | Past_Price | Current_Price | Order_Date
------------ ------------------ ------------ --------------- ---------------------
1 33 96.05 100.10 2014-01-01 00:00:00
1 33 97.65 100.10 2014-12-03 12:34:52
1 33 98.98 100.10 2015-01-02 05:50:32
1 33 99.98 100.10 2016-03-02 06:50:43
1 33 100.01 100.10 2016-12-12 06:05:43
1 33 100.05 100.10 2017-01-02 05:34:43
I was wondering if its possible to query for the rows such that we get the row that has the closest date to Dec 31,{Year} ?
So the output would be :
ProductNum | ProductVariation | Past_Price | Current_Price | Order_Date
------------ ------------------ ------------ --------------- ---------------------
1 33 98.98 100.10 2015-01-02 05:50:32
1 33 99.98 100.10 2016-03-02 06:50:43
1 33 100.01 100.10 2017-01-02 05:34:43
Each order being the closest to Dec 31,{Year} for Years: 2014,2015,2016
You can sort by the date difference and get the top 1 row for each year.
For SqlServer:
DECLARE #year2014 datetime2 = '2014-12-31 12:00:00';
DECLARE #year2015 datetime2 = '2015-12-31 12:00:00';
DECLARE #year2016 datetime2 = '2016-12-31 12:00:00';
select * from (
select top(1) * from products
order by abs(datediff(second, #year2014, Order_Date))
) as p
union all
select * from (
select top(1) * from products
order by abs(datediff(second, #year2015, Order_Date))
)as p
union all
select * from (
select top(1) * from products
order by abs(datediff(second, #year2016, Order_Date))
) as p
Change the time of the 31st of December as you like.
For MySql:
set #year2014 = '2014-12-31 12:00:00';
set #year2015 = '2015-12-31 12:00:00';
set #year2016= '2016-12-31 12:00:00';
select * from (
select * from products
order by abs(TIMESTAMPDIFF(second, #year2014, Order_Date)) limit 1
) as p
union all
select * from (
select * from products
order by abs(TIMESTAMPDIFF(second, #year2015, Order_Date)) limit 1
)as p
union all
select * from (
select * from products
order by abs(TIMESTAMPDIFF(second, #year2016, Order_Date)) limit 1
) as p
Get row_number()s for each year ordered by the absolute datediff() between the order date and 31-12 of the year. Then select all where one of the row numbers equals 1.
SELECT *
FROM (SELECT *,
row_number() OVER (ORDER BY abs(datediff(second, '2014-12-31', t.order_date))) rn2014,
row_number() OVER (ORDER BY abs(datediff(second, '2015-12-31', t.order_date))) rn2015,
row_number() OVER (ORDER BY abs(datediff(second, '2016-12-31', t.order_date))) rn2016
FROM elbat t) x
WHERE 1 IN (x.rn2014,
x.rn2015,
x.rn2016);
db<>fiddle
You can use this. This will avoid hard coding years and copying pasting unions.
declare #currDate datetime;
select #currDate = '12/31/2019';
while #currDate > '12/31/2013'
begin
select *
from Product
where abs(datediff(second, OrderDate, #currDate))
= (select min(
abs(datediff(second, OrderDate, #currDate))
)
from Product )
select #currDate = dateadd(year,-1,#currDate);
end
I used the following fiddle:
create table Product (ProdNum int, ProdVar int, PastPrice decimal, CurrentPrice decimal, OrderDate datetime);
insert into Product values (1, 33, 96.05, 100.10, '2014-01-01 00:00:00');
insert into Product values (1, 33, 97.65, 100.10, '2014-12-03 12:34:52');
insert into Product values (1, 33, 98.98, 100.10, '2015-01-02 05:50:32');
insert into Product values (1, 33, 99.98, 100.10, '2016-03-02 06:50:43');
insert into Product values (1, 33, 100.01, 100.10, '2016-12-12 06:05:43');
insert into Product values (1, 33, 100.05, 100.10, '2017-01-02 05:34:43');
You seem to actually want the first date after the end of the year:
select top (1) with ties t.*
from t
order by row_number() over (partition by year(order_date) order by order_date asc);

MySQL query, MAX() + GROUP BY

Daft SQL question. I have a table like so ('pid' is auto-increment primary col)
CREATE TABLE theTable (
`pid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`cost` INT UNSIGNED NOT NULL,
`rid` INT NOT NULL,
) Engine=InnoDB;
Actual table data:
INSERT INTO theTable (`pid`, `timestamp`, `cost`, `rid`)
VALUES
(1, '2011-04-14 01:05:07', 1122, 1),
(2, '2011-04-14 00:05:07', 2233, 1),
(3, '2011-04-14 01:05:41', 4455, 2),
(4, '2011-04-14 01:01:11', 5566, 2),
(5, '2011-04-14 01:06:06', 345, 1),
(6, '2011-04-13 22:06:06', 543, 2),
(7, '2011-04-14 01:14:14', 5435, 3),
(8, '2011-04-14 01:10:13', 6767, 3)
;
I want to get the PID of the latest row for each rid (1 result per unique RID). For the sample data, I'd like:
pid | MAX(timestamp) | rid
-----------------------------------
5 | 2011-04-14 01:06:06 | 1
3 | 2011-04-14 01:05:41 | 2
7 | 2011-04-14 01:14:14 | 3
I've tried running the following query:
SELECT MAX(timestamp),rid,pid FROM theTable GROUP BY rid
and I get:
max(timestamp) ; rid; pid
----------------------------
2011-04-14 01:06:06; 1 ; 1
2011-04-14 01:05:41; 2 ; 3
2011-04-14 01:14:14; 3 ; 7
The PID returned is always the first occurence of PID for an RID (row / pid 1 is frst time rid 1 is used, row / pid 3 the first time RID 2 is used, row / pid 7 is first time rid 3 is used). Though returning the max timestamp for each rid, the pids are not the pids for the timestamps from the original table. What query would give me the results I'm looking for?
(Tested in PostgreSQL 9.something)
Identify the rid and timestamp.
select rid, max(timestamp) as ts
from test
group by rid;
1 2011-04-14 18:46:00
2 2011-04-14 14:59:00
Join to it.
select test.pid, test.cost, test.timestamp, test.rid
from test
inner join
(select rid, max(timestamp) as ts
from test
group by rid) maxt
on (test.rid = maxt.rid and test.timestamp = maxt.ts)
select *
from (
select `pid`, `timestamp`, `cost`, `rid`
from theTable
order by `timestamp` desc
) as mynewtable
group by mynewtable.`rid`
order by mynewtable.`timestamp`
Hope I helped !
SELECT t.pid, t.cost, to.timestamp, t.rid
FROM test as t
JOIN (
SELECT rid, max(tempstamp) AS maxtimestamp
FROM test GROUP BY rid
) AS tmax
ON t.pid = tmax.pid and t.timestamp = tmax.maxtimestamp
I created an index on rid and timestamp.
SELECT test.pid, test.cost, test.timestamp, test.rid
FROM theTable AS test
LEFT JOIN theTable maxt
ON maxt.rid = test.rid
AND maxt.timestamp > test.timestamp
WHERE maxt.rid IS NULL
Showing rows 0 - 2 (3 total, Query took 0.0104 sec)
This method will select all the desired values from theTable (test), left joining itself (maxt) on all timestamps higher than the one on test with the same rid. When the timestamp is already the highest one on test there are no matches on maxt - which is what we are looking for - values on maxt become NULL. Now we use the WHERE clause maxt.rid IS NULL or any other column on maxt.
You could also have subqueries like that:
SELECT ( SELECT MIN(t2.pid)
FROM test t2
WHERE t2.rid = t.rid
AND t2.timestamp = maxtimestamp
) AS pid
, MAX(t.timestamp) AS maxtimestamp
, t.rid
FROM test t
GROUP BY t.rid
But this way, you'll need one more subquery if you want cost included in the shown columns, etc.
So, the group by and join is better solution.
If you want to avoid a JOIN, you can use:
SELECT pid, rid FROM theTable t1 WHERE t1.pid IN ( SELECT MAX(t2.pid) FROM theTable t2 GROUP BY t2.rid);
Try:
select pid,cost, timestamp, rid from theTable order by timestamp DESC limit 2;