Using newline character and adding several rows of strings into one - sql

I have in my hands an old app on informix server and I'm migrating data into a different database. Both are informix databases. I have a particular problem with one table. Old app used it to support multiline text.
OldTable:
HeaderID int
LineNum int
Descr nvarchar(50,1)
NewTable:
HeaderID int
Descr lvarchar(max)
So, for each HeaderID I have to read the descriptions ordered by line number and put them all together for insert into a new table. There has to be a newline character between each line for conversion to succeed.
Any tips on how to do this?

If you need to do it from SQL then you can use procedure:
CREATE FUNCTION get_text(aHeaderID int)
RETURNING lvarchar;
DEFINE result lvarchar;
DEFINE vcfld lvarchar;
LET result=NULL;
EXECUTE PROCEDURE IFX_ALLOW_NEWLINE('T');
FOREACH cur1
FOR SELECT Descr INTO vcfld FROM OldTable WHERE HeaderID = aHeaderID ORDER BY LineNum
IF result IS NULL THEN
LET result = vcfld;
ELSE
LET result = result || '
' || vcfld;
END IF;
END FOREACH;
RETURN result;
END FUNCTION;
(notice usage of IFX_ALLOW_NEWLINE and line breaking when updating result)
Then you can fill NewTable using:
UPDATE NewTable SET Descr=get_text(HeaderID);

You can use PreparedStatement. This is example in Jython that uses JDBC Informix driver:
db = DriverManager.getConnection(db_url, usr, passwd)
pstm = db.prepareStatement("SELECT vc FROM src ORDER BY id")
rs = pstm.executeQuery()
lines = []
while (rs.next()):
lines.append(rs.getString(1))
pstm = db.prepareStatement("INSERT INTO dest (lvc) VALUES (?)")
pstm.setString(1, '\n'.join(lines))
rs = pstm.execute()
db.close()

Related

Is it possible to invoke BigQuery procedures in python client?

