Attempting to add a new column in SQL by subtracting a year - sql

Noob at this. So I will cut to the point on this. This is a class assignment and my professor has not answered emails since Thursday. The problem I am trying to solve is -
"Write a query that will subtract one year from the production date of each album and label this as the RecordDate and display the album title, production date, and record date for each entry in the table. Order the results by album title."
Here is what the table is supposed to look like
Here is the query I used (given in class I know repeated inserts but what he wanted)...
CREATE TABLE ALBUM(
ALBUM_ID char(4) UNIQUE NOT NULL,
ALBUM_TITLE varchar(255),
ALBUM_YEAR year,
ALBUM_PRODUCER varchar(255),
Primary Key(ALBUM_ID)
);
INSERT INTO ALBUM (ALBUM_ID, ALBUM_TITLE, ALBUM_YEAR, ALBUM_PRODUCER)
VALUES ('A001', 'Awake', '1994', 'East West Record');
INSERT INTO ALBUM (ALBUM_ID, ALBUM_TITLE, ALBUM_YEAR, ALBUM_PRODUCER)
VALUES ('A002', 'Moving Pictures', '1981', 'Anthem');
INSERT INTO ALBUM (ALBUM_ID, ALBUM_TITLE, ALBUM_YEAR, ALBUM_PRODUCER)
VALUES ('A003', 'Damage', '2013', 'RCA REcords');
INSERT INTO ALBUM (ALBUM_ID, ALBUM_TITLE, ALBUM_YEAR, ALBUM_PRODUCER)
VALUES ('A004', 'Continuum', '2006', 'Columbia Records');
Here is what I used to START to answer the question
ALTER TABLE ALBUM ADD RECORD_DATE INT;
UPDATE ALBUM SET RECORD_DATE=(ALBUM_YEAR-1);
This does make a new column and gives the results for what I want so far (have not gotten to the later part of the question). But this is two different queries...
So, from advice from SQL experts, to achieve what he wants will I have to write multiple queries or can this be done in one single query? Also are my datatypes okay?
No, I am not asking for the SQL to do this. This isn't a "PLEASE DO MY HOMEWORK FOR ME". And sorry, but it has to all be in cap locks because he wants it that way.

Andrew the tutor hasn't asked you to add a new column or anything, he has asked you to present data in a specific way.
We store data in SQL Server using some specific rules called Database Normalization rules.
But to show data in any specific form we write SELECT queries which select the data from the tables and we use all sorts of functions and methods to manipulate data at run-time and present the data in required structure/format/way.
Similarly in this case for your requirement you do not need to add another column just to do what your tutor as asked to do, it actually violates the rules of normalization. All you need is a simple SELECT query, which will show the data in the required format/way.
The select query will be something as simple as ....
SELECT [ALBUM_TITLE] AS [Album Title]
,[ALBUM_YEAR] AS [Production Date]
,[ALBUM_YEAR] - 1 AS [Record Date]
FROM ALBUM
ORDER BY [ALBUM_TITLE]

You don't need to alter the table at all. You're just going to want to select an additional column. I won't write the full query for you, but I will show you what I mean:
SELECT ALBUM_TITLE, ALBUM_YEAR, ALBUM_YEAR - 1 AS RECORD_DATE
-- rest of the query here --

Related

I have a table where I need to update or insert depending on field paramaters

