Sunday, January 31, 2016

Better String Aggregation since Oracle 11gR2

In my previous blog "String aggregate in Oracle", I mentioned the restriction of that function is no ordering. Actually since Oracle 11g Release 2, Oracle introduced a new function LISTAGG which provides the same functionality, and also with ordering. You can find details here.

Anyway, I'll show some quick examples in this blog too. To achieve the same result, the query below can be used:
    select employee_id,
           LISTAGG(job_id, '|') WITHIN GROUP (ORDER BY job_id) job_list
    from job_history
    group by employee_id;

And below is the result.
    EMPLOYEE_ID JOB_LIST                     
    ----------- --------------------
            101 AC_ACCOUNT|AC_MGR   
            102 IT_PROG             
            114 ST_CLERK            
            122 ST_CLERK            
            176 SA_MAN|SA_REP       
            200 AC_ACCOUNT|AD_ASST
            201 MK_REP             

And you can see every item in job_list is order.

This new function can be used as analytical function as well.

Saturday, January 30, 2016

Spring Framework: Confusions of RowCallbackHandler

I have had some time not writing my blog. Mainly because my previous employer doesn't allow me to access blogspot during the work hour - also I kind of ran out of topics. I would like to write something that is advanced, not only just tutorials. You can find some many entry-level materials repeating each other, but many times, you'll struggle too long when facing a deeper issue. At least I struggled so many times.

Anyway, I tried to restart blogging. And today's topic is about RowCallbackHandler interface in Spring framework.

You can find many tutorials/samples that suggesting you write program as below:

public class RowCallbackTutorial {
    private DataSource dataSource;
    public void query(String sql) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.query(sql, new RowCallbackHandler(){
            public void processRow(ResultSet rs) throws SQLException {
                System.out.println("Inside RowCallbackHandler");
while ( rs.next() ) { System.out.println("Got value:" + rs.getObject(1)); } } }); } }
This actually is not right. Mentioned in the Java doc of this interface, method processRow "should not call next() on the ResultSet" - I guess they mean "should not call the next() on the first row of the ResultSet", since the ResultSet has already opened and pointing to first row when this method is called.

Here is a test using Spring embedded database support.

First we prepare a sql script to create table and insert a few rows, "db/init.sql". I have:
    CREATE TABLE customer (
        id         INTEGER PRIMARY KEY,
        name       VARCHAR(30)
    );

    insert into customer values(1, 'cust1');
    insert into customer values(2, 'cust2');
Then we test with the data.

    public static void main(String[] args) {
        EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.DERBY)
        .addScript("db/init.sql")
        .build();
        String sql = "select * from customer";
        RowCallbackTutorial sample = new RowCallbackTutorial();
        sample.setDataSource(db);
        sample.query(sql);
    }
We're expecting the program to output:
    Got value:1
    Got value:2
In fact, the previous program will only show second row: Got value:2. Where is the first row?

The ResultSet has already opened, and the cursor is pointing to first row. The first "next()" will move the cursor to next row. So the correct operation should be:
                do {
                    System.out.println("Got value:" + rs.getObject(1));
                }  while ( rs.next() );
And run the program again, we now get the correct result:
    Got value:1
    Got value:2
Now here is the last question. What if the ResultSet is empty? Will "do {...} while ()" encounter any error?

The answer is, no problem. In such case, the RowCallbackHandler will not be invoked at all. Change the query to "select * from customer where 1=2" for a new test, the output "Inside RowCallbackHandler" will not appear.

Friday, March 01, 2013

SQLPlus connection without tnsnames.ora

You can connect using sqlplus with user/pwd@tnsname, or with tnsname string directly as:



sqlplus  user/pwd@'(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=<host>)(PORT=1521)))(CONNECT_DATA=(SID=<sid>))'


This is cumbersome, and has a lot of problem on unix, since brackets "(" and ")" need backslashes.   Since Oracle 10g, there's a better way:  
  
sqlplus user/pwd@//host:1521/sid



I feel it's very useful, so noted down for future use.

Wednesday, October 24, 2012

Another program to find hot threads in Java VM

Many people know the GUI tools, such as jconsole and JTop, and maybe jvisualvm in latest JDK releases, to monitor and analyze JVM performance.

The tools jconsole together with JTop can be used to indentify threads that take most of the CPU usage. However, they work under GUI environment. In case you work in UNIX, without XServer, plus you don't have the JMX agent started, you may not have such luxury. It may be rare, but did happen in my case: HP UX, no XServer, cannot be attached to another XServer due to production firewall, the JMX agent is not started. Anyway, it's very frustrating.

