SQL: updating a column in one table based on a count result from another table - sql

I've got a table called GUEST and a table called HOTEL_BOOKING.
I want to update a column in the GUEST table called guest_nobookings (this is the number of bookings a guest has made at the hotel).
I can get the number of bookings a guest has made from the HOTEL_BOOKING table by performing a count on the hotel booking number based on the guest number.
This is what I have so far:
SELECT COUNT(hotel_bookingno)
FROM hotel_booking
GROUP BY guest_no;
That gives me the number of bookings for each guest who has stayed at the hotel. To update the GUEST table I've tried this:
UPDATE guest
SET (guest_nobookings = (SELECT COUNT(hotel_bookingno)
FROM hotel_booking
GROUP BY guest_no));
I get a 'single-row subquery returns more than one row' error when I try this however.
Is there a more direct way of solving this problem?
Thanks in advance!

You could use a correlated subquery to update here:
UPDATE guest g
SET guest_nobookings = (SELECT COUNT(*) FROM hotel_booking hb
WHERE hb.guest_no = g.hotel_bookingno);
You may need to alter the above slightly to make it work. As a side note, you might want to rethink your design and avoid even doing this update. The reason is that each time the state in the hotel_booking table changes, the count aggregates might become invalid, and would have to recomputed. In general, we try to avoid storing aggregates of original data in SQL.

Why you want to have sane data in two tables? It will lead to inconsitent data in one of them. (As already described by Tim)
My suggestion is to use the view for all details of the guest and in the view use count from hotel_bookings table.
If you really want to update the count then You can use merge statement as following:
Merge into guest g
Using (select COUNT(*) as cnt, hb.guest_no
FROM hotel_booking hb
GROUP BY hb.guest_no) hb
On (hb.guest_no = g.hotel_bookingno)
When matched then
Update set g.guest_nobookings = hb.cnt;
Cheers!!

UPDATE guest
SET guest_nobookings = (
WITH t1 AS (
SELECT COUNT(*) no_bk, guest_no FROM hotel_booking
GROUP BY hotel_booking.guest_no
)
SELECT no_bk FROM t1
WHERE guest_no = guest.hotel_bookingno
);
This is another way to update the table using WITH clause.

Related

Query build to find records where all of a series of records have a value

Let me explain a little bit about what I am trying to do because I dont even know the vocab to use to ask. I have an Access 2016 database that records staff QA data. When a staff member misses a QA we assign a job aid that explains the process and they can optionally send back a worksheet showing they learned about what was missed. If they do all of these ina 3 month period they get a credit on their QA score. So I have a series of records all of whom have a date we assigned the work(RA1) and MAY have a work returned date(RC1).
In the below image "lavalleer" has earned the credit because both of her sheets got returned. "maduncn" Did not earn the credit because he didn't do one.
I want to create a query that returns to me only the people that are like "lavalleer". I tried hitting google and searched here and access.programmers.co.uk but I'm only coming up with instructions to use Not null statements. That wouldn't work for me because if I did a IS Not Null on "maduncn" I would get the 4 records but it would exclude the null.
What I need to do is build a query where I can see staff that have dates in ALL of their RC1 fields. If any of their RC1 fields are blank I dont want them to return.
Consider:
SELECT * FROM tablename WHERE NOT UserLogin IN (SELECT UserLogin FROM tablename WHERE RCI IS NULL);
You could use a not exists clause with a correlated subquery, e.g.
select t.* from YourTable t where not exists
(select 1 from YourTable u where t.userlogin = u.userlogin and u.rc1 is null)
Here, select 1 is used purely for optimisation - we don't care what the query returns, just that it has records (or doesn't have records).
Or, you could use a left join to exclude those users for which there is a null rc1 record, e.g.:
select t.* from YourTable t left join
(select u.userlogin from YourTable u where u.rc1 is null) v on t.userlogin = v.userlogin
where v.userlogin is null
In all of the above, change all occurrences of YourTable to the name of your table.

Add / update column from a query SELECT? SQL