Scripting/procedures for BigQuery just came out in beta - is it possible to invoke procedures using the BigQuery python client?
I tried:
query = """CALL `myproject.dataset.procedure`()...."""
job = client.query(query, location="US",)
print(job.results())
print(job.ddl_operation_performed)
print(job._properties) but that didn't give me the result set from the procedure. Is it possible to get the results?
Thank you!
Edited - stored procedure I am calling
CREATE OR REPLACE PROCEDURE `Project.Dataset.Table`(IN country STRING, IN accessDate DATE, IN accessId, OUT saleExists INT64)
BEGIN
IF EXISTS (SELECT 1 FROM dataset.table where purchaseCountry = country and purchaseDate=accessDate and customerId = accessId)
THEN
SET saleExists = (SELECT 1);
ELSE
INSERT Dataset.MissingSalesTable (purchaseCountry, purchaseDate, customerId) VALUES (country, accessDate, accessId);
SET saleExists = (SELECT 0);
END IF;
END;
If you follow the CALL command with a SELECT statement, you can get the return value of the function as a result set. For example, I created the following stored procedure:
BEGIN
-- Build an array of the top 100 names from the year 2017.
DECLARE
top_names ARRAY<STRING>;
SET
top_names = (
SELECT
ARRAY_AGG(name
ORDER BY
number DESC
LIMIT
100)
FROM
`bigquery-public-data.usa_names.usa_1910_current`
WHERE
year = 2017 );
-- Which names appear as words in Shakespeare's plays?
SET
top_shakespeare_names = (
SELECT
ARRAY_AGG(name)
FROM
UNNEST(top_names) AS name
WHERE
name IN (
SELECT
word
FROM
`bigquery-public-data.samples.shakespeare` ));
END
Running the following query will return the procedure's return as the top-level results set.
DECLARE top_shakespeare_names ARRAY<STRING> DEFAULT NULL;
CALL `my-project.test_dataset.top_names`(top_shakespeare_names);
SELECT top_shakespeare_names;
In Python:
from google.cloud import bigquery
client = bigquery.Client()
query_string = """
DECLARE top_shakespeare_names ARRAY<STRING> DEFAULT NULL;
CALL `swast-scratch.test_dataset.top_names`(top_shakespeare_names);
SELECT top_shakespeare_names;
"""
query_job = client.query(query_string)
rows = list(query_job.result())
print(rows)
Related: If you have SELECT statements within a stored procedure, you can walk the job to fetch the results, even if the SELECT statement isn't the last statement in the procedure.
# TODO(developer): Import the client library.
# from google.cloud import bigquery
# TODO(developer): Construct a BigQuery client object.
# client = bigquery.Client()
# Run a SQL script.
sql_script = """
-- Declare a variable to hold names as an array.
DECLARE top_names ARRAY<STRING>;
-- Build an array of the top 100 names from the year 2017.
SET top_names = (
SELECT ARRAY_AGG(name ORDER BY number DESC LIMIT 100)
FROM `bigquery-public-data.usa_names.usa_1910_2013`
WHERE year = 2000
);
-- Which names appear as words in Shakespeare's plays?
SELECT
name AS shakespeare_name
FROM UNNEST(top_names) AS name
WHERE name IN (
SELECT word
FROM `bigquery-public-data.samples.shakespeare`
);
"""
parent_job = client.query(sql_script)
# Wait for the whole script to finish.
rows_iterable = parent_job.result()
print("Script created {} child jobs.".format(parent_job.num_child_jobs))
# Fetch result rows for the final sub-job in the script.
rows = list(rows_iterable)
print("{} of the top 100 names from year 2000 also appear in Shakespeare's works.".format(len(rows)))
# Fetch jobs created by the SQL script.
child_jobs_iterable = client.list_jobs(parent_job=parent_job)
for child_job in child_jobs_iterable:
child_rows = list(child_job.result())
print("Child job with ID {} produced {} rows.".format(child_job.job_id, len(child_rows)))
It works if you have SELECT inside your procedure, given the procedure being:
create or replace procedure dataset.proc_output() BEGIN
SELECT t FROM UNNEST(['1','2','3']) t;
END;
Code:
from google.cloud import bigquery
client = bigquery.Client()
query = """CALL dataset.proc_output()"""
job = client.query(query, location="US")
for result in job.result():
print result
will output:
Row((u'1',), {u't': 0})
Row((u'2',), {u't': 0})
Row((u'3',), {u't': 0})
However, if there are multiple SELECT inside a procedure, only the last result set can be fetched this way.
Update
See below example:
CREATE OR REPLACE PROCEDURE zyun.exists(IN country STRING, IN accessDate DATE, OUT saleExists INT64)
BEGIN
SET saleExists = (WITH data AS (SELECT "US" purchaseCountry, DATE "2019-1-1" purchaseDate)
SELECT Count(*) FROM data where purchaseCountry = country and purchaseDate=accessDate);
IF saleExists = 0 THEN
INSERT Dataset.MissingSalesTable (purchaseCountry, purchaseDate, customerId) VALUES (country, accessDate, accessId);
END IF;
END;
BEGIN
DECLARE saleExists INT64;
CALL zyun.exists("US", DATE "2019-2-1", saleExists);
SELECT saleExists;
END
BTW, your example is much better served with a single MERGE statement instead of a script.

How to split a column by the number of white spaces in it with SQL?