Here's a tool can benefit the above case: http://weblogs.java.net/blog/brucechapman/archive/2008/03/hot_threads.html. It takes a process id as input, and prints top threads that take most of the CPU usage. In HP UX environemnt, I have to include tools.jar in the classpath:

    ${JAVA_HOME}/bin/java -Xbootclasspath/a:${JAVA_HOME}/lib/tools.jar -jar HotThread.jar 

Without tools.jar, it gives error that "java.lang.NoClassDefFoundError: com/sun/tools/attach/VirtualMachine".
Tried this in development environment, but didn't have chance to try it in production, due to lengthy procedures. Sigh...

Thursday, October 11, 2012

CVS check out on UNIX with ssh

This article describes scenario that both your CVS server and client are on unix boxes, and you want to check out/check in using ssh.

By doing this, you can avoid typing password each time. Other than CVS settings, it mainly discusses how to set ssh login from one unix server to anohter. Here are the steps.

1. On your client unix, you first need to have environment variables below set:
    export CVSROOT=:ext:<your_cvs_account>@<cvs_server_name>:<cvs_root_dir>
    export CVS_RSH=ssh
    export CVS_SERVER=/usr/local/bin/cvs (or where your cvs is installed on server side)
You may put them in .profile file under your home.

2. Generate key pair on client unix using dsa
    cd ~/.ssh ssh-keygen –t dsa 
Just hit “Return” key when prompted with “Enter passphrase”. You’ll then find two new files created: id_dsa and id_dsa.pub.
    cat id_dsa.pub