I'm quite a novice on this and I don't know if I will explain myself well. I am trying to do an exercise in SQL in which asks me to update the data in an "X" table from other data in a "Y" table. The problem is that it is not about updating table X exactly like the data in table Y. I put the statement and my tables:
Update the "numJocs" field (number of games) for all platforms, depending on the number of games each of the platforms in the GAMES table has.
PLATFORM table:
where: "nom" is name.
GAMES table:
where: "nom" is name, "preu" is price, "idPlataforma" is idPlatform and "codiTenda" is storeCode, but only idPlataforma interested for this exercise.
If I do:
SELECT COUNT(games.idPlataforma)
FROM games
GROUP BY (games.idPlataforma)
I can see how many games there are for each platform. The result would be:
count(games.idPlataforma)
__________________________
2
1
2
2
I would like to be able to put this result in the PLATFORM table, column "numJocs". But I don't know how to do it ... I also don't want to put it manually, that is, a "2" in a row "1", etc ... but I would like to be able to make a query and add that query in the column that I have to fill in. He tried to do a thousand things, but nothing ... Any help?
Thanks!!
for one time update you can use below query
update Product P
INNER JOIN (
SELECT games.idPlataforma, COUNT(games.idPlataforma) as cnt
FROM games
GROUP BY games.idPlataforma
) x ON P.id= x.idPlataforma
SET P.numJocs= x.cnt
For the next time on every entry of new game you have a update numJocs
Suppose you have 2 tables, table 1 and table 2:
Table 1:
Table 2:
You could insert new values into table 1, based on table 2 by doing the following:
insert into Table1(number,CFG) select ITEM,results from Table2
Which has the following result in table 1:
Any database should support the syntax using a correlated subquery:
update platforms
set numjocs = (select count(*)
from games g
where g.idPlatforms = platforms.id
);
I would caution you though about storing this value in the table. It will be immediately out of data if the platforms table changes. If you want to keep it in synch, then you need to create triggers -- and this is all rather complicated.
Rather, calculate the data on the fly:
select p.*,
(select count(*)
from games g
where g.idPlatforms = platforms.id
) as numjocs
from platforms p ;
You can put this in a view if you like. Many databases support materialized views where the results of the view are stored in a "table" and the table is kept up-to-date with the underlying data.

How I use where clause with computed column to get related records only

I read this FAQ http://www.firebirdfaq.org/faq289/ and I want to use select to count detail record from current record so I tried this
((
select count(*) from detail_table where detail_table.id = id
))
But I did not get correct values I got numbers like 19 where in fact it is the total number of records in detail_table! How can I get only the count of records related to current master record in master table?
The problem is that id refers to the id column of detail_table and not of the master table. So it is the equivalent of:
select count(*) from detail_table where detail_table.id = detail_table.id
Which means you are simply counting all rows. Instead - assuming the master table is master_table - you should use:
select count(*) from detail_table where detail_table.id = master_table.id
Note that, as also mentioned in the FAQ you link to, you should really consider using a view instead of a computed column when referencing other tables as it is not very good for performance.
The view equivalent would be something like
CREATE OR ALTER VIEW master_with_detail_count
AS
SELECT master_table.id, coalesce(c.detail_count, 0) as detail_count
FROM master_table
LEFT JOIN (SELECT id, count(*) as detail_count FROM detail GROUP BY id) c
ON c.id = master.id

Update values in each row based on foreign_key value