I've got a single column that contains a set of names in it. I didn't design the database so that it contains multiple values in one column, but as it is I've got to extract that information now.
The problem is that in one field I've got multiple values like in this example:
"Jack Tom Larry Stan Kenny"
So the first three should be one group, and the other ones on the far right are another group. (Basically the only thing that separates them in the column is a specific number of whitespace between them, let's say 50 characters.)
How can I split them in pure SQL, so that I can get two columns like this:
column1 "Jack Tom Larry"
column2 "Stan Kenny"
A fairly simplistic answer would be to use a combination of left(), right() and locate(). Something like this (note I've substituted 50 spaces with "XXX" for readability):
declare global temporary table session.x(a varchar(100))
on commit preserve rows with norecovery;
insert into session.x values('Jack Tom LarryXXXStan Kenny');
select left(a,locate(a,'XXX')-1),right(a,length(a)+1-(locate(a,'XXX')+length('XXX'))) from session.x;
If you need a more general method of extracting the nth field from a string with a given separator, a bit like the split_part() function in PostgreSQL, in Ingres your options would be:
Write a user defined function using the Object Management Extension (OME). This isn't entirely straightforward but there is an excellent example in the wiki pages of Actian's community site to get you started:
http://community.actian.com/wiki/OME:_User_Defined_Functions
Create a row-producing procedure. A bit more clunky to use than an OME function, but much easier to implement. Here's my attempt at such a procedure, not terribly well tested but it should serve as an example. You may need to adjust the widths of the input and output strings:
create procedure split
(
inval = varchar(200) not null,
sep = varchar(50) not null,
n = integer not null
)
result row r(x varchar(200)) =
declare tno = integer not null;
srch = integer not null;
ptr = integer not null;
resval = varchar(50);
begin
tno = 1;
srch = 1;
ptr = 1;
while (:srch <= length(:inval))
do
while (substr(:inval, :srch, length(:sep)) != :sep
and :srch <= length(:inval))
do
srch = :srch + 1;
endwhile;
if (:tno = :n)
then
resval=substr(:inval, :ptr, :srch - :ptr);
return row(:resval);
return;
endif;
srch = :srch + length(:sep);
ptr = :srch;
tno = :tno + 1;
endwhile;
return row('');
end;
select s.x from session.x t, split(t.a,'XXX',2) s;

String operation in SQL to reverse a string

In DB2 9.7 I am looking for a way to reverse a string in a SQL query.
I am familiar with SQL Server where the query would be like
SELECT
REVERSE(LEFT_TO_REIGHT) AS RIGHT_TO_LEFT
FROM
TABLE1;
I couldn't find a similar function in DB2. is there a simple way to reverse a string?
Creating a REVERSE(..) function is unnecessary.
DB2 has something called RIGHT(string-expression, length):
The RIGHT function returns the rightmost string of string-expression
of length length, expressed in the specified string unit. If
string-expression is a character string, the result is a character
string. If string-expression is a graphic string, the result is a
graphic string
So if you're interested in the last 8 characters, you can pretty trivially do this via:
SELECT RIGHT(left_to_right, 8) AS right_to_left
FROM Table1
(I'm actually still concerned about the fact that you're splitting off 8 characters consistently, as it implies you have a multi-part key of some sort).
Try something like:
SELECT STRIP(CAST( TRANSLATE('87654321',LEFT_TO_REIGHT, '12345678') AS VARCHAR(8) ))
FROM TABLE1;
Due to the original question this is the first webpage that comes up when one searches for 'How to reverse a string in DB2'.
Here is an answer that doesn't require implementing it in C and shouldn't brake on non-pure-Engilsh strings regardless of their length.
Be warned though, the efficiency is 'meh' at best.
CREATE FUNCTION REVERSE_STRING(STR VARCHAR(100))
RETURNS VARCHAR(100)
LANGUAGE SQL
SPECIFIC REVERSE_STRING
DETERMINISTIC
REVERSE: BEGIN
DECLARE REVERSED_STRING VARCHAR(100);
DECLARE REVERSED_CHARACTERS_INDEX INTEGER;
SET REVERSED_STRING='';
SET REVERSED_CHARACTERS_INDEX=0;
WHILE (REVERSED_CHARACTERS_INDEX < CHARACTER_LENGTH(STR, CODEUNITS16))
DO
SET REVERSED_CHARACTERS_INDEX = REVERSED_CHARACTERS_INDEX + 1;
SET REVERSED_STRING = CONCAT(
REVERSED_STRING,
LEFT(RIGHT(STR, REVERSED_CHARACTERS_INDEX, CODEUNITS16), 1, CODEUNITS16));
END WHILE;
RETURN REVERSED_STRING;
END REVERSE#
The idea is to get a substring which starts from the n-th character from the right till the end of the string, then take the first element of this substring from the left and append it to a reversed string. This operation is conducted n times where n is the length of a string to be reversed.
You can use it like any other function.
SELECT FIRSTNME AS FIRSTNAME, REVERSE_STRING(FIRSTNME) AS REVERSED_FIRSTNAME
FROM SAMPLE.EMPLOYEE#
Example output
Answering the original question of reversing a string there's user defined functions published on the IBM site that will do it that you can find here. There's apparently no built in ability in DB2
https://www.ibm.com/developerworks/community/blogs/SQLTips4DB2LUW/entry/reverse?lang=en
Tortured SQL version:
CREATE OR REPLACE FUNCTION REVERSE(INSTR VARCHAR(4000))
RETURNS VARCHAR(4000) SPECIFIC REVERSE
DETERMINISTIC NO EXTERNAL ACTION CONTAINS SQL
RETURN WITH rec(pos, res) AS (VALUES (1, CAST('' AS VARCHAR(4000)))
UNION ALL
SELECT pos + 1, SUBSTR(INSTR, pos , 1) || res
FROM rec
WHERE pos <= LENGTH(INSTR)
AND pos < 5000)
SELECT res FROM rec WHERE pos > LENGTH(INSTR);
But then you have to do this as well, yuck:
CREATE BUFFERPOOL bp32 PAGESIZE 32K;
CREATE SYSTEM TEMPORARY TABLESPACE tsp32 PAGESIZE 32K BUFFERPOOL bp32;
A saner C implementation
#include <sqludf.h>
#ifdef __cplusplus
extern "C"
#endif
void SQL_API_FN ReverseSBCP(SQLUDF_VARCHAR *inVarchar,
SQLUDF_VARCHAR *outVarchar,
SQLUDF_SMALLINT *inVarcharNullInd,
SQLUDF_SMALLINT *outVarcharNullInd,
SQLUDF_TRAIL_ARGS)
{
int inLen, inPos, outPos;
if (*inVarcharNullInd == -1)
{
*outVarcharNullInd = -1;
}
else
{
inLen = strlen(inVarchar);
for (inPos = 0, outPos = inLen -1; inPos < inLen; inPos++, outPos--)
{
outVarchar[outPos] = inVarchar[inPos];
}
outVarchar[inLen] = '\0';
*outVarcharNullInd = 0;
}
return;
}

Oracle UPDATE has no effect

I am working on Sql Developper an I created the following procedure in a package:
PROCEDURE VALIDER(a_session IN NUMBER) AS
i NUMBER;
TYPE type_tab IS TABLE OF PANIER%ROWTYPE;
tabSeances type_tab;
BEGIN
SELECT * BULK COLLECT INTO tabSeances
FROM PANIER
WHERE a_session = sessionweb;
i:=0;
FOR i IN 1 .. tabSeances.count LOOP
-- UPADTE DU NOMBRE DE PLACES LIBRES
BEGIN
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND day = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
--UPDATE ON PANIER
UPDATE PANIER
SET valide = 1
WHERE sessionweb = a_session
AND num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN raise_application_error(-20035, 'Pas de données');
WHEN OTHERS THEN raise_application_error(-20006,'Autres Erreurs');
END;
END LOOP;
END VALIDER;
The procedure executes normaly and I don't get an error.
I have a kind of product cart: "PANIER". I loop all the entries in thsi cart for one person (session) to validate them in the database and decrement the total number of seats.
But the field "remaining-seats" (from PROJECTIONS) in the first update don't work. The field isn't updated. I have already tried with other values but nothing.
I am sure that the procedure is executetd because the second update still works. It marks the cart entry as "CONFIRMED".
I don't have any trigger on this field.
My tables contains valid data (<>NULL).
I execute this procedure like this (in a BEGIN END; block):
CMDPLACES.VALIDER(1);
Thank for your reply.
Is it day or dateseance in your first update?
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
Also as #ThorstenKettner was mentioning, the timestamp data in the date , may fail while comparing, so we have TRUNCATE the timestamp data using TRUNC() [if needed]!
If the date column is indexed, beware the index will not be used by the database .
To handle NO Data in UPDATE, you can check (SQL%ROWCOUNT > 0) to identify the number of rows updated!
Your first update compares days. What data type are these? In case you deal with DATETIME, make sure to compare without the time part if any. Use TRUNC to achieve this.
AND TRUNC(day) = TRUNC(tabseances(i).dateseance)

Stored procedure to find next and previous row in SQL Server 2005

Right now I have this code to find next and previous rows using SQL Server 2005. intID is the gallery id number using bigint data type:
SQL = "SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec FROM gallery AS p CROSS JOIN gallery AS n where p.galleryid < '"&intID&"' and n.galleryid > '"&intID&"'"
Set rsRec = Server.CreateObject("ADODB.Recordset")
rsRec.Open sql, Conn
strNext = rsRec("nextrec")
strPrevious = rsRec("previousrec")
rsRec.close
set rsRec = nothing
Problem Number 1:
The newest row will return nulls on the 'next record' because there is none. The oldest row will return nulls because there isn't a 'previous record'. So if either the 'next record' or 'previous record' doesn't exist then it returns nulls for both.
Problem Number 2:
I want to create a stored procedure to call from the DB so intid can just be passed to it
TIA
This will yield NULL for previous on the first row, and NULL for next on the last row. Though your ordering seems backwards to me; why is "next" lower than "previous"?
CREATE PROCEDURE dbo.GetGalleryBookends
#GalleryID INT
AS
BEGIN
SET NOCOUNT ON;
;WITH n AS
(
SELECT galleryID, rn = ROW_NUMBER()
OVER (ORDER BY galleryID)
FROM dbo.gallery
)
SELECT
previousrec = MAX(nA.galleryID),
nextrec = MIN(nB.galleryID)
FROM n
LEFT OUTER JOIN n AS nA
ON nA.rn = n.rn - 1
LEFT OUTER JOIN n AS nB
ON nB.rn = n.rn + 1
WHERE n.galleryID = #galleryID;
END
GO
Also, it doesn't make sense to want an empty string instead of NULL. Your ASP code can deal with NULL values just fine, otherwise you'd have to convert the resulting integers to strings every time. If you really want this you can say:
previousrec = COALESCE(CONVERT(VARCHAR(12), MIN(nA.galleryID)), ''),
nextrec = COALESCE(CONVERT(VARCHAR(12), MAX(nB.galleryID)), '')
But this will no longer work well when you move from ASP to ASP.NET because types are much more explicit. Much better to just have the application code be able to deal with, instead of being afraid of, NULL values.
This seems like a lot of work to get the previous and next ID, without retrieving any information about the current ID. Are you implementing paging? If so I highly recommend reviewing this article and this follow-up conversation.
Try this (nb not tested)
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
I'm assuming you validate that intID is an integer before using this code.
As for a stored procedure -- are you asking how to write a stored procedure? If so there are many tutorials which are quite good on the web.
Since Hogan contributed with the SQL statement, let me contribute with the stored proc part:
CREATE PROCEDURE spGetNextAndPreviousRecords
-- Add the parameters for the stored procedure here
#intID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
END
And you call this from code as follows (assuming VB.NET):
Using c As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
c.Open()
Dim command = New SqlCommand("spGetNextAndPreviousRecords")
command.Parameters.AddWithValue("#intID", yourID)
Dim reader as SqlDataReader = command.ExecuteReader()
While(reader.Read())
' read the result here
End While
End Using