How can I filter RETURNING *? - postgresql

I have the following scenario. I have a table that has an IsDeleted flag I set for doing a 'soft delete' of records. I am doing an UPSERT where I am adding, modifying and flagging as deleted some records. I want to exclude records that have been flagged as deleted from the RETURNING statement. I have attempted to just append WHERE tbltest_IsDeleted = 0 to the end of the following SQL but it gives me the error: ERROR: syntax error at or near "WHERE"
How can I filter the results of the RETURNING * in the following statement?
INSERT INTO tbltest (
tbltest_ID,
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted)
VALUES
(DEFAULT, 'new record','new record description', 0),
(4, 'modified record name','modified record description', 0),
(5, 'existing record name','existing record description', 1)
ON CONFLICT (tbltest_ID) DO UPDATE SET (
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted) = (
excluded.tbltest_Name,
excluded.tbltest_Description,
excluded.tbltest_IsDeleted) RETURNING *;

Worked it out, here is how I was able to do it:
WITH rows AS (
INSERT INTO tbltest (
tbltest_ID,
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted)
VALUES
(DEFAULT, 'new record','new record description', 0),
(4, 'modified record name','modified record description', 0),
(5, 'existing record name','existing record description', 1)
ON CONFLICT (tbltest_ID) DO UPDATE SET (
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted) = (
excluded.tbltest_Name,
excluded.tbltest_Description,
excluded.tbltest_IsDeleted) RETURNING *
)
SELECT * FROM rows WHERE rows.tbltest_IsDeleted = 0
Hopefully this saves someone some time ;-)

Related

How to update or insert into same table in DB2

I am trying to update if exists or insert into if not exists in same table in DB2 (v 9.7).
I have one table "V_OPORNAC" (scheme is SQLDBA) which contains three columns with two primary keys: IDESTE (PK), IDEPOZ (PK), OPONAR
My case is, if data (OPONAR) where IDESTE = 123456 AND IDEPOZ = 0 not exits then insert new row, if exits then update (OPONAR). I have tried this:
MERGE INTO SQLDBA.V_OPONAROC AS O1
USING (SELECT IDESTE, IDEPOZ, OPONAR FROM SQLDBA.V_OPONAROC WHERE IDESTE = 123456 AND IDEPOZ = 0) AS O2
ON (O1.IDESTE = O2.IDESTE)
WHEN MATCHED THEN
UPDATE SET
OPONAR = 'test text'
WHEN NOT MATCHED THEN
INSERT
(IDESTE, IDEPOZ, OPONAR)
VALUES (123456, 0, 'test new text')
Executing code above I am getting this error:
Query 1 of 1, Rows read: 0, Elapsed time (seconds) - Total: 0,013, SQL query: 0,013, Reading results: 0
Query 1 of 1, Rows read: 3, Elapsed time (seconds) - Total: 0,002, SQL query: 0,001, Reading results: 0,001
Warning: DB2 SQL Warning: SQLCODE=100, SQLSTATE=02000, SQLERRMC=null, DRIVER=4.21.29
SQLState: 02000
ErrorCode: 100
I figured out, by using "SYSIBM.SYSDUMMY1"
MERGE INTO SQLDBA.V_OPONAROC AS O1
USING (SELECT 1 AS IDESTE, 2 AS IDEPOZ, 3 AS OPONAR FROM SYSIBM.SYSDUMMY1) AS O2
ON (O1.IDESTE = 123456 AND O1.IDEPOZ = 0)
WHEN MATCHED THEN
UPDATE SET
O1.OPONAR = 'test text'
WHEN NOT MATCHED THEN
INSERT
(O1.IDESTE, O1.IDEPOZ, O1.OPONAR)
VALUES (123456, 0, 'test new text')

How to refer OLD.X at ON CONFLICT DO UPDATE SET X = OLD.X + EXCLUDED.X