I have spent many hours researching this problem and trying various solutions but I never quite find a suitable solution for my specific problem. I am new to SQL and some of the examples are confusing as well.
So here is my dilemma. I have a equipment table that tracks oil changes for specific units in a database. The table looks like this:
**id UnitID Posted_On Date_Completed Note OverDueBy**
1 BT-109F 2019-02-04 2019-02-14 Hrs Overdue 23
1 BT-108G 2020-01-17 2020-01-22 Days Overdue 12
1 BT-122K 2020-01-02 2020-01-16 Days Overdue 12
1 BT-109F 2019-02-04 Days Overdue 3
The example records above need to be created or updated by the query. The date completed is entered manually by the technician when he has completed the oil change.
What I want the query to do is, Check to see if a specific Unit has a record where the 'Date_Completed' field is empty, and if so update the 'OverDueBy' field to reflect the new value. If all the records for the specified Unit have the 'Date_Completed' fields filled in, then the query should create a new record will all fields filled in except for the 'Date_Completed' field.
Can anyone help me construct such a query?
Thanks
Clan
First create a unique partial index for the column UnitID:
CREATE UNIQUE INDEX idx_unit ON tablename(UnitID)
WHERE Date_Completed IS NULL;
so that only 1 row with Date_Completed=null is allowed for each UnitID.
So a statement like this:
INSERT INTO tablename(id, UnitID, Posted_On, Date_Completed, Note, OverDueBy)
VALUES (?, 'BT-109F', ?, null, ?, ?)
ON CONFLICT(UnitID) WHERE Date_Completed IS NULL DO UPDATE
SET OverDueBy = ?;
will insert the new values only if there is no row already for UnitID='BT-109F' with null in Date_Completed.
But if there is such a row then it will update the column OverDueBy.
I'm not sure what values you want to insert or what will be the updated value so replace the ? with the appropriate values.
Firstly I would use a view rather than a table to store any calculated data - it reduces storage overheads and will update the calculation every time the view is opened.
If you're using SQLite you should be able to get the overdue by subtracting the Posted_On from its function to return today's date something like date('now') or julianday('now') - read up on and test the functions to ensure it does what you want.
So along the lines of:-
create view MyView as select *, julianday('now') - julianday(Posted_On) as OverDueBy from ClansTable where Date_Completed is null;
If you want to store a snapshot you can always create a table from a view in any case:-
create table MyStoredOverduesOn4thFeb as select * from MyView;
You can find your units that have all Date_Completed and create a single new record like so:-
Create table CompletedUnits as select id, UnitID, max(posted_on) as latest_posted_on, '' as Date_Completed from ClansTable group by id, UnitID having count(*) = count(Date_Complete);
Test this SQL and see if you can get it working - note I've created a text field for the date. Apparently there is no date/datetime data type as such:-
https://www.sqlitetutorial.net/sqlite-date/
Hope this helps,
Phil
I think you need something like this:
MERGE INTO EQUIPMENT A
USING (SELECT * FROM EQUIPMENT B WHERE DATE_COMPLETED IS NULL) C
ON (A.UNITID=C.UNITID)
WHEN MATCHED THEN UPDATE SET A.OVERDUEBY="new value"
WHEN NOT MATCHED THEN INSERT (A.id,A.UnitID,A.Posted_On,A.Date_Completed,A.Note,A.OverDueBy)
VALUES (C.id,C.UnitID,C.Posted_On,NULL,C.Note,C.OverDueBy)
Not sure where new values from update will come from. It's not clear in your question. But something like this could work.

SQL Trigger to allow only customers who are old enough to buy a book?

