I have a database of multiple employees and how many hours they work. Please see below.
So, every Time.Type that says Wellsite needs two rows. One for the Attribute: Reg Work Day and the next row showing the Attribute: Job ID. My code is showing all rows that have Job ID which is what I need to get rid of unless they have the Reg Work Day above it that meets my condition (see below). Also, I have a row of On Office Time Type that is fine and I'm not having any issues with.
I've been assisted to do this, but for one employee. Now I need my data to work through this process for multiple employees.
The condition I need to be true is when a row has a Time.Type of "Wellsite/Job/Vessel" and an Attribute that says "Regular Work Day" the "Hourly Value" needs to be >15. Also, I need the row right below it which has an Attribute of "Job ID" with a job number under the "value" tab too. From what I have learned is that the Date column can be used to keep the rows together, but i just need it to work for the certain employees/days rather than everything.
The table below shows what I have and what I want it to do is below this table..
CREATE TABLE mytable(
Complete_Name VARCHAR(9) NOT NULL PRIMARY KEY
,Day_of_Week VARCHAR(7) NOT NULL
,Date VARCHAR(11) NOT NULL
,Time_Type VARCHAR(19) NOT NULL
,Attribute VARCHAR(16) NOT NULL
,Value VARCHAR(28) NOT NULL
,Hourly_value_in_decimals NUMERIC(5,2)
);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeA','Tuesday','20-Oct-2015','Wellsite/Job/Vessel','Regular Work Day','RGWD - Regular Work Day (BR)',16.75);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeA','Tuesday','20-Oct-2015','Wellsite/Job/Vessel','Job ID','2213840',NULL);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeB','Tuesday','20-Oct-15','Wellsite/Job/Vessel','Regular Work Day','RGWD - Regular Work Day (BR)',2.25);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeB','Tuesday','20-Oct-15','Wellsite/Job/Vessel','Job ID','2213840',NULL);
Table:
Complete_Name Day_of_Week Date Time_Type Attribute Value Hourly_value_in_decimals
------------- ----------- ----------- ------------------- ---------------- ---------------------------- ---------------------------------------
EmployeeA Tuesday 20-Oct-2015 Wellsite/Job/Vessel Regular Work Day RGWD - Regular Work Day (BR) 16.75
EmployeeA Tuesday 20-Oct-2015 Wellsite/Job/Vessel Job ID 2213840 NULL
EmployeeB Tuesday 20-Oct-15 Wellsite/Job/Vessel Regular Work Day RGWD - Regular Work Day (BR) 2.25
EmployeeB Tuesday 20-Oct-15 Wellsite/Job/Vessel Job ID 2213840 NULL
What I want to see is...
CREATE TABLE mytable(
Complete_Name VARCHAR(9) NOT NULL PRIMARY KEY
,Day_of_Week VARCHAR(7) NOT NULL
,Date VARCHAR(11) NOT NULL
,Time_Type VARCHAR(19) NOT NULL
,Attribute VARCHAR(16) NOT NULL
,Value VARCHAR(28) NOT NULL
,Hourly_value_in_decimals NUMERIC(5,2)
);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeA','Tuesday','20-Oct-2015','Wellsite/Job/Vessel','Regular Work Day','RGWD - Regular Work Day (BR)',16.75);
INSERT INTO mytable(Complete_Name,Day_of_Week,Date,Time_Type,Attribute,Value,Hourly_value_in_decimals) VALUES ('EmployeeA','Tuesday','20-Oct-2015','Wellsite/Job/Vessel','Job ID','2213840',NULL);
Table:
Complete_Name Day_of_Week Date Time_Type Attribute Value Hourly_value_in_decimals
------------- ----------- ----------- ------------------- ---------------- ---------------------------- ---------------------------------------
EmployeeA Tuesday 20-Oct-2015 Wellsite/Job/Vessel Regular Work Day RGWD - Regular Work Day (BR) 16.75
EmployeeA Tuesday 20-Oct-2015 Wellsite/Job/Vessel Job ID 2213840 NULL
So building on my prior answer you need to pull for different employees based on different criteria.
For example employee name Hogan might want days > 15 while employee named NotHogan might want days > 24.
To do this you put the criteria in a table (in the example below I build the table dynamically) and then join to that table to get the dates and names required.
Then I join to this result to get the final data.
SELECT *
FROM ROSTER
JOIN (
-- A list of dates and users we want to select
SELECT `Date`, Name
FROM ROSTER
JOIN (
-- Innermost table, our criteria, this can be an actual table in your db
SELECT 'Hogan' as N, 15 as C
UNION ALL
SELECT 'NotHogan' as N, 24 as C
) Criteria ON
Criteria.N = ROSTER.Name AND
ROSTER.`Hourly Value` > Criteria.C AND
Attribute = 'Reg Work Day'
) sub ON ROSTER.`Date` = sub.`Date` and ROSTER.Name = Sub.Name
Related
I have a table of following nature. The process of transactions on the database where this table resides follows a path where a user can change the delivery date of one, more than one or all of the items. Now, what i want is a set of rows where the user updates the delivery date corresponding to the specific items. You can see that the PO_Number is same for all the items and so is the PO_Date. So, how would the required rows may be queried and displayed?
PO_NO PO_DT ITEM_CODE DEL_DATE UPDATED_ON OLD_DEL_DATE NEW_DEL_DATE
========================================================================================
01T0012384 20/05/2015 UP01662 24/06/2015 23/5/2015 20/06/2015 24/06/2015
01T0012384 20/05/2015 UP01762 22/06/2015 21/5/2015 20/06/2015 22/06/2015
01T0012384 20/05/2015 UP01661 24/06/2015 23/5/2015 20/06/2015 24/06/2015
01T0012384 20/05/2015 UP01763 22/06/2015 21/5/2015 20/06/2015 22/06/2015
Please also consider a scenario if the user does his last transaction and updates delivery dates for item_code- (UP01662,UP01661) for a (say)10th time on a day. Then in the end all i want is a query which fetches data-sets for only these two items and nothing else.
Any help would greatly be appreciated.
The idea is to create your custom function to check if any column was changed and an after update trigger. If you know some columns that must always change with every update than this is even easier to check if your row was changed or not - otherwise you have to check all columns from user_tab_columns for your table.
For the generic approach, please check:
https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:59412348055
i think this might help u a little https://social.msdn.microsoft.com/Forums/sqlserver/en-US/4a0742b7-b976-42b7-8230-4ae46441a63a/when-was-the-last-time-a-record-was-addedmodified-in-a-sql-table?forum=transactsql
declare #objectid int
select #objectid = object_id from sys.objects where name = 'YOURTABLENAME'
select top 1 * from sys.dm_db_index_usage_stats where object_id = #objectid
and last_user_update is not null
order by last_user_update
Please let me know if this is what you need.
create table xx_ap_orders ( po_number varchar2(10)
, item_code varchar2(10)
, amount number
, updated_date date
);
So, the scenario is:
all records were updated 5 days ago
row with MOUSE item code was updated 4 days ago
CABLE and BATERY item codes were updated 2 days ago.
Records in table would look like:
insert into xx_ap_orders values ('PO_500','LAPTOP', 3500, trunc (sysdate - 5) );
insert into xx_ap_orders values ('PO_500','MOUSE', 200, trunc (sysdate - 4) );
insert into xx_ap_orders values ('PO_500','CABLE', 100, trunc (sysdate - 2) );
insert into xx_ap_orders values ('PO_500','BATERY', 500, trunc (sysdate - 2) );
If you would like to select only CABLE and BATERY records (the last updated) you have the following SQL:
SELECT * FROM (
SELECT po_number
, item_code
, amount
, updated_date
, rank() OVER (PARTITION BY po_number
order by updated_date desc) rank_updated
FROM XX_AP_ORDERS)
WHERE rank_updated = 1
;
Please help. I have 2 tables. First one is called Reservation and second one is called LastOrders.
There are the columns DATE_FROM in Date type and DATE:)_TO in date type too. And I want if the date in table Reservation in column DATE_TO = SYSDATE then delete this row and insert new row to table LastOrders I think a trigger can do that, but I dont know how. Any idea? Thank you very much and have a nice Christmas :)
TABLE RESERVATION
Name Null Type
-------------------------- -------- -------------
ID NOT NULL VARCHAR2(25)
DESCRIPTION NOT NULL VARCHAR2(100)
DATE_FROM NOT NULL DATE
DATE_TO NOT NULL DATE
TABLE LASTORDERS
Name Null Type
-------------------------- -------- -------------
ID NOT NULL VARCHAR2(25)
DESCRIPTION NOT NULL VARCHAR2(100)
DATE_FROM NOT NULL DATE
DATE_TO NOT NULL DATE
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE RESERVATION (
ID VARCHAR2(25) CONSTRAINT RESERVATION__ID__NN NOT NULL,
DESCRIPTION VARCHAR2(100) CONSTRAINT RESERVATION__DE__NN NOT NULL,
DATE_FROM DATE CONSTRAINT RESERVATION__DF__NN NOT NULL,
DATE_TO DATE CONSTRAINT RESERVATION__DT__NN NOT NULL
)
/
CREATE TABLE LASTORDERS (
ID VARCHAR2(25) CONSTRAINT LASTORDERS__ID__NN NOT NULL,
DESCRIPTION VARCHAR2(100) CONSTRAINT LASTORDERS__DE__NN NOT NULL,
DATE_FROM DATE CONSTRAINT LASTORDERS__DF__NN NOT NULL,
DATE_TO DATE CONSTRAINT LASTORDERS__DT__NN NOT NULL
)
/
CREATE PROCEDURE fulfilReservation(
I_ID IN RESERVATION.ID%TYPE,
O_SUCCESS OUT NUMBER
)
AS
r_reservation RESERVATION%ROWTYPE;
BEGIN
DELETE FROM RESERVATION
WHERE ID = I_ID
AND SYSDATE BETWEEN DATE_FROM AND DATE_TO
RETURNING ID, DESCRIPTION, DATE_FROM, DATE_TO INTO r_reservation;
INSERT INTO lastorders VALUES r_reservation;
o_success := 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
o_success := 0;
END;
/
INSERT INTO RESERVATION VALUES ( 1, 'Test', SYSDATE - 1, SYSDATE + 1 )
/
DECLARE
success NUMBER(1,0);
BEGIN
fulfilReservation( 1, success );
END;
/
Query 1:
SELECT * FROM RESERVATION
Results:
No Results
Query 2:
SELECT * FROM LASTORDERS
Results:
| ID | DESCRIPTION | DATE_FROM | DATE_TO |
|----|-------------|----------------------------|----------------------------|
| 1 | Test | December, 24 2015 18:59:07 | December, 26 2015 18:59:07 |
Trigger can do such job but it will fire only on some changes on table data. If you need to move data between tables in real-time you have to use jobs.
begin
dbms_scheduler.create_job(
job_name => 'my_job',
job_type => 'PLSQL_BLOCK',
enabled => true,
repeat_interval => 'FREQ=DAILY; INTERVAL=1',
start_date => round(sysdate,'DD'),
job_action => 'begin
insert into LastOrders
select *
from Reservation
where DATE_TO = round(sysdate,''DD'');
delete Reservation where DATE_TO = round(sysdate,''DD'');
commit;
end;');
end;
This job will move data from Reservation to LastOrders every day at 00:00.
Ooo, tricky one!
The main question is how "realtime" you need this to be. You need to consider that data changes on both sides of the equation - meaning, there is new data that enters the table and should be compared to SYSDATE (that's the easy part). The other side is SYSDATE, changing every second. So if you need your data to be as accurate as possible you basically need to check the entire table every second to see if any of the DATE_DO has "arrived" to SYSDATE. This is where the job #Artem suggested comes in handy.
BUT
Running the job once a day means that rows would be moved only once a day, and running it every second would mean querying the table every second (potential performance overhead).
SO, you need to be smart about it -
Add an index on DATE_TO
Make sure to use bind variables in repeating
queries you're making on the table. i.e, change the query in the
job.
Build the job in a smart way - Let's say you run it every 5
min (because you don't want your potentially heavy query to run
every sec), and you check for the lines that will reach SYSDATE in
the next 5 min. You can then move to a temporary table that holds
only these lines, and have another job run on that every second.
Sounds a bit complex, but would save you lot's of trouble if this data is planned to scale.
Good luck, Let me know if you have any questions.
I'm looking for input on getting a COUNT of records that were 'active' in a certain date range.
CREATE TABLE member {
id int identity,
name varchar,
active bit
}
The scenario is one where "members" number fluctuate over time. So I could have linear growth where I have 10 members at the beginning of the month and 20 at the end. Currently We go off the number of CURRENTLY ACTIVE (as marked by an 'active' flag in the DB) AT THE TIME OF REPORT. - this is hardly accurate and worse, 6 months from now, my "members" figure may be substantially different than now. and Since I'm doing averages per user, if I run a report now, and 6 months from now - the figures will probably be different.
I don't think a simple "dateActive" and "dateInactive" will do the trick... due to members coming and going and coming back etc. so:
JOE may be active 12-1 and deactivated 12-8 and activated 12-20
so JOE counts as being a 'member' for 8 days and then 11 days for a total of 19 days
but the revolving door status of members means keeping a separate table (presumably) of UserId, status, date
CREATE TABLE memberstatus {
member_id int,
status bit, -- 0 for in-active, 1 for active
date date
} (adding this table would make the 'active' field in members obsolete).
In order to get a "good" Average members per month (or date range) - it seems I'd need to get a daily average, and do an average of averages over 'x' days. OR is there some way in SQL to do this already.
This extra "status" table would allow an accurate count going back in time. So in a case where you have a revenue or cost figure, that DOESN'T change or is not aggregate, it's fixed, that when you want cost/members for last June, you certainly don't want to use your current members count, you want last Junes.
Is this how it's done? I know it's one way, but it the 'better' way...
#gordon - I got ya, but I guess I was looking at records like this:
Members
1 Joe
2 Tom
3 Sue
MemberStatus
1 1 '12-01-2014'
1 0 '12-08-2014'
1 1 '12-20-2014'
In this way I only need the last record for a user to get their current status, but I can track back and "know" their status on any give day.
IF I'm understanding your method it might look like this
CREATE TABLE memberstatus {
member_id int,
active_date,
inactive_date
}
so on the 1-7th the record would look like this
1 '12-01-2014' null
and on the 8th it would change to
1 '12-01-2014' '12-08-2014'
the on the 20th
1 '12-01-2014' '12-08-2014'
1 '12-20-2014' null
Although I can get the same data out, it seems more difficult without any benefit - am i missing something?
You could also use a 2 table method to have a one-to-many relationship for working periods. For example you have a User table
User
UserID int, UserName varchar
and an Activity table that holds ranges
Activity
ActivityID int, UserID int, startDate date, (duration int or endDate date)
Then whenever you wanted information you could do something like (for example)...
SELECT User.UserName, count(*) from Activity
LEFT OUTER JOIN User ON User.UserID = Activity.UserID
WHERE startDate >= '2014-01-01' AND startDate < '2015-01-01'
GROUP BY User.UserID, User.UserName
...to get a count grouped by user (and labeled by username) of the times they were became active in 2014
I have used two main ways to accomplish what you want. First would be something like this:
CREATE TABLE [MemberStatus](
[MemberID] [int] NOT NULL,
[ActiveBeginDate] [date] NOT NULL,
[ActiveEndDate] [date] NULL,
CONSTRAINT [PK_MemberStatus] PRIMARY KEY CLUSTERED
(
[MemberID] ASC,
[ActiveBeginDate] ASC
)
Every time a member becomes active, you add an entry, and when they become inactive you update their ActiveEndDate to the current date.
This is easy to maintain, but can be hard to query. Another option is to do basically what you are suggesting. You can create a scheduled job to run at the end of each day to add entries to the table .
I recommend setting up your tables so that you store more data, but in exchange the structure supports much simpler queries to achieve the reporting you require.
-- whenever a user's status changes, we update this table with the new "active"
-- bit, and we set "activeLastModified" to today.
CREATE TABLE member {
id int identity,
name varchar,
active bit,
activeLastModified date
}
-- whenever a user's status changes, we insert a new record here
-- with "startDate" set to the current "activeLastModified" field in member,
-- and "endDate" set to today (date of status change).
CREATE TABLE memberStatusHistory {
member_id int,
status bit, -- 0 for in-active, 1 for active
startDate date,
endDate date,
days int
}
As for the report you're trying to create (average # of actives in a given month), I think you need yet another table. Pure SQL can't calculate that based on these table definitions. Pulling that data from these tables is possible, but it requires programming.
If you ran something like this once-per-day and stored it in a table, then it would be easy to calculate weekly, monthly and yearly averages:
INSERT INTO myStatsTable (date, activeSum, inactiveSum)
SELECT
GETDATE(), -- based on DBMS, eg., "current_date" for Postgres
active.count,
inactive.count
FROM
(SELECT COUNT(id) FROM member WHERE active = true) active
CROSS JOIN
(SELECT COUNT(id) FROM member WHERE active = true) inactive
I've got a .Net application with an attendance table which has fields for a Start and End date. I'm struggling to show a graph of attendance for a given period. I can easily find how many rows are applicable on any given day using between but I can't get my head around pivoting results so that I can graph a count of rows per day. I could run a SQL query for every day individually and then graph the results but is there any way of doing this with T-SQL that I could then use to graph with?
Edit:-
Apologies as this is the first time I've asked a question here, but as huMpty duMpty has stated the question probably needs more clarification. I've got both a startdate and enddate column in the sql db and I need to count per day if the range between these dates falls between the range of the selection criteria. e.g if I've got a start date of 2013-01-01 and end date 2013-01-10 and I report on a period of 2013-01-09 to 2013-01-11 then i'm looking at getting a result for 1 for 2013-01-09 and 1 for 2013-01-10... Hope this make more sense and thanks for your assistance
I think you have a table with start and end dates; for a given date range, you would like to know the given number of records that fall on each date.
I believe this problem may be solved with a numbers table. I created a numbers table on the fly in a stored procedure, but I recommend creating a permanent numbers table in your production code. Here's the SQL Fiddle.
Create Table Attendance (
id int primary key identity(1,1) not null
,start_date date not null
,end_date date not null
);
Go
Insert Attendance(start_date, end_date)
Values ('1/1/2013', '1/10/2013')
,('1/10/2013', '1/15/2013')
,('2/20/2013', '3/1/2013');
Go
-- Create numbers table. See: Method 3 of http://stackoverflow.com/a/1407488/772086
With Numbers(Number) As
(
Select 1 As Number
Union All
Select Number + 1 From Numbers Where Number < 10000
)
Select
AttendanceDate = Convert(date, DateAdd(day,Numbers.number, '1/1/2000'))
,AttendanceCount = Count(*)
From dbo.Attendance
Join Numbers
On Numbers.Number
Between DateDiff(day, '1/1/2000', Attendance.start_date)
And DateDiff(day, '1/1/2000', Attendance.end_date)
-- Reporting range between 1/9 and 1/11
Where DateAdd(day,Numbers.number, '1/1/2000') Between '1/9/2013'
And '1/11/2013'
Group By Convert(date, DateAdd(day,Numbers.number, '1/1/2000'))
Option(MaxRecursion 10000);
All dates are in US format (m/d/yy) - you may want to switch those to the internationalized standard (yyyy-mm-dd) in your production code.
You said you wanted a count by day in a date range. That can be done with a COUNT with a GROUP BY clause.
I don't know your schema, but a solution might look like this:
declare #MyTable table
(
ID int identity(1,1) primary key clustered,
MyDate smalldatetime
)
insert into #MyTable (MyDate)
values
('2012-12-31'), -- before the date range, so not included in results
('2013-01-10'),
('2013-01-10'), -- appears twice
('2013-01-11'), -- appears once
('2013-01-12') -- after the date range, so not included in results
select * from #MyTable
select
MyDate,
count(*)
from #MyTable
where MyDate between '2013-01-09' and '2013-01-11'
group by MyDate
I have a table with records that look like this:
CREATE TABLE sample (
ix int unsigned auto_increment primary key,
start_active datetime,
last_active datetime
);
I need to know how many records were active on each of the last 30 days. The days should also be sorted incrementing so they are returned oldest to newest.
I'm using MySQL and the query will be run from PHP but I don't really need the PHP code, just the query.
Here's my start:
SELECT COUNT(1) cnt, DATE(?each of last 30 days?) adate
FROM sample
WHERE adate BETWEEN start_active AND last_active
GROUP BY adate;
Do an outer join.
No table? Make a table. I always keep a dummy table around just for this.
create table artificial_range(
id int not null primary key auto_increment,
name varchar( 20 ) null ) ;
-- or whatever your database requires for an auto increment column
insert into artificial_range( name ) values ( null )
-- create one row.
insert into artificial_range( name ) select name from artificial_range;
-- you now have two rows
insert into artificial_range( name ) select name from artificial_range;
-- you now have four rows
insert into artificial_range( name ) select name from artificial_range;
-- you now have eight rows
--etc.
insert into artificial_range( name ) select name from artificial_range;
-- you now have 1024 rows, with ids 1-1024
Now make it convenient to use, and limit it to 30 days, with a view:
Edit: JR Lawhorne notes:
You need to change "date_add" to "date_sub" to get the previous 30 days in the created view.
Thanks JR!
create view each_of_the_last_30_days as
select date_sub( now(), interval (id - 1) day ) as adate
from artificial_range where id < 32;
Now use this in your query (I haven't actually tested your query, I'm just assuming it works correctly):
Edit: I should be joining the other way:
SELECT COUNT(*) cnt, b.adate
FROM each_of_the_last_30_days b
left outer join sample a
on ( b.adate BETWEEN a.start_active AND a.last_active)
GROUP BY b.adate;
SQL is great at matching sets of values that are stored in the database, but it isn't so great at matching sets of values that aren't in the database. So one easy workaround is to create a temp table containing the values you need:
CREATE TEMPORARY TABLE days_ago (d SMALLINT);
INSERT INTO days_ago (d) VALUES
(0), (1), (2), ... (29), (30);
Now you can compare a date that is d days ago to the span between start_active and last_active of each row. Count how many matching rows in the group per value of d and you've got your count.
SELECT CURRENT_DATE - d DAYS, COUNT(*) cnt,
FROM days_ago
LEFT JOIN sample ON (CURRENT_DATE - d DAYS BETWEEN start_active AND last_active)
GROUP BY d
ORDER BY d DESC; -- oldest to newest
Another note: you can't use column aliases defined in your select-list in expressions until you get to the GROUP BY clause. Actually, in standard SQL you can't use them until the ORDER BY clause, but MySQL supports using aliases in GROUP BY and HAVING clauses as well.
Turn the date into a unix timestamp, which is seconds, in your query and then just look for the difference to be <= the number of seconds in a month.
You can find more information here:
http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_unix-timestamp
If you need help with the query please let me know, but MySQL has nice functions for dealing with datetime.
[Edit] Since I was confused as to the real question, I need to finish the lawn but before I forget I want to write this down.
To get a count of the number by day you will want your where clause to be as I described above, to limit to the past 30 days, but you will need to group by day, and so select by converting each start to a day of the month and then do a count of those.
This assumes that each use will be limited to one day, if the start and end dates can span several days then it will be trickier.