IBM DB2 update using JOIN - sql

I need to update a table containing orders depending on customer information. This is how i would have approached it but apparently DB2 doesnt support JOINs in UPDATEs. I am working on an IBM iSeries.
UPDATE lib.orders as o
JOIN lib.customers as c
ON o.cstmrid = c.id
SET o.updatehere = 'NEWVALUE'
WHERE c.info = 'VALUE'
There are allready questions on that topic but none really help me.
Thanks!

Okay so this is really late but in case someones reading this: none of the comments/answers were correct. The important point is that i am working on an iseries which uses db2 udb and does neither support joins on updates nor merge (at least the version we work with).
The only way i figured out will work is a WHERE EXISTS clause.

I would recommend something like:
UPDATE lib.orders
SET updatehere = 'NEWVALUE'
WHERE cstrmid in (
SELECT id
FROM lib.customers
WHERE info = 'VALUE'
)
The reality is that an update statement has four main pieces:
what table is being updated?
possibly with an alias specified
your example used one
my suggestion didn't need one
what field(s) is/are being updated within item 1?
can be a single field, as in your example
can be a tuple, containing multiple fields
what value(s) is/are being populated into item 2?
can be a single value or a tuple of values, depending on item 2
can be the result of a subquery
must return a single value or a tuple, as needed, to match item 2
can join against the table specified in item 1 to ensure different values are output for different rows in the item 1's table
what matching criteria are there for records in item 1?
can be one or more criteria
if you don't specify this, DB/2 will iterate through ALL records in the table
can include a subquery
if you have a subquery in item 3, and a row matching this doesn't match up with any results from that subquery, item 2's fields will be assigned null values
I specified item 1 in line 1 of my suggestion.
I specified items 2 and 3 on line 2 of my suggestion.
I specified item 4 using a subquery in lines 3 - 7
I have, on occasion, written update statements where:
item 2 was a tuple of multiple fields
item 3 was a subquery which returned multiple values, joining against the table specified in item 1
item 4 used a subquery similar to what was used in item 3 but returning join values instead of update values for fields
The result was that DB/2 queried against item 1, using item 4 as criteria and then iterated through the resulting rows, updating fields in item 2 against values returned from item 3. As item 3 was a subquery, the join against the table alias specified in item 1 provided necessary criteria for what was returned from the subquery.
I've written update statements where the subqueries in question were Table Value Constructors. In that fashion, I was able to update a hundreds of records based on a small set of known values.
I have done all of this on DB/2 on an iSeries. I never used a MERGE because, as you've noticed, not all versions of DB/2 support that.

Related

How to track changes not only in the source table but also tables that are joined to it?

Assume I have this SQL statement:
INSERT INTO MYTABLE
select A.code,B.name,C.add
from codes A
left join names B on A.fid=B.id
left join addresses C on B.num=C.id
where A.datechanged>somedate;
This adds new records for every row in A that gets modified. I want it to also track changes in B and C, so for example if B.datechanged>somedate is true but A.datechanged>somedate is false, a new row still gets added.
In other words, I want it to pull data from all three tables if datechanged>somedate any of the tables, not just A. How can i do this? I contemplated repeating the same statement with different tables in the FROM clause but I have about 10 tables that are being joined. Is there a better way to do this?
It sounds like you just want multiple predicates combined with an OR clause
WHERE a.datechanged > somedate
OR b.datechanged > somedate
OR c.datechanged > somedate
A SQL statement specifies particular set to be returned. Predicates are combined using standard boolean algebra to determine which rows are returned. In this case, if any of the predicates are satisfied, the row will be returned. Obviously, if you used AND to combine the predicates, then you would only get rows that satisfy all three conditions.

Why am I loosing table data from adding additional join statement in SQL Server?