Copy the content (you'll paste it on server side in next step).

3. Copy the public key to cvs server.
Login to cvs server using your account:
    cd .ssh
    vi authorized_keys
Add a new line at the end, and paste the content (copied from previous step). Save and quit.
Note: it may be authorized_keys2 depends on the ssh version.

4. Do a test on client unix.
    ssh <cvs_server>
It should let you in without asking for password.

5. Now you can use cvs as usual.
    cvs co <cvs_path>

Struts 2: Populate data for page even after validation failed

I recently added a validation interceptor in one of our Struts 2 applications, but got some errors when validation failed. It took me some time to figure out the error since all exceptions have been redirected to a global exception page, and no logging was written.

Anyway, the error is:
The requested list key 'sortTypeList' could not be resolved as a collection/array/map/enumeration/iterator type.
After some investigation, I figured out the reason being that there is a piece of code in the action method to prepare this list to be dispalyed on the jsp page as a drop down list:

    sortTypeList=new ArrayList();
    sortTypeList.add(new KeyValueVO("id", getText("label.id")));
    sortTypeList.add(new KeyValueVO("name", getText("label.name")));

When the validation fails, this piece of code won't run, leaving the list not being populated. Where KeyValueVO is a class contains two Strings: key and value.

To avoid this, Struts 2 provided an interface Preparable, which has one method:
    public void prepare();

I made the action implement Preparable interface and moved the above code to inside prepare method. It worked properly.

My action also contains a few other method for different actions, not all of them will need populate this list. I made the method prepare empty, and move the code to new methods named as prepare(). For example, the list is needed in sort(), and search(), but not delete(). I created new methods for them:

    prepareSort() { // will run before sort() ...
    prepareSearch() { // will run before search() ... 
The prepare methods are invoked by the interceptor below:
    <interceptor-ref name="prepare"/>
So, you'll need to make sure it is on the interceptor stack and it appears before interceptor "validation".
    <interceptor-ref name="prepare"/>
    ... 
    <interceptor-ref name="validation"/> 
The preparable interceptor supports a parameter: alwaysInvokePrepare. By default it's true, meaning the prepare methods will run.

There are more details can be found in http://struts.apache.org/2.2.1/docs/prepare-interceptor.html, and this link shows an additional solution: http://struts.apache.org/2.2.1/docs/how-do-we-repopulate-controls-when-validation-fails.html.

Thursday, July 22, 2010

Interval Partitions in Oracle 11g

Among many other enhancements in Oracle 11g, interval partition is definitely a good one for DBAs. There is a good article here discussing it (and other partition enhancements).

In one of our projects, we are looking for sub-partitions within an interval partition, and it's well able to handle. Here's an extended discussion of the link above.

This time we have a interval partition, and list subpartitions inside.
CREATE TABLE interval_tab (
  id           NUMBER,
  code         VARCHAR2(10),
  description  VARCHAR2(50),
  created_date DATE
)
PARTITION BY RANGE (created_date)
INTERVAL (NUMTOYMINTERVAL(1,'MONTH'))
SUBPARTITION BY LIST( code )
SUBPARTITION TEMPLATE
  ( SUBPARTITION CD_01 VALUES ('ONE'),
    SUBPARTITION CD_02 VALUES ('TWO'),
    SUBPARTITION CD_03 VALUES ('THREE')
  )
(
   PARTITION part_01 values LESS THAN (TO_DATE('01-NOV-2007','DD-MON-YYYY'))
);

INSERT INTO interval_tab VALUES (1, 'ONE', 'One', TO_DATE('16-OCT-2007', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (2, 'TWO', 'Two', TO_DATE('31-OCT-2007', 'DD-MON-YYYY'));
COMMIT;
EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

COLUMN partition_name FORMAT A20
COLUMN subpartition_name FORMAT A20
COLUMN high_value FORMAT A10

SELECT partition_name, subpartition_name, high_value, num_rows
FROM   user_tab_subpartitions
where table_name = 'INTERVAL_TAB'
ORDER BY table_name, partition_name, subpartition_name;

PARTITION_NAME       SUBPARTITION_NAME    HIGH_VALUE   NUM_ROWS
-------------------- -------------------- ---------- ----------
PART_01              PART_01_CD_01        'ONE'               1
PART_01              PART_01_CD_02        'TWO'               1
PART_01              PART_01_CD_03        'THREE'             0

By adding more data that expands the partitions, you'll see the subpartitions are generated as well.
INSERT INTO interval_tab VALUES (3, 'THREE', 'Three', TO_DATE('01-NOV-2007', 'DD-MON-YYYY'));
INSERT INTO interval_tab VALUES (4, 'TWO', 'TWO', TO_DATE('30-NOV-2007', 'DD-MON-YYYY'));
COMMIT;
EXEC DBMS_STATS.gather_table_stats(USER, 'INTERVAL_TAB');

SELECT partition_name, subpartition_name, high_value, num_rows
FROM   user_tab_subpartitions
where table_name = 'INTERVAL_TAB'
ORDER BY table_name, partition_name, subpartition_name;

PARTITION_NAME       SUBPARTITION_NAME    HIGH_VALUE   NUM_ROWS
-------------------- -------------------- ---------- ----------
PART_01              PART_01_CD_01        'ONE'               1
PART_01              PART_01_CD_02        'TWO'               1
PART_01              PART_01_CD_03        'THREE'             0
SYS_P40              SYS_SUBP37           'ONE'               0
SYS_P40              SYS_SUBP38           'TWO'               1
SYS_P40              SYS_SUBP39           'THREE'             1

Subpartition names are automatically with system name, which is not I'm expecting: it's better to named as partition + subpartition_template, ie, SYS_P40_CD_01.

Friday, April 10, 2009

Encrypt in WebLogic

A colleague gave me a piece of code to plug into my WebLogic 10 JDBC configure file:
<jdbc-data-source ...
...
<password-encrypted>{3DES}xxxxxxxxxxxxxxx</password-encrypted>
...
</jdbc-data-source>

When I copied over, and tried to start WebLogic, got an exception:

weblogic.management.ManagementRuntimeException: com.rsa.jsafe.JSAFE_PaddingException: Could not perform unpadding: invalid pad byte.

It's really not telling what you'll have to do. After some research, I figured out I need to re-encrypt the password. Here's the utility to use:

java -cp <weblogic_home>\server\lib\weblogic.jar -Dweblogic.RootDirectory=<your_domain_dir> weblogic.security.Encrypt <password>

Pasted the result to replace old password, it worked fine.


Thursday, May 01, 2008

Slow When Bulk Inserting Records to Database Using Lotus Notes Agent

I don't do Lotus Notes program. But our Notes developers told me that it was extremely slow to insert records into Oracle (SQL Server as well) database. One example is that 1 million rows took 8 hours.

After some investigation, I found that they were constructing full SQLs, instead of using parameters. Below is a piece of their code:
    Dim con As ODBCConnection
   Dim qry As ODBCQuery
   Dim result As ODBCResultSet

   Set qry = New ODBCQuery
   Set result = New ODBCResultSet
   con.ConnectTo("")
   Set qry.Connection = con

   for each document loop
       qry.SQL = "insert into person (fname,lname) values( '" & v_fname & 
                  "','" & v_lname & "' )"
       Set result.Query = qry
   end loop
   ...
The high-lite is the trouble. Whenever this was called, I noticed so many queries on Oracle data dictionary views. It seems to me that Notes was parsing the SQL, and the second statement sometimes took about 2 seconds.

To solve it, I did some research and found the parameters was useful.
    qry.SQL = "insert into person (fname,lname) values( ?fname?, ?lname? )"
    Set result.Query = qry
    for each document loop
        Call result.SetParameter( fname, "'" & v_fname & "'" )
        Call result.SetParameter( lname, "'" & v_lname & "'" )
    end loop


The performance improved greatly, one example is 30K records reduced time from 4 hours to 10 minutes. Still high, but Notes spends most of the time preparing data.

Friday, March 14, 2008

Accessing Non-exsits Item in Oracle Associative Array

Look at this piece of code:
 set serveroutput on
 declare
  type MONTH_TYPE is table of varchar(20) index by binary_integer;

  month_table   MONTH_TYPE;
begin
  month_table(1) := 'Jan';
  month_table(2) := 'Feb';

  if month_table(3) is null then
    dbms_output.put_line( 'March is not defined.' );
  end if;
end;
/
What you'll get? You may think the print line.

However, you'll get an error:
    ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 9
Well, associative array is working the same way as table (is that why it's defined as TABLE of ...), and month_table is similar to select value from month_table into v..., so need to have an exception handling.
    set serveroutput on
 declare
   type MONTH_TYPE is table of varchar(20) index by binary_integer;

   month_table   MONTH_TYPE;
 begin
   month_table(1) := 'Jan';
   month_table(2) := 'Feb';

   if month_table(3) is null then
     dbms_output.put_line( 'March is not defined.' );
   end if;
 exception
   when NO_DATA_FOUND then
     dbms_output.put_line( 'March is not found.' );
 end;
 /
Then, you'll get: March is not found.

Thursday, January 03, 2008

Track Long Operations in Oracle

You may need to run a process on 1 million rows and the whole process takes a few hours to finish. In Oracle, there is a way that you can track the process.

Here's a piece of example code in PL/SQL, it takes advantage of package DBMS_APPLICATION_INFO.
 declare
   -- main variables
   ...

   -- long op info
   v_rindex     PLS_INTEGER;
   v_slno       PLS_INTEGER;
   v_totalwork  NUMBER;
   v_sofar      NUMBER;
   v_obj        PLS_INTEGER;

   v_op_name    varchar(100) := 'My work';
   v_units      varchar(100) := 'rows processed';
 begin
   -- Calculate total work (number of rows to be processed, etc.)
   select count(*) into v_totalwork from ...;

   v_sofar := 0;
   v_rindex     := DBMS_APPLICATION_INFO.set_session_longops_nohint;

   for ... -- A loop to process your work
   loop
     ...  -- do your work here.

     -- log longops view.
     v_sofar := v_sofar + 1;
     if mod( v_sofar, 500 ) = 0 then   -- log your operation every 500 rounds
        DBMS_APPLICATION_INFO.set_session_longops(rindex   => v_rindex,
                                               slno        => v_slno,
                                               op_name     => v_op_name,
                                               target      => v_obj,
                                               context     => 0,
                                               sofar       => v_sofar,
                                               totalwork   => v_totalwork,
                                               target_desc => 'Some description here',
                                               units       => v_units);
     end if;

   end loop;

   -- mark the end
   DBMS_APPLICATION_INFO.set_session_longops(rindex   => v_rindex,
                                          slno        => v_slno,
                                          op_name     => v_op_name,
                                          target      => v_obj,
                                          context     => 0,
                                          sofar       => v_sofar,
                                          totalwork   => v_totalwork,
                                          target_desc => 'Some description here',
                                          units       => v_units);

 end;
 /

To view the status of the process, run query:
 select sid, serial#, opname, sofar, totalwork, start_time, last_update_time
 from v$session_longops where opname = 'My Work';

Columns sofar and totalwork show you how much work has been done so far.

Wednesday, November 14, 2007

Another Way to Reorgnize Table in Oracle

After inserting/deleting from a table for a long time, the table may contain much spared space that hurts the full table scan greatly. Instead of export and import, or recreate it. Move it from one tablespace to another is another way.

Here's a case I did recently. I noticed a table is slow when running full table scan, however it contains only 30K rows, and each row is not that big.

select segment_name, sum(bytes)/1024/1024 MB_Bytes, sum(blocks) blocks, sum(extents) extents
from user_segments
where segment_name = 'BAD_TABLE'
group by segment_name;

SEGMENT_NAME        MB_BYTES   BLOCKS     EXTENTS
------------------- ---------- ---------- ----------
BAD_TABLE           220        28160      99
OK, move it to another tablespace:
alter table BAD_TABLE move tablespace tb_another;
Run that check again:
select segment_name, sum(bytes)/1024/1024 MB_Bytes, sum(blocks) blocks, sum(extents) extents
from user_segments
where segment_name = 'BAD_TABLE'
group by segment_name;

SEGMENT_NAME           MB_BYTES   BLOCKS     EXTENTS
---------------------- ---------- ---------- ----------
BAD_TABLE              32         4096       47
It's much smaller. Well, you may want to move it back to its original tablespace.

Friday, July 20, 2007

Oracle Database Console Credential Failures on Windows

There is an error message in Oracle Database console:

Connection to host as user USER failed: ERROR: Wrong password for user

Usually you find this error when trying to connect to host, for example, you want to start/shutdown, or backup the database. It had confused me for a long time. Then I found an solution.

1. Provide the 'Log on as a batch job' privilege.
  • Go to control panel/administrative tools
  • click on "local security policy"
  • click on "local policies"
  • click on "user rights assignments"
  • double click on "log on as a batch job"
  • click on "add" and add the user(s) that you're going to use in database console.
2. Set credentials and test
  • Go to the Preferences link in the database console page
  • click on Preferred Credentials (link on the left menu)
  • under "Target Type: Host" click on "set credentials"
  • enter the OS user(s) for whom you have set "logon as a batch job" privilege
  • click on "Test"
Then you should see the backup is working.

Friday, February 16, 2007

Get Month End in SQL Server

SQL Server doesn't have a function to get the month end. Here shows one I wrote:

 ALTER  function dbo.sp_getmonthend ( @inputDate    DATETIME )
 /*
   This function returns the month end of a specific date.
 */
 RETURNS DATETIME
 BEGIN
     DECLARE @outputDate        DATETIME

     select @outputDate = CAST(YEAR(@inputDate) AS VARCHAR(4)) + '-' +
                        CAST(MONTH(@inputDate) AS VARCHAR(2)) + '-01'
     select @outputDate = dateadd( day, -1, dateadd( month, 1, @outputDate ) )
     return @outputDate
 END
Since there is 1st in every month, we forward 1 month from 1st of the month and backward a day.

Tuesday, February 13, 2007

Undrop table in Oracle

I don't "undrop" table a lot. So once when I tried to "undrop" a table, I got an error:

sql> undrop table test1;
SP2-0734:unknown command beginning "undrop tab..." - rest of line ignored.

Well, I soon found, it's not "undrop", instead:

SQL> flashback table test1 to before drop rename to test2;
Flashback complete.

You can rename it if the previous name has been used by others.

Wednesday, December 13, 2006

Row Locks When Inserting - Oracle

My co-worker told me his application hanging while inserting a row into a table. By looking at the Oracle Enterprise Manager, I noticed it's caused by foreign key. Here I show an example.

Open a SQL Plus, create the tables and foreign key.
    create table tmp_test 
    ( id         number(5) not null, 
      test_type  char(1), 
      comments varchar2(100),
      constraint pk_tmp_test primary key (id) 
    );

    create table tmp_test_type 
    ( test_type char(1), 
      description  varchar2(100),
      constraint pk_tmp_test_type primary key (test_type) );

    alter table tmp_test add constraint fk1_tmp_test foreign key ( test_type )
      references tmp_test_type;


Now try to insert a row without referring type. Expecting an error:
    insert into tmp_test values( 1, 'A', 'Just a test' );

    ORA-02291: integrity constraint (EAS_DEMO.FK1_TMP_TEST) violated - parent key not found


Open another session using SQL Plus, insert a row in test_test_type table but don't commit:
    insert into tmp_test_type values( 'A', 'Test type A' );


Run the insert again using the first SQL Plus, you'll find it hangs. In Oracle Enterprise Manager, you can see "enq: TX - row lock contention".

Commit the changes and cleanup:
    drop table tmp_test;
    drop table tmp_test_type;

Wednesday, October 18, 2006

Delete Duplicate Rows

It's easy to delete duplicate rows in Oracle using cursor and rownum. Here shows an example.

Create table and insert some duplicate rows.

CREATE TABLE DUP_TEST
(
COMPANY_ID VARCHAR(8),
COMPANY_NAME VARCHAR(80),
ADDRESS VARCHAR(80)
);

-- Create test data, dup by company id
INSERT INTO DUP_TEST VALUES ('1', 'Company One', 'Address1');
INSERT INTO DUP_TEST VALUES ('1', 'Company One', 'Address1');
INSERT INTO DUP_TEST VALUES ('2', 'Company Two', 'Address2');
INSERT INTO DUP_TEST VALUES ('2', 'Company Two', 'Address');
INSERT INTO DUP_TEST VALUES ('3', 'Company Three', 'Address3');

The duplicates are with company id 1 and 2. Let delete the duplicates.
set serveroutput on
declare
  rows_deleted integer := 0;
begin
  for cur in ( select company_id, count(*) cnt from DUP_TEST
               group by company_id having count(*) > 1 )
  loop
    delete from DUP_TEST
    where company_id = cur.company_id and rownum < cur.cnt;
    rows_deleted := rows_deleted + SQL%ROWCOUNT;
  end loop;
  dbms_output.put_line( 'records deleted: '  rows_deleted );
end;
/


Now show the result after deleting.
SQL> select * from dup_test;
COMPANY_ COMPANY_NAME ADDRESS
-------- -------------------- --------------------
1 Company One Address1
2 Company Two Address
3 Company Three Address3
It's a little bit diffcult to delete duplicate rows in SQL Server. I'll show one solution using a cursor. Consider the performance when you have large amount of data to search or delete.
declare cur cursor for select COMPANY_ID from DUP_TEST
declare @CompanyId int

open cur
fetch cur into @CompanyId

while @@fetch_status = 0
begin
if ( select count(*) from DUP_TEST where COMPANY_ID = @CompanyId ) > 1
begin
delete from DUP_TEST where current of cur
end
fetch cur into @CompanyId
end
close cur
deallocate cur
go
It goes through the full table and search duplicates for each row, if found, delete the current of cursor.

Monday, October 16, 2006

Ref: moving from Sybase to SQL

This is a good article discussing the difference between Sybase and SQL Server.

Thursday, October 05, 2006

SQL Server - Append Query Result to .csv file

While searching on the web, I found a lot talking about appending SQL Server data to Excel files. But not many about appending data to .csv file. After some research, I got one.

First, create the csv file and prepare the column header:
master..xp_cmdshell 'echo EmpID,LastName,FirstName > C:\temp\Employees.csv'
It creates a file c:\temp\Employees.csv with 3 columns.

Then, append the data using OPENROWSET:
INSERT INTO OPENROWSET('Microsoft.Jet.OleDB.4.0',
'Text;Database=C:\temp',
[Employees#csv])
select EmployeeID, LastName, FirstName from NOrthwind..Employees
(9 row(s) affected)
Open the file c:\temp\Employees.csv, you'll see there are 9 rows appended.
Some points here are: 1) Database=C:\temp (need to put the folder here only), and 2) [Employees#csv] (need to replace . with # as the table name.

OK, it's similar to read the file back:
select * from OPENROWSET('Microsoft.Jet.OleDB.4.0',
'Text;Database=C:\temp', [Employees#csv])

1 Davolio Nancy
...
9 Dodsworth Anne
At the end, I'll show another way to read the data in csv file:
select * from OpenRowset('MSDASQL',
'Driver={Microsoft Text Driver (*.txt; *.csv)};DefaultDir=C:\temp;',
'select top 3 * from "Employees.csv"')
It seems you cannot append to csv using MSDASQL.

Friday, August 18, 2006

Drop Linked Server in SQL Server

You have learned how to create linked server in SQL Server in Accessing Oracle From SQL Server. Sometimes you may want to drop it. I'll show you how.

First find and drop the logins associated with the linked server.
user master
go

select s.srvid, s.srvname, l.name login_name_associated
from dbo.sysxlogins x, dbo.syslogins l, dbo.sysservers s
where l.sid = x.sid and s.srvid = x.srvid and s.srvname = 'ora_test'
go
srvid srvname login_name_associated
----- --------- ------------------------
1 ora_test sa
1 ora_test analyst
Now sa and analyst need to be dropped from the linked server.
sp_droplinkedsrvlogin @rmtsrvname = 'ora_test', @locallogin = 'analyst'
go
sp_droplinkedsrvlogin @rmtsrvname = 'ora_test', @locallogin = 'sa'
go
Then the linked server is ready to be dropped.
sp_dropserver @server = 'ora_test'
go
The logins have to be dropped from linked server first, otherwise an error message will show:
There are still remote logins for the server 'ora_test'.