I'm trying to create a table on Informix (11.70). I want to add the function WEEKDAY to a datetime value on the CREATE clause, so that I can automatically get an integer returned when inserting data.
For example.
CREATE TABLE orders (
order_num serial
order_date datetime year to second
order_weekday datetime year to second
)
I've tried the following and I get syntax error.
CREATE TABLE orders (
order_num serial
order_date datetime year to second
order_weekday WEEKDAY(datetime) year to second
)
Also this
CREATE TABLE orders (
order_num serial
order_date datetime year to second
WEEKDAY(order_weekday) datetime year to second
)
Is there some way to achieve this?
What are you trying to achieve? It is a fundamental principle of RDBMS normalisation that you should not store a value that can be derived from another existing field. You can calculate WEEKDAY(order_date) any time you need it at virtually no cost.
However, if you really want to do this, what you need to do is write an INSERT TRIGGER, as #JonathanLeffer suggested whilst I was writing this.
You might also want to clarify what you mean by "get an integer returned". The database returns something on a successful INSERT, but you cannot override that arbitrarily. If you really want to do that, you need to write a PROCEDURE to handle inserting the record to the orders table and returning the value you require.
You best bet is probably using FOR EACH ROW triggers which assign to the weekday column on INSERT and UPDATE given the value in the Order-Date column.
As RET notes in his answer, it is often best not to store derived data, especially not such readily derivable data as the weekday.
However, assuming you need to do so, then your trigger would look like:
BEGIN WORK;
CREATE TABLE orders
(
order_num SERIAL NOT NULL,
order_date DATETIME YEAR TO SECOND NOT NULL,
order_weekday INTEGER CHECK (order_weekday BETWEEN 0 AND 6) NOT NULL
);
INSERT INTO orders VALUES(0, CURRENT YEAR TO SECOND, WEEKDAY(MOD(WEEKDAY(TODAY) + 3, 7)));
SELECT *, WEEKDAY(order_date) AS calc_weekday FROM orders;
order_num order_date order_weekday calc_weekday
SERIAL DATETIME YEAR TO SECOND INTEGER SMALLINT
1 2016-02-04 23:21:36 0 4
CREATE TRIGGER i_orders INSERT ON orders
REFERENCING NEW AS NEW
FOR EACH ROW
(
UPDATE orders
SET order_weekday = WEEKDAY(NEW.order_date)
WHERE order_num = NEW.order_num
);
CREATE TRIGGER u_orders UPDATE OF order_date ON orders
REFERENCING NEW AS NEW
FOR EACH ROW
(
UPDATE orders
SET order_weekday = WEEKDAY(NEW.order_date)
WHERE order_num = NEW.order_num
);
INSERT INTO orders(order_num, order_date) VALUES(0, CURRENT YEAR TO SECOND);
SELECT *, WEEKDAY(order_date) AS calc_weekday FROM orders;
order_num order_date order_weekday calc_weekday
SERIAL DATETIME YEAR TO SECOND INTEGER SMALLINT
1 2016-02-04 23:21:36 0 4
2 2016-02-04 23:21:36 4 4
UPDATE orders
SET order_date = order_date - 10 UNITS DAY
WHERE order_weekday != WEEKDAY(order_date);
SELECT *, WEEKDAY(order_date) AS calc_weekday FROM orders;
order_num order_date order_weekday calc_weekday
SERIAL DATETIME YEAR TO SECOND INTEGER SMALLINT
1 2016-01-25 23:21:36 1 1
2 2016-02-04 23:21:36 4 4
ROLLBACK WORK;
Related
I am trying to find the average billing amount per year from 2019 to 2021 for every customer, and I want to return $0 if the customer has no billing from any specific year. I tried doing a left join but unfortunately it does not give the result I need. In the table below, how do I return $0 for customer_id 1 since the year for 2021 does not exist. Thanks.
billing table:
customer_id
billing_id
created_date
billing_amount
1
id_11
2019-06-21
100
1
id_12
2020-05-11
126
1
id_13
2019-12-28
86
2
id_21
2019-12-28
28
2
id_22
2020-12-28
56
2
id_23
2021-12-28
26
Here is my incorrect query:
Select a.customer_id,
extract(year from a.created_date),
avg(a.billing_amount)
from billing as a
left join billing as b
on a.customer_id = b.customer_id
where a.created_date between '2019-01-01' and '2021-12-31'
group by 1, 2
It was edit for add the links
You can try using WITH
First the DDL statements
create database test;
create table billing (
billing_id int,
customer_id int,
created_date date,
billing_amount int
);
alter table billing add constraint pk_billing primary key (billing_id);
create table customer (
customer_id int,
name varchar
)
alter table customer add constraint pk_customer primary key (customer_id);
alter table billing add constraint fk_customer foreign key (customer_id) references customer (customer_id);
Now the DML statements
insert into customer values (1,'Client 1'), (2,'Client 2');
insert into billing values (11,1,'2019-06-21',100),(12,1,'2020-05-11',126),(13,1,'2019-12-28',86),(21,2,'2019-12-28',28),(22,2,'2020-12-28',56),(23,2,'2021-12-28',26);
And finally the DQL query with WITH
with customer_year as (select distinct a.customer_id,vy."Year"
from billing a,(select distinct extract(year from a.created_date) as "Year"
from billing as a
where a.created_date between '2019-01-01' and '2021-12-31') vy
order by customer_id,vy."Year")
select cy.customer_id,cy."Year",
(select case when avg(a.billing_amount) is null then 0 else avg(a.billing_amount) end
from billing a
where a.customer_id = cy.customer_id and
extract(year from a.created_date) = cy."Year")
from customer_year cy;
This answer is for PostgreSQL
For MySQL or MariaDB change something in the DDL and the DQL
You can check in:
PostgreSQL
MySQL
MariaDB
I have a table that looks like this:
orders (
user_id INTEGER
item_id INTEGER
quantity INTEGER
... (more columns and constraints)
CHECK(quantity > 0)
)
I want to decrease the quantity of an order by one, and delete it if that would make the quantity zero.
Is it possible to do this in one statement?
Right now I have:
UPDATE orders SET quantity = quantity - 1 WHERE *blah blah complicated clause*
However when the quantity is 1, this fails and leaves the quantity at 1, because setting it to 0 would be a constraint error.
I want it to instead just delete the row when the quantity is 1, because the order is now empty. How can I do this?
I'd suggest deleting rows, according to the complicated clause with AND quantity < 2 prior to doing the UPDATE.
e.g. (where user_id = 1 represents the complicated clause)
DROP TABLE IF EXISTS orders;
CREATE TABLE IF NOT EXISTS orders (user_id INTEGER, order_id INTEGER, quantity INTEGER, CHECK(quantity > 0) );
INSERT INTO orders VALUES (1,1,10),(1,2,1),(1,3,2),(2,1,3),(2,2,2),(2,3,5);
SELECT * FROM orders;
DELETE FROM orders WHERE user_id = 1 AND quantity < 2;
UPDATE orders SET quantity = quantity -1 WHERE user_id = 1;
SELECT * FROM orders;
results in (all rows before anything is done) :-
and then :-
i.e. orders 1 and 3 have been updated whilst order 2 (circled in above) has been deleted.
I have run this simple query and return no result
enterselect * from record where recorddate = TO_DATE(2018, 'YYYY');
I have tested
Select to_date(recorddate,'YYYY') from record
It return ora01830:date format picture ends before converting entire input string
Here is my table structure :
create table record(
recordid varchar2(10),
singerid varchar2(10),
producedcountryid varchar2(10),
songid varchar2(10),
recorddate date,
constraint recordid_pk primary key (recordid),
constraint singerid2_fk foreign key (singerid) references singer(singerid),
constraint songid2_fk foreign key (songid) references song(songid)
);
DATEs in Oracle include hours, minutes and seconds.
So unless there are any RECORDDATEs that are at exactly 00:00:00 in the given month, the predicate where recorddate = TO_DATE(2018, 'YYYY') will not find anything to match.
In the second query, to_date(recorddate,'YYYY') is not a valid syntax for using to_date. Please see to_date for more information.
If you are trying to find all the RECORDs with RECORDDATEs in the year 2018, There are many ways to do so. Below are a couple examples.
CREATE TABLE RECORD (LOREM_IPSUM NUMBER, RECORDDATE DATE);
INSERT INTO RECORD VALUES (1,DATE '2017-05-05');
INSERT INTO RECORD VALUES (2,DATE '2018-05-05');
COMMIT;
SELECT * FROM RECORD;
LOREM_IPSUM RECORDDATE
1 05-MAY-17
2 05-MAY-18
Then:
SELECT * FROM RECORD WHERE EXTRACT(YEAR FROM RECORDDATE) = 2018;
Result:
LOREM_IPSUM RECORDDATE
2 05-MAY-18
-- Or:
SELECT * FROM RECORD WHERE TO_CHAR(RECORDDATE,'YYYY') = '2018';
Result:
LOREM_IPSUM RECORDDATE
2 05-MAY-18
If you want records from a specific year + month, you can:
SELECT * FROM RECORD WHERE TRUNC(RECORDDATE,'MM') = DATE '2017-05-01';
Result:
LOREM_IPSUM RECORDDATE
1 05-MAY-17
You will get the result set you want by querying the year-part of the date column recorddate.
select * from record
where extract (year from recorddate) = 2018
In sql server how can we get days gap in between 2 dates (I am fetching these 2 dates from other two columns as Sdate and EDate). I want to include the resultant data as another column
CREATE TABLE Remaingdays1
(
id INT NOT NULL PRIMARY KEY,
SDate DATE,
EDate Date,
remaingdays as SDate-EDate-- This should be the resultant of days
);
you can use computed column:
CREATE TABLE Remaingdays1 ( id INT NOT NULL PRIMARY KEY,
SDate DATE, EDate Date,
remaingdays as (datediff(day, sdate,edate)))
ID Level Effective Date ExpirationDate
000012-12 2 12/01/2005 NULL
000012-12 1 12/01/2005 NULL
000012-12 2 12/01/2005 01/01/2009
000012-A12 2 10/01/1994 11/30/2005
000012-A12 2 01/01/1999 11/30/2005
000012-A12 2 09/01/2001 11/30/2005
000012-A12 1 12/01/2005 12/31/2007
Only most current Records will be fetched. It means in the above scenario
Exp date - If null the record is still active.
If greater then current time stamp, its future exp date , which means still active.
If less then current time stamp , then terminated.
Most current is the most active or latest terminated record. If it has active and terminated then only active will be shown. Else last terminated record.
One ID can have 2 rows for same effective date and exp date but multiple levels. So in that case we would need to select only 1 record for level one.
So as per the data set above below is the intended output
Output
000012-12 1 12/01/2005 NULL
000012-A12 2 12/01/2005 01/01/2009
Please help
Thomas. Please look into the following data set.
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'1994-10-01',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'1999-01-01',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',2,'2001-09-01',NULL );
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'2003-01-01','2007-12-31');
When you run the query it should give
000872-A24 2 09/01/2001 NULL
but now it returns
000872-A24 1 01/01/2003 12/31/2007
It is difficult to provide an answer without knowing the database product.
1. if there is no auto_increment/identity column
2. and if there is no other primary key (which is a bad idea obviously)
3. and if the given database product supports `CURRENT_TIMESTAMP` (each DBMS will likely have some equivalent to the current date and time)
4. and if the target date by which you measure "latest" is the current date and time
Select Id, Level
From Table As T
Where T. EffectiveDate = (
Select Max(T2.EffectiveDate)
From Table As T2
Where T2.ID = T.ID
And ( T2.EffectiveDate Is Null
Or (
CURRENT_TIMESTAMP >= T2.EffectiveDate
And CURRENT_TIMESTAMP <= T2.ExpirationDate
)
)
)
You will note a number of caveats in my answer. That is an indicatation that we need more information:
What database product and version?
Is there an auto_incrementing, unique key on the table?
How does the Level fit into the results you want? (Please expand your sample data to include edge cases).
What should happen if the current date and time is prior to the effective date that has a null expiration date?
EDIT
Now that we know you are using SQL Server 2008, that makes the solution easier:
If object_id('tempdb..#Test') is not null
Drop Table #Test;
GO
Create Table #Test (
PkCol int not null identity(1,1) Primary Key
, Id varchar(50) not null
, Level int not null
, EffectiveDate datetime not null
, ExpirationDate datetime null
);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',2,'12/01/2005',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',1,'12/01/2005',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',2,'12/01/2005','01/01/2009');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'10/01/1994','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'01/01/1999','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'09/01/2001','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',1,'12/01/2005','12/31/2007');
With Items As
(
Select PkCol, Id, Level, EffectiveDate, ExpirationDate
, Row_Number() Over ( Partition By Id
Order By EffectiveDate Desc, Coalesce(ExpirationDate,'99991231') Desc, Level Asc ) As Num
From #Test
)
Select PkCol, Id, Level, EffectiveDate, ExpirationDate
From Items
Where Num = 1
In your sample output, you have the combination ('000012-A12',2,'12/01/2005','01/01/2009') which does not appear in your original data.
I'm using two features that were added in SQL Server 2005: common-table expressions and ranking functions. The common-table expression Item acts like a in-place view or query. The ranking function Row_Number is where the real magic happens. As the name implies, it returns a sequential list of numbers ordered by the Order By clause. However, it also restarts numbering for each Id value (that's the Partition By bit). By filtering on Num = 1, I'm returning the "top" value for each Id.