So I have table X with let's say 50 rows of data in it and it is being joined to another table with only 2 rows in it. When I "normal" JOIN them together only the overlapping data will show, i.e. the two rows of data that are found in the larger collection of 50. In my case I want all 50 rows to persist so I use a LEFT JOIN and I am returned all 50 rows like planned.
Now I want to start adding and joining in other tables to get additional data about these rows and have them display togehter, whenever there is no data, I am fine with getting null. Now I'm adding a new JOIN and it's only going to find data for those two rows and not for the other 48, which is perfectly fine and I would like NULL to be displayed where no match is found. My problem is that rather than doing that, displaying null, it is instead removing the 48 rows will partial null data in their columns entirely and only showing the two that match, WHY?
I can provide code if needed, I thought this may be easier to understand.
If you inner join the third table, there are only 2 rows that can match (I assume you don't match on nulls). Only 2 rows from the second table have non-null values in them, so only those 2 can produce a result if you do an inner join with the third table.
You will need another left join in this case. This will return all the rows from the second table, including those that are all null (as the result of the first left join).

Returning an Access recordset with zeros instead of nulls

Here's the problem:
I have an Access query that feeds a report, which sometimes doesn't return any records for certain criteria. I would like to display zeros in the report instead of an empty line (an empty recordset is currently being returned).
Is there an SQL solution that (perhaps using some kind of union statement and/or nested SQL) always returns one record (with zeros) if there are not matching records from the initial query?
One possible solution would be to create a second table with the same primary key, and add just one record. In your query, choose as join type all records in the second table, including those with no matching records in the first one. Select as output all fields in the first table.
You can materialize a one-row table with zero for all columns. This is a slight pain to achieve in Access (ACE, Jet, whatever) because it doesn't support row constructors and the FROM must resolve to a base table. In other words, you'll need a table that is guaranteed to always contain at least one row.
This isn't a problem for me because my databases always include auxilliary tables e.g. a calendar table, a sequence table of integers, etc. For exmaple, to materialize a table one-row, all-zeros table using my 3000 row Calendar table:
SELECT DISTINCT 0 AS c
FROM Calendar;
I can then UNION my query with my materialized table but include an antijoin to ensure the all-zeros row only appears in the resultset when my query is the empty set:
SELECT c
FROM T
UNION
SELECT 0
FROM Calendar
WHERE NOT EXISTS (
SELECT c
FROM T
);
Note the use of UNION allows me to remove the DISTINCT keyword and the AS clause ("column alias") from the materialized table.

How not to display columns which are NULL in a view

I've set up a view which combines all the data across several tables. Is there a way to write this so that only columns which contain non-null data are displayed, and those columns which contain all NULL values are not included?
ADDED:
Sorry, still studying and working on my first big project so every day seems to be a new experience at the minute. I haven't been very clear, and that's partly because I'm not sure I'm going about things the right way! The client is an academic library, and the database records details of specific collections. The view I mentioned is to display all the data held about an item, so it is bringing together tables on publication, copy, author, publisher, language and so on. A small number of items in the collection are papers, so have additional details over and above the standard bibliographic details. What I didn't want was a user to get all the empty fields relating to papers if what was returned only consisted of books, therefore the paper table fields were all null. So I thought perhaps there would be a way to not show these. Someone has commented that this is the job of the client application rather than the database itself, so I can leave this until I get to that phase of the project.
There is no way to do this in sql.
CREATE VIEW dbo.YourView
AS
SELECT (list of fields)
FROM dbo.Table1 t1
INNER JOIN dbo.Table2 t2 ON t1.ID = t2.FK_ID
WHERE t1.SomeColumn IS NOT NULL
AND t2.SomeOtherColumn IS NOT NULL
In your view definition, you can include WHERE conditions which can exclude rows that have certain columns that are NULL.
Update: you cannot really filter out columns - you define the list of columns that are part of your view in your view definition, and this list is fixed and cannot be dynamically changed......
What you might be able to do is us a ISNULL(column, '') construct to replace those NULLs with an empty string. Or then you need to handle excluding those columns in your display front end - not in the SQL view definition...
The only thing I see you could do is make sure to select only those columns from the view that you know aren't NULL:
SELECT (list of non-null fields) FROM dbo.YourView
WHERE (column1 IS NOT NULL)
and so forth - but there's no simple or magic way to select all columns that aren't NULL in one SELECT statement...
You cannot do this in a view, but you can do it fairly easily using dynamic SQL in a stored procedure.
Of course, having a schema which shifts is not necessarily good for clients who consume the data, but it can be efficient if you have very sparse data AND the consuming client understands the varying schema.
If you have to have a view, you can put a "header" row in your view which you can inspect client-side on the first row in your loop to see if you want to not bother with the column in your grid or whatever, you can do something like this:
SELECT * FROM (
-- This is the view code
SELECT 'data' as typ
,int_col
,varchar_col
FROM TABLE
UNION ALL
SELECT 'hdr' as typ
-- note that different types have to be handled differently
,CASE WHEN COUNT(int_col) = 0 THEN NULL ELSE 0 END
,CASE WHEN COUNT(varchar_col) = 0 THEN NULL ELSE '' END
FROM TABLE
) AS X
-- have to get header row first
ORDER BY typ DESC -- add other sort criteria here
If we're reading your question right, there won't be a way to do this in SQL. The output of a view must be a relation - in (over-)simplified terms, it must be rectangular. That is, each row must have the same number of columns.
If you can tell us more about your data and give us some idea of what you want to do with the output, we can perhaps offer more positive suggestions.
In general, add a WHERE clause to your query, e.g.
WHERE a IS NOT NULL AND b IS NOT NULL AND c IS NOT NULL
Here, a b c are your column names.
If you are joining tables together on potentially NULL columns, then use an INNER JOIN, and NULL values will not be included.
EDIT: I may have misunderstood - the above filters out rows, but you may be asking to filter out columns, e.g. you have several columns and you only want to display columns that contain at least one null value across all the rows you are returning. Using dynamic SQL offers a solution, since the set columns varies depending upon your data.
Here's a SQL query that builds another SQL query containing the appropriate columns. You could run this query, and then submit it's result as another query. It assumes 'pk' is some column that is always non-null, e.g. a primary key - this means we can prefix additional row names with a comma.
SELECT CONCAT("SELECT pk"
CASE (count(columnA)) WHEN 0 THEN '' ELSE ',columnA' END,
CASE (count(columnB)) WHEN 0 THEN '' ELSE ',columnB' END,
// etc..
' FROM (YourQuery) base')
FROM
(YourQuery) As base
The query works using Count(column) - the aggregate function ignores NULL values, and so returns 0 for a column consisting entirely of NULLs. The query builder assumes that YourQuery uses aliases to ensure there no duplicate column names.
While you cant put this into a view, you could wrap it up as a stored procedure that copies the data to another table - the result table. You may also set up a trigger so that the result table is updated whenever the base tables change.
I suspect what's going on is that an end user is running CrystalReports and complaining about all the empty columns that have to be removed manually.
It would actually be possible to create a stored procedure that would create a view on the fly, leaving out dataless columns. But then you would have to run this proc before using the view.
Is that acceptable?

Update all rows of a single column

I'm dealing with two tables which have 2 columns, as listed under.
Table 1: table_snapshot
account_no | balance_due
Table 2: table_ paid
account_no | post_balance | delta_balance
I added a third column to table2 with the following command:
ALTER TABLE table_paid ADD delta_balance number(18);
I'm trying to use the following query, to update the new column ( delta_balance ) with the difference in balances between 1 and 2.
FYI, table_paid is a subset of table_snapshot. i,e., table 2 has only a few accounts present in table 1. I get an error saying : SQL Statement not properly ended. the query i'm using is:
UPDATE table_paid
SET table_paid.delta_balance = table_paid.post_balance - table_snapshot.balance_due
from table_paid, table_snapshot
WHERE table_paid.account_no = table_snapshot.account_no;
Appreciate if someone can correct my query.
Many thanks.
novice.
Oracle doesn't have the UPDATE ... FROM syntax that you're using from MS Sql Server (which, I believe, isn't ANSI anyway). Instead, when you need to do an update on a result set, Oracle has you create the resultset as a kind of inline view, then you update through the view, like so:
UPDATE ( SELECT tp.delta_balance
, tp.post_balance
, ts.balance_due
FROM table_paid tp
JOIN table_snapshot ts
ON tp.account_no = ts.account_no
)
SET delta_balance = post_balance - balance_due;
This is more "correct" than the answers supplied by Babar and palindrom, as their queries will update every row in table_paid, even if there are no corresponding rows in table_snapshot. If there is a 1-1 correspondance, you don't need to worry, but it's safer to do it with the inline view.
It's unclear from your example which table is the parent table, or (as I'm guessing) neither is the parent table and account_no is pointing to the primary key of another table (presumably account, or "table_account" by your naming conventions). In any case, it's clear that there is not a 1-1 correspondence in your table - 15K in one, millions in the other.
This could mean 2 things: either there are many rows in table_snapshot that have no corresponding row in table_paid, or there are many rows in table_snapshot for each row in table_paid. If the latter is true, your query is impossible - you will have multiple updates for each row in table_paid, and the result will be unpredictable; how will you know which of the "post_balance - balance_due" expressions will ultimately determine the value of a given delta_balance?
If you run my query, you will find this out quickly enough - you will get an error message that says, "ORA-01779: cannot modify a column which maps to a non key-preserved table". This error will appear based not on the data in the table (it may be okay), but based on the primary keys you have defined on the two tables. If the join condition you specify doesn't unambiguously result in a 1-1 relationship between the updated table and the rest of the join, based on the defined keys, you will get this error. It's Oracle's way of telling you, "You're about to screw up your data".
In the other answers here, you will only get an error (in that case, ORA-01427: single-row subquery returns more than one row) if you actually have data that would cause a problem; my version is more strict, so it may turn out that you will need to use the other versions.
And, as the others have said, you'll definitely want an index on account_no for the table_snapshot table. One on the table_paid wouldn't hurt either.
Try this
UPDATE table_paid
SET table_paid.delta_balance = table_paid.post_balance -
(SELECT table_snapshot.balance_due from table_snapshot WHERE table_paid.account_no =
table_snapshot.account_no);
UPDATE table_paid
SET table_paid.delta_balance = table_paid.post_balance - ( select balance_due from table_snapshot
WHERE table_paid.account_no = table_snapshot.account_no )