I have 3 tables, person, audiobook and audiobook_purchases. My database is running MariaDB.
person has fields: id, date_of_birth;
audiobook has fields: ISBN, age_rating, title;
audiobook_purchases has fields: ISBN, customer_id, date_of_purchase;
I'm trying to write a trigger to make sure that when a customer tried to purchases an audiobook, they are old enough to do so according to the age_rating in audiobook.
For example, If Harry Potter and the Philosipher's Stone had age rating 16 and customer Jennifer (ID 1) with date of birth 2010-01-01 tried to purchase this book, this would not be allowed, but Dominick(ID 2) with date_of_birth 1978-01-01 would be allowed.
Please could someone show me a way to run this trigger?
I don't know MariaDB in particular, so my answer may need some adjustments.
You want to create an insert trigger on audiobook_purchase so that a new order will be inserted only if the person who wants to place the order is old enough according to audiobook.age_rating.
First you need to figure out a way of extracting the year from person.date_of_birth. Something like the YEAR() scalar function will probably be available. MariaDB may also provide a NOW() function, which gives the current date. So the person age right now will be: YEAR(NOW()) - YEAR(person.date_of_birth).
Then you have to write the insert trigger. The tricky part is to query the person table to get the person date_of_birth from his id, then to compare it to audiobook.age_rating.
Let's set out an example. First we declare the tables schemas:
CREATE TABLE person(id, name, date_of_birth);
CREATE TABLE audiobook(isbn, age_rating, title);
CREATE TABLE audiobook_purchases(isbn, customer_id, date_of_purchase);
Then we put in some data:
INSERT INTO person VALUES (10, "jennifer", '2010-01-01');
INSERT INTO person VALUES (20, "dominick", '1978-01-01');
INSERT INTO audiobook VALUES (1234, 16, "harry potter");
Then we create the trigger:
CREATE TRIGGER check_purchases
AFTER INSERT ON audiobook_purchases
FOR EACH ROW
WHEN (
SELECT strftime('%Y', 'now') - strftime('%Y', date_of_birth) AS age
FROM person
WHERE new.customer_id=person.id) < (
SELECT audiobook.age_rating
FROM audiobook
WHERE audiobook.isbn=new.isbn)
BEGIN
DELETE FROM audiobook_purchases
WHERE isbn=new.isbn AND
customer_id=new.customer_id AND
date_of_purchase=new.date_of_purchase;
END;
I'll broke down the trigger into smaller steps:
AFTER INSERT ON audiobook_purchases creates a trigger on table audiobook_purchases which will be triggered after the insertion of a new record.
FOR EACH ROW applies the trigger to each new record inserted.
The WHEN clause limits triggering only to those records who satisfy its condition. On the left side of the < sign of the condition there is a query which selects the age of the customer. On the right side there is a query which selects the age rating of the book. Notice the reference to a new table. This table stores the record which triggers the event (see the two examples below). strftime is a scalar function which formats datetime stamps in SQLite. You can read:
strftime('%Y', 'now') as YEAR(NOW()) and
strftime('%Y', date_of_birth) as YEAR(date_of_birth).
Finally between BEGIN and END there are instructions that will be executed on triggering. In this case there is a single instruction which removes the record just inserted. MariaDb may provide a ROLLBACK statement, which can be more efficient than the DELETE statement.
So, for example:
INSERT INTO audiobook_purchases VALUES (1234, 10, '2018-11-25');
will activate the trigger, because the customer with id=10 ('jennifer') is 8 years old and the book with isbn=1234 requires the customer to be at least 16 years old, while:
INSERT INTO audiobook_purchases VALUES (1234, 20, '2018-11-25');
will not activate the trigger, because this customer is 40 years old.
You must be aware that this solution silently ignore the invalid order. I don't know if this is your desired behaviour.
I tested this trigger on SQLite 3.11.0, so it may not be compatible with your SQL interpreter.

Adding Row in existing table (SQL Server 2005)