Downloads table:
id (primary key)
user_id
item_id
created_at
updated_at
The user_id and item_id in this case are both incorrect, however, they're properly stored in the users and items table, respectively (import_id for in each table). Here's what I'm trying to script:
downloads.each do |download|
user = User.find_by_import_id(download.user_id)
item = item.find_by_import_id(download.item_id)
if user && item
download.update_attributes(:user_id => user.id, :item.id => item.id)
end
end
So,
look up the user and item based on
their respective "import_id"'s. Then
update those values in the download record
This takes forever with a ton of rows. Anyway to do this in SQL?
If I understand you correctly, you simply need to add two sub-querys in your SELECT statement to lookup the correct IDs. For example:
SELECT id,
(SELECT correct_id FROM User WHERE import_id=user_id) AS UserID,
(SELECT correct_id FROM Item WHERE import_id=item_id) AS ItemID,
created_at,
updated_at
FROM Downloads
This will translate your incorrect user_ids to whatever ID you want to come from the User table and it will do the same for your item_ids. The information coming from SQL will now be correct.
If, however, you want to update the tables with the correct information, you could write this like so:
UPDATE Downloads
SET user_id = User.user_id,
item_id = Item.item_id
FROM Downloads
INNER JOIN User ON Downloads.user_id = User.import_id
INNER JOIN Item ON Downloads.item_id = Item.import_id
WHERE ...
Make sure to put something in the WHERE clause so you don't update every record in the Downloads table (unless that is the plan). I rewrote the above statement to be a bit more optimized since the original version had two SELECT statements per row, which is a bit intense.
Edit:
Since this is PostgreSQL, you can't have the table name in both the UPDATE and the FROM section. Instead, the tables in the FROM section are joined to the table being updated. Here is a quote about this from the PostgreSQL website:
When a FROM clause is present, what essentially happens is that the target table is joined to the tables mentioned in the fromlist, and each output row of the join represents an update operation for the target table. When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.
http://www.postgresql.org/docs/8.1/static/sql-update.html
With this in mind, here is an example that I think should work (can't test it, sorry):
UPDATE Downloads
SET user_id = User.user_id,
item_id = Item.item_id
FROM User, Item
WHERE Downloads.user_id = User.import_id AND
Downloads.item_id = Item.import_id
That is the basic idea. Don't forget you will still need to add extra criteria to the WHERE section to limit the rows that are updated.
i'm totally guessing from your question, but you have some kind of lookup table that will match an import user_id with the real user_id, and similarly from items. i.e. the assumption is your line of code:
User.find_by_import_id(download.user_id)
hits the database to do the lookup. the import_users / import_items tables are just the names i've given to the lookup tables to do this.
UPDATE downloads
SET downloads.user_id = users.user_id
, downloads.item_id = items.items_id
FROM downloads
INNER JOIN import_users ON downloads.user_id = import_users.import_user_id
INNER JOIN import_items ON downloads.item_id = import_items.import_item_id
Either way (lookup is in DB, or it's derived from code), would it not just be easier to insert the information correctly in the first place? this would mean you can't have any FK's on your table since sometimes they point to one table, and others they point to another. seems a bit odd.

How can I compare two tables and delete on matching fields (not matching records)

Scenario: A sampling survey needs to be performed on membership of 20,000 individuals. Survey sample size is 3500 of the total 20000 members. All membership individuals are in table tblMember. Same survey was performed the previous year and members whom were surveyed are in tblSurvey08. Membership data can change over the year (e.g. new email address, etc.) but the MemberID data stays the same.
How do I remove the MemberID/records contained tblSurvey08 from tblMember to create a new table of potential members to be surveyed (lets call it tblPotentialSurvey09). Again the record for a individual member may not match from the different tables but the MemberID field will remain constant.
I am fairly new at this stuff but I seem to be having a problem Googling a solution - I could use the EXCEPT function but the records for the individuals members are not necessarily the same from one table to next - just the MemberID may be the same.
Thanks
SELECT
* (replace with column list)
FROM
member m
LEFT JOIN
tblSurvey08 s08
ON m.member_id = s08.member_id
WHERE
s08.member_id IS NULL
will give you only members not in the 08 survey. This join is more efficient than a NOT IN construct.
A new table is not such a great idea, since you are duplicating data. A view with the above query would be a better choice.
I apologize in advance if I didn't understand your question but I think this is what you're asking for. You can use the insert into statement.
insert into tblPotentialSurvey09
select your_criteria from tblMember where tblMember.MemberId not in (
select MemberId from tblSurvey08
)
First of all, I wouldn't create a new table just for selecting potential members. Instead, I would create a new true/false (1/0) field telling if they are eligible.
However, if you'd still want to copy data to the new table, here's how you can do it:
INSERT INTO tblSurvey00 (MemberID)
SELECT MemberID
FROM tblMember m
WHERE NOT EXISTS (SELECT 1 FROM tblSurvey09 s WHERE s.MemberID = m.MemberID)
If you just want to create a new field as I suggested, a similar query would do the job.
An outer join should do:
select m_09.MemberID
from tblMembers m_09 left outer join
tblSurvey08 m_08 on m_09.MemberID = m_08.MemberID
where
m_08.MemberID is null