There are an alias for "old value" in the ON CONFLICT DO UPDATE?
My real life problem is
INSERT INTO art.validterm (namespace,term,X,info)
SELECT namespace,term,array_agg(Xi), 'etc'
FROM term_raw_Xs
GROUP BY namespace,term
ON CONFLICT (term) DO
UPDATE SET aliases=OLD.X||EXCLUDED.X
WHERE term=EXCLUDED.term
PS: no "OLD" exists, is the question. The parser say that only X is ambigous.
Simply replacing OLD with the name of the table, in your case: validterm, worked for me.
My test:
DROP TABLE IF EXISTS work.term_raw;
CREATE TABLE work.term_raw
(
unique_field INT UNIQUE,
x_field TEXT
);
INSERT INTO work.term_raw VALUES (1, 'A');
INSERT INTO work.term_raw VALUES (1, 'B')
ON CONFLICT (unique_field) DO UPDATE SET x_field = term_raw.x_field || EXCLUDED.x_field;
SELECT * FROM work.term_raw;
My result:

Invalid length parameter passed to the LEFT or SUBSTRING function

I have the following description: 'Sample Product Maker Product Name XYZ - Size' and I would like to only get the value 'Product Name XYZ' from this. If this were just one row I'd have no issue just using SUBSTRING but I have thousands of records and although the initial value Sample Product Maker is the same for all products the Product Name could be different and I don't want anything after the hyphen.
What I have so far has generated the error in the header of this question.
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
I am getting 'Argument data type varchar is invalid for argument 3 of substring function'
As you can see, I am getting the value for the last line the sql statement but when I try and plug that into the SUBSTRING function I get various issues.
Chances are good you have rows where the '-' is missing, which is causing your error.
Try this...
SELECT i.Itemid,
SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription+'-', 22)) AS ProductDescriptionAbbrev,
FROM t_items i
You could also strip out the Sample Product Maker text and go from there:
SELECT RTRIM(LEFT(
LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker', '')),
CHARINDEX('-', LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker',
'' ))) - 1))
AS ShortDescription
Your first call to SUBSTRING specifies a length of SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)).
You might try:
declare #t_items as Table ( ItemId Int Identity, ShortDescription VarChar(100) )
insert into #t_items ( ShortDescription ) values
( 'Sample Product Maker Product Name XYZ - Size' )
declare #SkipLength as Int = Len( 'Sample Product Maker' )
select ItemId,
RTrim( LTrim( Substring( ShortDescription, #SkipLength + 1, CharIndex( '-', ShortDescription, #SkipLength ) - #SkipLength - 1 ) ) ) as ProductDescriptionAbbrev
from #t_items
The problem is that your outer call to SUBSTRING is being passed a character data type from the inner SUBSTRING call in the third parameter.
+--This call does not return an integer type
SELECT i.Itemid, V
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
The third parameter must evaluate to the length that you want. Perhaps you meant LEN(SUBSTRING(...))?
Seems like you want something like this (22, not 25):
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription)-22))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription)-22 as charindexpos
FROM t_items i
You want:
LEFT(i.ShortDescription, isnull(nullif(CHARINDEX('-', i.ShortDescription),0) - 1, 8000))
Note that a good practice is to wrap charindex(...)'s and patindex(...)'s with nullif(...,0), and then handle the null case if desired (sometimes null is the right result, in this case we want all the text so we isnull(...,8000) for the length we want).

SQL: PIVOTting Count & Percentage against a column