I want to add another row in my existing table and I'm a bit hesitant if I'm doing the right thing because it might skew the database. I have my script below and would like to hear your thoughts about it.
I want to add another row for 'Jane' in the table, which will be 'SKATING" in the ACT column.
Table: [Emp_table].[ACT].[LIST_EMP]
My script is:
INSERT INTO [Emp_table].[ACT].[LIST_EMP]
([ENTITY],[TYPE],[EMP_COD],[DATE],[LINE_NO],[ACT],[NAME])
VALUES
('REG','EMP','45233','2016-06-20 00:00:00:00','2','SKATING','JANE')
Will this do the trick?
Your statement looks ok. If the database has a problem with it (for example, due to a foreign key constraint violation), it will reject the statement.
If any of the fields in your table are numeric (and not varchar or char), just remove the quotes around the corresponding field. For example, if emp_cod and line_no are int, insert the following values instead:
('REG','EMP',45233,'2016-06-20 00:00:00:00',2,'SKATING','JANE')
Inserting records into a database has always been the most common reason why I've lost a lot of my hairs on my head!
SQL is great when it comes to SELECT or even UPDATEs but when it comes to INSERTs it's like someone from another planet came into the SQL standards commitee and managed to get their way of doing it implemented into the final SQL standard!
If your table does not have an automatic primary key that automatically gets generated on every insert, then you have to code it yourself to manage avoiding duplicates.
Start by writing a normal SELECT to see if the record(s) you're going to add don't already exist. But as Robert implied, your table may not have a primary key because it looks like a LOG table to me. So insert away!
If it does require to have a unique record everytime, then I strongly suggest you create a primary key for the table, either an auto generated one or a combination of your existing columns.
Assuming the first five combined columns make a unique key, this select will determine if your data you're inserting does not already exist...
SELECT COUNT(*) AS FoundRec FROM [Emp_table].[ACT].[LIST_EMP]
WHERE [ENTITY] = wsEntity AND [TYPE] = wsType AND [EMP_COD] = wsEmpCod AND [DATE] = wsDate AND [LINE_NO] = wsLineno
The wsXXX declarations, you will have to replace them with direct values or have them DECLAREd earlier in your script.
If you ran this alone and recieved a value of 1 or more, then the data exists already in your table, at least those 5 first columns. A true duplicate test will require you to test EVERY column in your table, but it should give you an idea.
In the INSERT, to do it all as one statement, you can do this ...
INSERT INTO [Emp_table].[ACT].[LIST_EMP]
([ENTITY],[TYPE],[EMP_COD],[DATE],[LINE_NO],[ACT],[NAME])
VALUES
('REG','EMP','45233','2016-06-20 00:00:00:00','2','SKATING','JANE')
WHERE (SELECT COUNT(*) AS FoundRec FROM [Emp_table].[ACT].[LIST_EMP]
WHERE [ENTITY] = wsEntity AND [TYPE] = wsType AND
[EMP_COD] = wsEmpCod AND [DATE] = wsDate AND
[LINE_NO] = wsLineno) = 0
Just replace the wsXXX variables with the values you want to insert.
I hope that made sense.

Custom SERIAL / autoincrement per group of values

I'm trying to make a blog system of sort and I ran into a slight problem.
Simply put, there's 3 columns in my article table:
id SERIAL,
category VARCHAR FK,
category_id INT
id column is obviously the PK and it is used as a global identifier for all articles.
category column is well .. category.
category_id is used as a UNIQUE ID within a category so currently there is a UNIQUE(category, category_id) constraint in place.
However, I also want for category_id to auto-increment.
I want it so that every time I execute a query like
INSERT INTO article(category) VALUES ('stackoverflow');
I want the category_id column to be automatically be filled according to the latest category_id of the 'stackoverflow' category.
Achieving this in my logic code is quite easy. I just select latest num and insert +1 of that but that involves two separate queries.
I am looking for a SQL solution that can do all this in one query.
This has been asked many times and the general idea is bound to fail in a multi-user environment - and a blog system sounds like exactly such a case.
So the best answer is: Don't. Consider a different approach.
Drop the column category_id completely from your table - it does not store any information the other two columns (id, category) wouldn't store already.
Your id is a serial column and already auto-increments in a reliable fashion.
Auto increment SQL function
If you need some kind of category_id without gaps per category, generate it on the fly with row_number():
Serial numbers per group of rows for compound key
Concept
There are at least several ways to approach this. First one that comes to my mind:
Assign a value for category_id column inside a trigger executed for each row, by overwriting the input value from INSERT statement.
Action
Here's the SQL Fiddle to see the code in action
For a simple test, I'm creating article table holding categories and their id's that should be unique for each category. I have omitted constraint creation - that's not relevant to present the point.
create table article ( id serial, category varchar, category_id int )
Inserting some values for two distinct categories using generate_series() function to have an auto-increment already in place.
insert into article(category, category_id)
select 'stackoverflow', i from generate_series(1,1) i
union all
select 'stackexchange', i from generate_series(1,3) i
Creating a trigger function, that would select MAX(category_id) and increment its value by 1 for a category we're inserting a row with and then overwrite the value right before moving on with the actual INSERT to table (BEFORE INSERT trigger takes care of that).
CREATE OR REPLACE FUNCTION category_increment()
RETURNS trigger
LANGUAGE plpgsql
AS
$$
DECLARE
v_category_inc int := 0;
BEGIN
SELECT MAX(category_id) + 1 INTO v_category_inc FROM article WHERE category = NEW.category;
IF v_category_inc is null THEN
NEW.category_id := 1;
ELSE
NEW.category_id := v_category_inc;
END IF;
RETURN NEW;
END;
$$
Using the function as a trigger.
CREATE TRIGGER trg_category_increment
BEFORE INSERT ON article
FOR EACH ROW EXECUTE PROCEDURE category_increment()
Inserting some more values (post trigger appliance) for already existing categories and non-existing ones.
INSERT INTO article(category) VALUES
('stackoverflow'),
('stackexchange'),
('nonexisting');
Query used to select data:
select category, category_id From article order by 1,2
Result for initial inserts:
category category_id
stackexchange 1
stackexchange 2
stackexchange 3
stackoverflow 1
Result after final inserts:
category category_id
nonexisting 1
stackexchange 1
stackexchange 2
stackexchange 3
stackexchange 4
stackoverflow 1
stackoverflow 2
Postgresql uses sequences to achieve this; it's a different approach from what you are used to in MySQL. Take a look at http://www.postgresql.org/docs/current/static/sql-createsequence.html for complete reference.
Basically you create a sequence (a database object) by:
CREATE SEQUENCE serials;
And then when you want to add to your table you will have:
INSERT INTO mytable (name, id) VALUES ('The Name', NEXTVAL('serials')

Which SQL would work?

I am trying to work out the best DB structure and SQL for my application.
The scenario is this:
Jim and John need to update their time sheets each week for different
projects. When they do this they use an html Form to insert their
hours based a date of the last day of the week.
This is fine and will insert a new row with NAME, Project, Date and Hours
However if jim and john do more hours than they expected and need to update their time sheet, I want them to be able to use the form and it will UPDATEthe hours coloumn on the DB if the name, project name and date are the matching the what has been submited.
SQL TO INSERT:
INSERT OR REPLACE INTO RESOURCE(NAME, DATE, PROJECT, HOURS) VALUES ('"+ name+ "', '"+ date+ "', '"+ project+ "','" + hours + "');
Work so far:
I have create the below DB structure on an SQLITE DB.
CREATE TABLE RESOURCE (
name VARCHAR(16),
date DATE,
project VARCHAR(16),
hours VARCHAR(16)
)
And I have create the following index to attempt to solve my issue.
CREATE UNIQUE INDEX myindex ON RESOURCE(NAME, DATE, PROJECT)
However, the problem is when jim updates his hours for the same date, project and hours as john, Johns row is replaced and only jims is left.
Hopefully I have explained this issue clearly, if not please let me know and I will supply whatever else is needed.
My question is: What can I change with either my SQL or my DB structure that would allow this aforementioned scenario to work?
What i got is that when jim/jhon enters his timing again an update query executes which update the whole records instead of updating jim/jhons own record. the main reason is that you don't have any unique key/primary key on your table that's why your query updates whole lot.
what you need to do is that have a key column and on the basis of that key you will update their records you will be using something like this in where clause
WHERE RESOURCE_ID = #RID //ID OF THAT PARTICULAR RESOURCE