I'm trying to produce a report that shows, for each Part No, the results of tests on those parts in terms of the numbers passed and failed, and the percentages passed and failed.
So far, I have the following:
SELECT r2.PartNo, [Pass] AS Passed, [Fail] as Failed
FROM
(SELECT ResultID, PartNo, Result FROM Results) r1
PIVOT (Count(ResultID) FOR Result IN ([Pass], [Fail])) AS r2
ORDER By r2.PartNo
This is half of the solution (the totals for passes and fails); the question is, how do I push on and include percentages?
I haven't tried yet, but I imagine that I can start again from scratch, and build up a series of subqueries, but this is more a learning exercise - I want to know the 'best' (most elegant or most efficient) solution, so I thought I'd seek advice.
Can I extend this PIVOT query, or should I take a different approach?
DDL:
CREATE TABLE RESULTS (
[ResultID] [int] NOT NULL,
[SerialNo] [int] NOT NULL,
[PartNo] [varchar](10) NOT NULL,
[Result] [varchar](10) NOT NULL);
DML:
INSERT INTO Results VALUES (1, '100', 'ABC', 'Pass')
INSERT INTO Results VALUES (2, '101', 'DEF', 'Pass')
INSERT INTO Results VALUES (3, '100', 'ABC', 'Fail')
INSERT INTO Results VALUES (4, '102', 'DEF', 'Pass')
INSERT INTO Results VALUES (5, '102', 'DEF', 'Pass')
INSERT INTO Results VALUES (6, '102', 'DEF', 'Fail')
INSERT INTO Results VALUES (7, '101', 'DEF', 'Fail')
UPDATE:
My solution, based on bluefeet's answer is:
SELECT r2.PartNo,
[Pass] AS Passed,
[Fail] as Failed,
ROUND(([Fail] / CAST(([Pass] + [Fail]) AS REAL)) * 100, 2) AS PercentFailed
FROM
(SELECT ResultID, PartNo, Result FROM Results) r1
PIVOT (Count(ResultID) FOR Result IN ([Pass], [Fail])) AS r2
ORDER By r2.PartNo
I've ROUNDed a FLOAT(rather than CAST to DECIMAL twice) because its a tiny bit more efficient, and I've also decided that we only real need the failure %age.
It sounds like you just need to add a column for Percent Passed and Percent Failed. You can calculate those columns on your PIVOT.
SELECT r2.PartNo
, [Pass] AS Passed
, [Fail] as Failed
, ([Pass] / Cast(([Pass] + [Fail]) as decimal(5, 2))) * 100 as PercentPassed
, ([Fail] / Cast(([Pass] + [Fail]) as decimal(5, 2))) * 100 as PercentFailed
FROM
(
SELECT ResultID, PartNo, Result
FROM Results
) r1
PIVOT
(
Count(ResultID)
FOR Result IN ([Pass], [Fail])
) AS r2
ORDER By r2.PartNo

zend framework get last insert id of multi row insert using execute

How would I get the last inserted ID using a multirow insert?
Here is my code:
$sql='INSERT INTO t (col1, col2, col3) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; // example
$stmt = $contactsTable->getAdapter()->prepare($sql);
$stmt->execute();
$rowsAdded=$stmt->rowCount(); // mysql_affected_rows
$lastId=$stmt->lastInsertId();
echo '<br>Last ID: '.$lastId;
Also, is there a method in ZF to get the next insert id before an insert?
thanks
$lastId=$contactsTable->getAdapter()->lastInsertId();
This worked.
So, here is the full working code I'm using to create a multi-row insert, getting the rows added and the last inserted id:
$contactsTable = new Contacts_Model_Contacts();
$sql='INSERT INTO t (col1, col2, col3) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; // example
$stmt = $contactsTable->getAdapter()->prepare($sql);
$stmt->execute();
$rowsAdded=$stmt->rowCount(); // mysql_affected_rows
$lastId=$contactsTable->getAdapter()->lastInsertId(); // last inserted id
echo '<br>Last ID: '.$lastId;
An alternate solution. Move off sql code from controllers and place them in models. That is what they are for.
If you are using models, you can given the name of table which has auto increment column, in class variable.
protected $_name="<table_name>";
Then in your model class method, you can get last insert id from
$insertID= $this->getAdapter()->lastInsertId();
that code should work, but it will only get you the id of your last insert.
you can get the next autoincrement with this mysql query:
SELECT Auto_increment FROM information_schema.tables WHERE TABLE_SCHEMA = 'your_db_name' AND TABLE_NAME='the_table_you_want';