2018-11-28

Get Significant Figures in Oracle

Basic Idea

You can use common logarithm (i.e. log base 10) to evaluate the number of digits.
FLOOR(LOG(10, n)) indicates the decimal places of the first non-zero digit as follows.

SQL> WITH DATA AS (
  2  SELECT 1234 N FROM DUAL
  3  UNION ALL SELECT 234 N FROM DUAL
  4  UNION ALL SELECT 34 N FROM DUAL
  5  UNION ALL SELECT 4 N FROM DUAL
  6  --UNION ALL SELECT 0 N FROM DUAL
  7  UNION ALL SELECT 0.123 N FROM DUAL
  8  UNION ALL SELECT 0.0234 N FROM DUAL
  9  UNION ALL SELECT 0.00345 N FROM DUAL
 10  )
 11  SELECT N, FLOOR(LOG(10, N)) FROM DATA;

         N FLOOR(LOG(10,N))
---------- ----------------
      1234                3
       234                2
        34                1
         4                0
      .123               -1
     .0234               -2
    .00345               -3

Note: LOG(10, 0) causes an error: ORA-01428: argument '0' is out of range

Therefore, you can get the value rounded to the first digit of a given number n by ROUND(n, - FLOOR(LOG(10, n))).

SQL> WITH DATA AS (
  2  SELECT 1234 N FROM DUAL
  3  UNION ALL SELECT 234 N FROM DUAL
  4  UNION ALL SELECT 34 N FROM DUAL
  5  UNION ALL SELECT 4 N FROM DUAL
  6  --UNION ALL SELECT 0 N FROM DUAL
  7  UNION ALL SELECT 0.123 N FROM DUAL
  8  UNION ALL SELECT 0.0234 N FROM DUAL
  9  UNION ALL SELECT 0.00345 N FROM DUAL
 10  )
 11  SELECT N, ROUND(n, - FLOOR(LOG(10, N))) FROM DATA;

         N ROUND(N,-FLOOR(LOG(10,N)))
---------- --------------------------
      1234                       1000
       234                        200
        34                         30
         4                          4
      .123                         .1
     .0234                        .02
    .00345                       .003

In order to round to first d digits, you can modify as ROUND(n, d - FLOOR(LOG(10, n)) - 1).

Function

These can be summarized as the following function.

CREATE OR REPLACE FUNCTION SIGNIFICANT_FIGURES(
    n   NUMBER,
    d   NUMBER
) RETURN NUMBER
IS
BEGIN
    IF n = 0 THEN
        RETURN 0;
    ELSE
        RETURN ROUND(n, d - FLOOR(LOG(10, n)) - 1);
    END IF;
END;

If you want to round half to even, use ROUND_HALF_EVEN function instead of the standard ROUND function.
Note: From Oracle Database 18c, you can use ROUND_TIES_TO_EVEN function.

2018-11-27

Round Half to Even in Oracle

Oracle standard ROUND function is rounding away from 0.
In order to round half to even (aka bankers’ rounding) in Oracle, you have to create a custom function.

Basic idea

Consider the case of rounding to an integer.

  • To judge whether the number is half-way between two integers, you have to divide the absolute value by 1 and see the remainder.
    If the remainder is 0.5, you need to round to even. Otherwise, you can use the standard ROUND function
  • Actually, you had better divide the absolute value by 2 rather than 1 in order to round half to even.
    In this case, you need to round to even when the remainder is 0.5 or 1.5.
    When the remainder is 0.5, you have to round towards 0 (i.e. truncate).
    When the remainder is 1.5, you have to round away from 0. (You can use the standard ROUND function.)

Function

These ideas can be summarized as the following function.

CREATE OR REPLACE FUNCTION ROUND_HALF_EVEN(
    n       NUMBER,
    integer NUMBER DEFAULT 0
) RETURN NUMBER
IS
BEGIN
    IF MOD(ABS(n) * POWER(10, integer), 2) = 0.5 THEN
        RETURN TRUNC(n, integer);
    ELSE
        RETURN ROUND(n, integer);
    END IF;
END;

From Oracle Database 18c, you can use ROUND_TIES_TO_EVEN function.

4

2018-11-19

ある文字を含むが、ある文字は含まない正規表現

ある文字を含むが、ある文字は含まない正規表現をネットで検索すると、
(以下、fooという文字を含み、barは含まない例)

/^(?!.*bar).*(?=foo).*$/

または、

/^(?=.*foo)(?!.*bar).*$/

がヒットするのですが、肯定先読みを使わない以下の書き方がパフォーマンスが良いようです。

/^(?!.*bar).*foo.*$/

参考

2018-02-28

Oracle 12c SCOTTユーザーの権限を確認する

前の記事「Oracle CONNECTおよびRESOURCEロールのまとめ」を書いていて、ふと気になったのですが、サンプル・スキーマのSCOTTユーザーの権限ははどうなっているのでしょうか。

SCOTTユーザーの権限は非推奨の CONNECTロールとRESOURCE ロールを使用していたはずです。Oracle12c では非推奨なので使われなくなっているのでしょうか。

サンプルスキーマの作成スクリプト ORACLE_HOME/rdbms/admin/utlsampl.sql を見ると、

GRANT CONNECT,RESOURCE,UNLIMITED TABLESPACE TO SCOTT IDENTIFIED BY tiger;

となっていました。

まだ、非推奨の RESOURCE ロールと CONNECT ロールが使われているようですね。
また、12c から RESOURCE ロールでは付与されなくなったUNLIMITED TABLESPACE 権限が別に付与されています。コメントに、

Rem mmoore 04/08/91 - use unlimited tablespace priv

と記載されていますので、だいぶ昔から RESOURCE ロールとは別に付与されていたようです。


データディクショナリで確認してみます。

SQL> select * from user_role_privs;

GRANTEE    GRANTED_ROLE
---------- -------------
SCOTT      RESOURCE
SCOTT      CONNECT
SQL> select * from user_sys_privs;

USERNAME  PRIVILEGE            
--------- ---------------------
SCOTT     UNLIMITED TABLESPACE


現セッションで利用できる権限を見てみます。

SQL> select * from session_privs;

PRIVILEGE
----------------------------------------
SET CONTAINER
CREATE INDEXTYPE
CREATE OPERATOR
CREATE TYPE
CREATE TRIGGER
CREATE PROCEDURE
CREATE SEQUENCE
CREATE CLUSTER
CREATE TABLE
UNLIMITED TABLESPACE
CREATE SESSION

昔、CONNECT ロールに付与されていた

  • ALTER SESSION
  • CREATE DATABASE LINK
  • CREATE SYNONYM
  • CREATE VIEW

は付与されてないですね。

非CDB環境で確認しているのですが、非CDBでは必要ないはずのコンテナの切替権限 SET CONTAINER が付与されています。
CONNECT ロールに新たに付与されているようです。

SQL> select * from role_sys_privs where ROLE = 'CONNECT';
ROLE                    PRIVILEGE
-------------------- --------------
CONNECT             SET CONTAINER
CONNECT             CREATE SESSION

2018-02-23

Oracle CONNECTおよびRESOURCEロールのまとめ

Oracle Database 10g R2 から「最低限の権限」原則により CONNECT および RESOURCE ロールは非推奨となった。

Oracle Database セキュリティ・ガイド 10gリリース2(10.2)- 認可: 権限、ロール、プロファイルおよびリソースの制限

注意:
CONNECTおよびRESOURCEロールは、将来のOracle Databaseのリリースで非推奨になる予定のため、使用しないでください。 CONNECTロールが現在保持している権限は、CREATE SESSIONのみです。

なお、非推奨ではあるものの Oracle 12g R2 でも CONNECTおよびRESOURCEロールは事前定義されている。

Oracle® Databaseセキュリティ・ガイド 12cリリース2 (12.2) - Oracle Databaseのインストールで事前に定義されているロール

CONNECTロール

前述の引用にもあるとおり 10g R2 からCONNECT ロールの権限は CREATE SESSION のみになったが、それ以前は以下の権限が付与されていた。

Oracle Database セキュリティ・ガイド 10gリリース2(10.2)- CONNECTロール変更への対処

  • ALTER SESSION
  • CREATE CLUSTER
  • CREATE DATABASE LINK
  • CREATE SEQUENCE
  • CREATE SESSION
  • CREATE SYNONYM
  • CREATE TABLE
  • CREATE VIEW

Oracle 12c から CONNECT ロールに新たに SET CONTAINER 権限が付与されているようです。

RESOURCEロール

RESOURECE ロールに付与されている権限は以下のとおり。

Oracle® Databaseセキュリティ・ガイド 12cリリース2 (12.2) - Oracle Databaseのインストールで事前に定義されているロール

  • CREATE CLUSTER
  • CREATE INDEXTYPE
  • CREATE OPERATOR
  • CREATE PROCEDURE
  • CREATE SEQUENCE
  • CREATE TABLE
  • CREATE TRIGGER
  • CREATE TYPE

Oracle 12c R1 から RESOUCEロールは UNLIMITED TABLESPACE 権限を付与しなくなった。

Oracle® Databaseセキュリティ・ガイド 12cリリース1 (12.1) - Oracle Databaseセキュリティ・ガイドのこのリリースの変更

このリリース以降、RESOURCEロールがデフォルトでUNLIMITED TABLESPACEシステム権限を付与しなくなりました。このシステム権限をユーザーに付与する場合、手動で付与する必要があります。

なお、 UNLIMITED TABLESPACE はロールに付与することはできないので以下のように直接ユーザに付与する。

GRANT UNLIMITED TABLESPACE TO username;

参考

2018-02-13

Windows Server のシステムパーティション(Cドライブ)容量

Windows Server セットアップ時(仮想化環境でない場合)にシステムパーティション(Cドライブ)にどの程度の容量を割り当てるか悩ましいですね。

少々古い記事ですが、Microsoft のブログで “How big should my OS drive be?” という記事が掲載されているので紹介しておきます。

How big should my OS drive be? | Ask the Core Team

Microsoft Enterprise Platforms Support: Windows Server Core Team

こちらの記事では、

  1. 3 x RAM
  2. 10 -12GB ベースOS(役割・機能による)
  3. 10GB アップデート
  4. 10GB その他
  5. +インストールするアプリケーションの容量

の容量を確保することを提案しています。

アプリケーションを除き、RAM 16 GB だと 80GB 、32 GB だと 128 GB になります。
(元記事では (2) を 11 GB で計算して 127 GB になっています。)

記事の執筆時点(2015年10月)で想定していたと思われる Windows Server 2012R2 のOSのサイズは Server Core で 6.4 GB、GUI 使用サーバーで 9.2 GB でした。
Windows Server 2016 では Server Core で 10.3 GB、デスクトップ エクスペリエンスで 15.2 GB に肥大化しています。Windows Server 2016 の場合、その分を加味した方が良いかと思います。

ベンダーの初期パーティション

各ベンダーのプレインストールモデルの初期パーティション容量はどの程度になっているのでしょうか。
ざっと見た感じでは、80 GB や 100 GB で構成されているケースが多いようです。
環境次第ですが、80 GB は少し余裕がないかもしれないですね。

2018-02-06

Display Post Titles in Blogger Page Navigation

Blogger’s page navigation at the bottom of the page displays just “Newer Post” and “Older Post”. It is preferable to show the actual page title instead of them.

I’d searched solutions satisfying the following conditions.

  1. Getting page titles from the blog feeds without loading another page.
    Loading another page is counted as a page visit and skew the stats.
  2. Simple JavaScript snippet (w/o jQuery as possible).
    I want to just overwrite link text by the information of link destination. It should be simple.

Firstly, I found MS-potilas’ post.

It seems nice, but somewhat in a roundabout way, it extracts page titles using publishing dates.

Next, I investigate about the Blogger’s feed specification, and I found that the blog feeds are provided by the Blogger Data API.

Blogger Data API supports “retrieving a post by its path”, so we can get a post information including title by specifying path parameter.

Then, I could write a simple JavaScript (w/o jQuery), which rewrite the link text, Newer / Older Post, to their own title.

JavaScript

Insert this snippet just before </body> in Edit HTML.

<script type='text/javascript'>
//<![CDATA[
// except root, labels, search and mobile pages
if (/.+\.html(\?m=0)?$/.test(location.href)) {
  var olderLink = document.getElementById('Blog1_blog-pager-older-link');
  if (olderLink) {
    getPageTitle(olderLink, setOlderPageTitle);
    function setOlderPageTitle(data){
      setPageTitle(data, olderLink, '', ' &#187;')
    };
  }
  var newerLink = document.getElementById('Blog1_blog-pager-newer-link');
  if (newerLink) {
    getPageTitle(newerLink, setNewerPageTitle);
    function setNewerPageTitle(data){
      setPageTitle(data, newerLink, '&#171; ', '')
    };
  }
  // set the page title from feed data
  function setPageTitle(data, pageLink, prefix, suffix) {
    if (data.feed.entry) {
      if (data.feed.entry.length > 0) {
        var title = data.feed.entry[0].title.$t;
      }
    }
    if (title) {
      pageLink.innerHTML = prefix + title + suffix;
    }
  }
  // get entry data from the feed
  function getPageTitle(pageLink, callback) {
      var pathname = pageLink.getAttribute('href').replace(location.protocol + '//' + location.hostname, '');
      var script = document.createElement('script');
      script.src = '/feeds/posts/summary?alt=json-in-script&max-results=1&redirect=false&path='+pathname+'&callback='+callback.name+'';
      document.body.appendChild(script);
  }
}
//]]>
</script>

CSS

To improve the appearance, add CSS as follows. Please modify as you like.

#blog-pager {
    font-size: 90%;
}
#blog-pager-newer-link {
    float: left;
    width: 40%;
    text-align: left;
}
#blog-pager-older-link {
    float: right;
    width: 40%;
    text-align: right;
}

Adjustment

After adding the above CSS, you may find the text “Home” not at the center of the page navigation in the home or the latest post page. It is because the latest page doesn’t have the element with #blog-pager-newer-link.

Goto Edit HTML and find

<div class='blog-pager' id='blog-pager'>

and

    <b:if cond='data:newerPageUrl'>
      <span id='blog-pager-newer-link'>
      <a class='blog-pager-newer-link' expr:href='data:newerPageUrl' expr:id='data:widget.instanceId + &quot;_blog-pager-newer-link&quot;' expr:title='data:newerPageTitle'><data:newerPageTitle/></a>
      </span>
    </b:if>

    <b:if cond='data:olderPageUrl'>
      <span id='blog-pager-older-link'>
      <a class='blog-pager-older-link' expr:href='data:olderPageUrl' expr:id='data:widget.instanceId + &quot;_blog-pager-older-link&quot;' expr:title='data:olderPageTitle'><data:olderPageTitle/></a>
      </span>
    </b:if>

You can see <span id='blog-pager-newer-link'></span> is inside <b:if cond='data:newerPageUrl'></b:if>.
The latest (newest) page cannot have a link to a newer page, so the page doesn’t have <span id='blog-pager-newer-link'>. The same goes in the “oldest” case.

Overwrite it as follows:

    <span id='blog-pager-newer-link'>
    <b:if cond='data:newerPageUrl'>
      <a class='blog-pager-newer-link' expr:href='data:newerPageUrl' expr:id='data:widget.instanceId + &quot;_blog-pager-newer-link&quot;' expr:title='data:newerPageTitle'><data:newerPageTitle/></a>
      <b:else/>&#160;
    </b:if>
    </span>

    <span id='blog-pager-older-link'>
    <b:if cond='data:olderPageUrl'>
      <a class='blog-pager-older-link' expr:href='data:olderPageUrl' expr:id='data:widget.instanceId + &quot;_blog-pager-older-link&quot;' expr:title='data:olderPageTitle'><data:olderPageTitle/></a>
      <b:else/>&#160;
    </b:if>
    </span>
  • &#160; is &nbsp;, because Blogger’s editor cannot accept some entity references like &nbsp;.

Then, both <span id='blog-pager-newer-link'> and <span id='blog-pager-older-link'> come to appear in any pages.


FYI:

  • This article is introducing how to add images to page navigation, and well-organized.

Add Next/Previous Pager Navigation with Image - Blogger - TwistBlogg - Premium Blogger Tutorials and Widgets

A simple guide on how to add older previous pager or next prev blog pager in new blogger layout. We also shared stylish version that displays image.

2018-02-01

Oracle 非CDB(Non-CDB)はいつまで使えるのか

Oracle 非CDB(Non-CDB)アーキテクチャは、12c から非推奨となっているのですが、いつまで使える(サポートされる)のか現時点の情報をまとめておきます。

まず、日本語版の「Oracle® Databaseアップグレード・ガイド 12cリリース2 (12.2) 」

Oracle Databaseアップグレード・ガイドのこのリリースでの変更

The new release of Oracle Database provides improvements to upgrade performance, automation, and reporting.

には、

  • 非CDBアーキテクチャの非推奨
    非CDBアーキテクチャはOracle Database 12cリリース1では非推奨で、Oracle Database 12c リリース2より後ではサポートされず、使用できない場合があります。

との記載があります。

一方、英語版の ”Oracle® Database Database Upgrade Guide 12c Release 2 (12.2)”

Deprecated Features in Oracle Database 12c Release 2 (12.2)

Review the deprecated features listed in this section to prepare to use alternatives after you upgrade.

には、

  • Deprecation of Non-CDB Architecture
    The non-CDB architecture was deprecated in Oracle Database 12c. It can be desupported and unavailable in a release after Oracle Database 19c.

との記載があります。

英語版の日付は “December 2017” になっているのに、日本語版は「2017年7月」になっているので日本語版はアップデートされていないようです。近いうちにアップデートされるのかな、と思います。

昨年 18c の発表がありましたが、18c ではなく 19c より後はサポートされないと書いてますね。英語で “after Oracle Database 18c” と書いた場合、「通常」18c は含まれないので “after Oracle Database 18c” で良いと思うのですが、18c まではサポートするということを明確にするため、わざと “after Oracle Database 19c” と記載しているのかもしれません。

こちらは、Oralce の Mike Dietrich さんのブログなのですが、”after Oracle Database 12c Release 2” と書いてあるのに、”12c Release 2” から非CDB構成が使えなくなる(サポートされなくなる)という誤解が結構あったみたいですね。

Non-CDB architecture still exists in Oracle Database 12.2.

Non-CDB architecture still exists in Oracle Database 12.2./ Oracle 18 / Oracle 19. The statement yet about non-CDB disappearing in the near future.

また、非CDBのニーズが思ったより強かったので、18c までサポートすることになったのかもしれません。(推測です。)

まとめ

非CDB(Non-CDB)アーキテクチャは 18c まではサポートされそう。


注: 最新・正確な情報はご自身でご確認ください。

2018-01-30

リンクカード(ブログカード)を作るブックマークレット

リンクの見栄えを良くしたくて、以下のようなリンクカード(ブログカード)を探していました。

  1. 外部サービスに依存しない。(サービスの継続性の問題とパフォーマンスの点から)
  2. OGP (Open Graph Protocol) に対応していないサイトへのリンクはimagedescriptionを無理に表示しない。(関係のない画像やコンテンツが表示されることがあるので)
  3. 仕様が開示されている。(カスタマイズ可能)
  4. StackEdit の Markdownで利用可能。(シンプルなHTMLであること)

なかなか良いものが見つからず、<blockquote> で代用したりしていたのですが、以下のサイトを見つけました。

はてな風のブログカードをブックマークレットから作ってみよう! | 株式会社グランフェアズ

こんにちは、めぐたんです。 ブログを書いていると、参考記事や過去に書いた記事など別ページへのリンクを貼る機会が何かと多くあります。…

 (これはリンク先のブックマークレットで作ったリンクカードです。)

詳しくはリンク先を見ていただければ分かるのですが、外部サービスへの依存がほとんどなく(favicon 表示のみ)、そのため表示も高速です。

画像も og:img のみの表示です。画像がない場合も違和感のないデザインです。

ブックマークレット(JavaScript)ですので使いやすくカスタマイズも可能です。

また、使用しているタグも <div> のみですので、ブックマークレットから出力された HTML をそのまま StackEdit に貼り付けて使うことができます。

カスタマイズ

そのままでも充分使えるのですが、ブックマークレットの JavaScript を少しカスタマイズしてみます。

タイトル

obj.title = $('title').text();

でタイトルを取得しているのですが、document.title 以外に title 要素を使ったものが引っかかることがある(Qiitaなど)ので、

obj.title = document.title;

にしておきます。

テキスト部分

'<p>' + obj.desc + '</p>'

<meta name ="description" content=""> の内容を表示しているのですが、description がないサイトへのリンクだと、undefinedと表示されてしまいます。undefined の場合、自分で説明を上書きすれば良いのですが、以下のようにしてundefined を一旦消すことにします。

'<p>' + (obj.desc || '') + '</p>'

リンク

私の場合、内部リンクは別ウィンドウで開きたくないので、target="_blank" は消しておきます。(テキスト部分と画像部分の2箇所。)

URL

URL とホスト名は canonical 属性の設定があれば、そちらを使うようにします。

obj.url = document.URL;
obj.domain = location.host;

obj.url = $('link[rel=canonical]').attr('href') || document.URL;
var m = obj.url.match(/^https?:\/\/([^/]+)/);
obj.domain = m[1];

に変更します。

favicon

favicon は Google の API を使って取得しているのですが、Google は http / https 間の接続を認めていないので、"http://www.google.com/""//www.google.com/"に変更しておきます。

CSS

CSSも少しだけいじりました。faviconpadding が継承されていたので 0 をセットしたのと、favicon 右のドメインが下付きになっていたので、vertical-align: middle; を追加しました。

.blogCardFooter a img {
  margin-right: 5px;
  padding: 0;
  vertical-align: middle;
}

修正後のブックマークレット

ブログカード


株式会社グランフェアズ様、とても役立つ情報ありがとうございました。

2018-01-19

oo4oからADOへの変換 (7) Adapterクラスの作成(ADOでパラメータの名前によるバインドを可能にする)

移行方針について

oo4o から ADO へ移行する場合、大きく二つの方針が考えられると思います。
  1. 既存コードに手に入れず、oo4o のインタフェースを実装した ADO(ADO.NET)のラッパークラスを作成する。
  2. 全面的に ADO(ADO.NET)に書き換える。
まず、手っ取り早く (1) を検討したくなります。既存コードをそのまま利用できるのですから。しかし、今後もそのコードを継続して使用する場合、廃止された仕様に縛られ続けることにもなります。また、ADO とoo4o の仕様の差は大きく、 oo4o のインタフェースを完全に実装したラッパークラスの作成は困難です。
かといって、(2) の場合は書き換えに要する時間と費用の問題があります。oo4o と ADO の仕様の差は大きく書き換えも単純ではありません。(コンバータの作成を考えましたが、文法が大きく異なるため中途半端なものにならざるを得ません。)

では、どうするのか

移行に関係なく、データプロバイダの API を素のまま使わずデータベースアクセス用の共通クラスや関数を作成して手続きを単純化することは、よくある話ですし、望ましいことです。
その共通クラスの作成の際、oo4o の仕様を織り込むことで移行コストを抑えつつ、メンテナンス性も維持することを考えてみたいと思います。

ADO ラッパー(Adapter)クラスの作成

以下のような方針で ADO のラッパークラスを作成してみます。
  • 単一クラスとする。( Excel や Access のファイルに簡単に織り込めるのが望ましい。)
  • oo4o の OraSession、OraDatabase のインタフェースを極力実装する。
  • OraDynaset は対象外(ADO.Recordset に書き換える。)
  • Oracle のデータ型を使えるようにする。
  • 「名前によるバインド」を ADO でも可能にする。
クラス名は OraAdapter とします。
OraAdapter

使用例

OraAdapter クラスを使って Oracle® Objects for OLE開発者ガイドの OraParametersコレクション Addメソッド の例を書き換えてみます。
Sub Form_Load()

'Declare variables
'Dim OraSession As OraSession
'Dim OraDatabase As OraDatabase
  Dim OraDatabase As OraAdapter

'Create the OraSession Object.
'Set OraSession = CreateObject("OracleInProcServer.XOraSession")
  Set OraDatabase = New OraAdapter

'Create the OraDatabase Object.
'Set OraDatabase = OraSession.OpenDatabase("ExampleDb", "scott/tiger", 0&)
  OraDatabase.OpenDatabase "ExamleDb", "scott/tiger"

'Add EMPNO as an Input/Output parameter and set its initial value.
'OraDatabase.Parameters.Add "EMPNO", 7369, ORAPARM_INPUT
  OraDatabase.AddParameter "EMPNO", 7369, ORAPARM_INPUT
'OraDatabase.Parameters("EMPNO").serverType = ORATYPE_NUMBER
  OraDatabase.SetParameterServerType "EMPNO", ORATYPE_NUMBER
'または、OraDatabase.Parameters("EMPNO").Type = adNumeric

'Add ENAME as an Output parameter and set its initial value.
'OraDatabase.Parameters.Add "ENAME", 0, ORAPARM_OUTPUT
  OraDatabase.AddParameter "ENAME", 0, ORAPARM_OUTPUT
'OraDatabase.Parameters("ENAME").serverType = ORATYPE_VARCHAR2
  OraDatabase.SetParameterServerType "ENAME", ORATYPE_VARCHAR2
'または、OraDatabase.Parameters("ENAME").Type = adVarChar
'OraDatabase.Parameters("ENAME").Size = 255

'Add SAL as an Output parameter and set its initial value.
'OraDatabase.Parameters.Add "SAL", 0, ORAPARM_OUTPUT
  OraDatabase.AddParameter "SAL", 0, ORAPARM_OUTPUT
'OraDatabase.Parameters("SAL").serverType = ORATYPE_NUMBER
  OraDatabase.SetParameterServerType "SAL", ORATYPE_NUMBER
'または、OraDatabase.Parameters("SAL").serverType = adNumeric

'Execute the Stored Procedure Employee.GetEmpName to retrieve ENAME.
' This Stored Procedure can be found in the file ORAEXAMP.SQL.
  OraDatabase.ExecuteSQL ("Begin Employee.GetEmpName (:EMPNO, :ENAME); end;")
'Display the employee number and name.

'Execute the Stored Function Employee.GetSal to retrieve SAL.
' This Stored Function can be found in the file ORAEXAMP.SQL.
  OraDatabase.ExecuteSQL ("declare SAL number(7,2); Begin" & _
           ":SAL:=Employee.GetEmpSal (:EMPNO); end;")

'Display the employee name, number and salary.
  MsgBox "Employee " & OraDatabase.Parameters("ENAME").value & ", #" & _
          OraDatabase.Parameters("EMPNO").value & ",Salary=" & _
          OraDatabase.Parameters("SAL").value

'Remove the Parameters.
'OraDatabase.Parameters.Remove "EMPNO"
'OraDatabase.Parameters.Remove "ENAME"
'OraDatabase.Parameters.Remove "SAL"
  OraDatabase.ClearParameters
End Sub
そのまま書き換えた場合に比べ、大幅に単純化されていることが分かります。

補足

  • データプロバイダに OraOLEDB でなく MSDAORA を指定しても動きます。
  • OraAdapter.ParametersADODB.Parameter のコレクションです。
    したがって、OraDatabase.Parameters("EMPNO").serverType = ORATYPE_NUMBER は、
    OraDatabase.SetParameterServerType "EMPNO", ORATYPE_NUMBER
    ではなく
    OraDatabase.Parameters("EMPNO").Type = adNumeric
    に書き換えることも可能です。
  • CreateOraDynasetOraDynaset ではなく、ADODB.Recordset を返します。
    OraDynasetRecordset の違いについては、以下の記事も参考にしてください。
  • パラメータを変更してOraDynaset.Refresh をしている場合は、
OraAdapter.RefreshParameters
OraDynaset.Requery
に書き換えてください。

2018-01-13

VB.NETとC#からのExcel出力

検索すればあちこちで見つかる VB.Net および C# からの Excel 出力ですが、意外と以下の条件を満たすものがないようですので、サンプルコードを掲載しておきます。

条件

  1. Excel 終了時にきちんと COM の開放が行われ、EXCEL.EXE が終了する。
  2. 高速化(2次元配列を使用)している。
  3. 書式設定が行われている。
  4. 遅延バインディング

参考

Excelファイルを C# と VB.NET で読み込む “正しい” 方法 - Qiita

はじめに “Excel C#” や “Excel VB.NET” でググった新人プログラマが、古い情報や間違った情報で茨の道を選ばずに済むようにと思って書きました。 この記事は、Windows で Visual Studio を使用したデスクトップアプリケーション開発を想定しています。 VB.NET でも作成可能ですが、サンプルコードでは C# 6.0 を使用しています。どちらでもいいなら C# を使いましょう。 C# または VB.NET でExcel…

サンプルコードは、Microsoft.Office.Interop.Excel を使用しています。
(リンク先では推奨されていないのですが、外部DLLを使わずにすみますので)

サンプルコード

仕様

  • DataTable の内容を新規 Book に出力し、そのまま(保存せずに)表示します。
  • String 型は文字列にしています。DateTime型は、”yyyy/mm/dd”の形式にしています。(Date 型と DateTime 型で書式を変えたい場合は文字列化した方が良いと思います。)
  • 罫線と列幅の自動調整まで行っています。
VB.NET
Option Strict Off
Imports Microsoft.VisualBasic
Imports System.Data
    Public Sub ExportExcel(ByVal dt As DataTable)
        Dim xlApp As Object = Nothing
        Dim xlBooks As Object = Nothing
        Dim xlBook As Object = Nothing
        Dim xlSheet As Object = Nothing
        Dim xlCells As Object = Nothing
        Dim xlRange As Object = Nothing
        Dim xlCellStart As Object = Nothing
        Dim xlCellEnd As Object = Nothing

        Try
            xlApp = CreateObject("Excel.Application")
            xlBooks = xlApp.Workbooks           
            xlBook = xlApp.Workbooks.Add
            xlSheet = xlBook.WorkSheets(1)
            xlCells = xlSheet.Cells

            Dim dc As DataColumn
            Dim columnData(dt.Rows.Count, 1) As Object
            Dim row As Integer = 1
            Dim col As Integer = 1

            For col = 1 To dt.Columns.Count
                row = 1
                dc = dt.Columns(col - 1)
                'ヘッダー行の出力
                xlCells(row, col).value = dc.ColumnName
                row = row + 1

                ' 列データを配列に格納
                For i As Integer = 0 To dt.Rows.Count - 1
                    columnData(i, 0) = String.Format(dt.Rows(i)(col - 1))
                Next
                xlCellStart = xlCells(row, col)
                xlCellEnd = xlCells(row + dt.Rows.Count - 1, col)
                xlRange = xlSheet.Range(xlCellStart, xlCellEnd)
                ' Excel書式設定
                Select Case Type.GetTypeCode(dc.DataType)
                    Case TypeCode.String
                        xlRange.NumberFormatLocal = "@"
                    Case TypeCode.DateTime
                        xlRange.NumberFormatLocal = "yyyy/mm/dd"
                        'Case TypeCode.Decimal
                        '    xlRange.NumberFormatLocal = "#,###"
                End Select
                xlRange.value = columnData
            Next

            xlCells.EntireColumn.AutoFit()
            xlRange = xlSheet.UsedRange
            xlRange.Borders.LineStyle = 1   'xlContinuous
            xlApp.Visible = True

        Catch
            xlApp.DisplayAlerts = False
            xlApp.Quit()
            Throw
        Finally
            If xlCellStart IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCellStart)
            If xlCellEnd IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCellEnd)
            If xlRange IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange)
            If xlCells IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCells)
            If xlSheet IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheet)
            If xlBooks IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBooks)
            If xlBook IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook)
            If xlApp IsNot Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)

            GC.Collect()
        End Try
    End Sub
C# (4.0以上)
using System.Data;
        public void ExportExcel(DataTable dt)
        {
            dynamic xlApp = null;
            dynamic xlBooks = null;
            dynamic xlBook = null;
            dynamic xlSheet = null;
            dynamic xlCells = null;
            dynamic xlRange = null;
            dynamic xlCellStart = null;
            dynamic xlCellEnd = null;
            try
            {
                xlApp = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
                xlBooks = xlApp.Workbooks;
                xlBook = xlBooks.Add;
                xlSheet = xlBook.WorkSheets(1);
                xlCells = xlSheet.Cells;

                DataColumn dc;
                object[,] columnData = new object[dt.Rows.Count, 1];
                int row = 1;
                int col = 1;

                for (col = 1; (col <= dt.Columns.Count); col++)
                {
                    row = 1;
                    dc = dt.Columns[(col - 1)];
                    // ヘッダー行の出力
                    xlCells[row, col].value2 = dc.ColumnName;
                    row++;
                    // 列データを配列に格納
                    for (int i = 0; (i <= (dt.Rows.Count - 1)); i++)
                    {
                        columnData[i, 0] = string.Format("{0}",dt.Rows[i][(col - 1)]);
                    }

                    xlCellStart = xlCells[row, col];
                    xlCellEnd = xlCells[(row + (dt.Rows.Count - 1)), col];
                    xlRange = xlSheet.Range(xlCellStart, xlCellEnd);
                    // Excel書式設定
                    switch (Type.GetTypeCode(dc.DataType))
                    {
                        case TypeCode.String:
                            xlRange.NumberFormatLocal = "@";
                            break;
                        case TypeCode.DateTime:
                            xlRange.NumberFormatLocal = "yyyy/mm/dd";
                            break;
                        //case TypeCode.Decimal:
                        //    xlRange.NumberFormatLocal = "#,###";
                        //    break;
                    }
                    xlRange.value2 = columnData;
                }

                xlCells.EntireColumn.AutoFit();
                xlRange = xlSheet.UsedRange;
                xlRange.Borders.LineStyle = 1;  // xlContinuous
                xlApp.Visible = true;
            }
            catch
            {
                xlApp.DisplayAlerts = false;
                xlApp.Quit();
                throw;
            }
            finally
            {
                if (xlCellStart != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCellStart);
                if (xlCellEnd != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCellEnd);
                if (xlRange != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange);
                if (xlCells != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCells);
                if (xlSheet != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheet);
                if (xlBook != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook);
                if (xlBooks != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBooks);
                if (xlApp != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);

                GC.Collect();
            }
        }

C# での注意点

  • 遅延バインディングにするため dynamic 型を使用しているので 4.0 以上が必要です。
  • Microsoft.Csharp の参照設定をしてください。

ネット上のコードで気になったこと

Workbooks の変数格納

Workbooks オブジェクトを変数に格納せず xlSheet = xlBooks.Add.WorkSheets(1); としているものも見受けられました。
c# - How do I properly clean up Excel interop objects? - Stack Overflow
試したところ、VB.NET では変数に格納しなくても EXCEL.EXE は終了しましたが、C# では終了しませんでした。サンプルコードでは VB.NET でも変数に格納するようにしています。

get_Range メソッドとRange プロパティ

Range の 取得に get_Range メソッドを使っている例がありましたが、 get_Range メソッドは MSDN

セルまたはセルの範囲を表す Microsoft.Office.Interop.Excel.Range オブジェクトを取得します。 このメソッドの代わりに Range プロパティを使用してください。

との記載があるのと、c# - Worksheet get_Range throws exception - Stack Overflow
によると、.NET4.0 からエラーになるようです。サンプルコードでは、Range プロパティを使用するようにしました。

Range.ValueRange.Value2

Excel のセルへの書き込みにValue プロパティと Value2 プロパティを使っているものがあります。両者の違いは

Value プロパティとの相違点は、Value2 プロパティでは、通貨型 (Currency) および日付型 (Date) のデータ型を使用しない点だけです。

のようです。
なぜ、二つのコードが出回っているかと言うと、VB.NET ではインテリセンスで Value が出て、C# では Value2 が出るため(Value プロパティにはパラメータがあるが、C# はパラメータ付きプロパティをサポートしていないため)で実質的な違いはないようです。
Parameterized Properties in C# and the mystery of Value2 in Excel – .NET4Office

ReleaseComObjectFinalReleaseComObject

COM の開放に FinalReleaseComObject を使っているものもありましたが、
How to properly release Excel COM objects: C# code examples によると、 FinalReleaseComObject を使うのは冗長のようですので、サンプルコードではReleaseComObject を使っています。

2018-01-09

Why "Don't track my views for this blog." does not work in Blogger

Don't track my views for this blog.
In the Blogger dashboard, when you click Stats -> Overview ->Manage tracking your own pageviews and check Don't track my views for this blog., your pageviews are expected to be excluded from the stats.
But your pageviews might be counted, and the checkbox is unchecked each time you restart the browser.

Causes

When Don't track my views for this blog. is checked, it invokes the following JavaScript setting the Cookie.

var COOKIE_NAME = '_ns';
var COOKIE_SET_VALUE = '2';
document.cookie = COOKIE_NAME + '=' + COOKIE_SET_VALUE;

But it has some issues as follows.

Domain attribute

It doesn’t specify the domain attribute, so current sub domain, blogname.blogspot.com, is set to the Cookie. If you are redirected to a country-specific URL (ccTLD), blogname.blogspot.ccTLD, the cookie is not sent because it has the different domain.(Redirect to the ccTLD has been expired. See Official Blogger Blog: It’s spring cleaning time for Blogger.)

Note: The domain attribute is supposed to be specified to apply the cookie to all sub domains under the domain. If you want the cookie for the specific sub domain like this case, you need not specify the domain attribute. And you can not specify the other domain you are requesting.

Path attribute

It doesn’t specify the path attribute, so the current page path /b is set for the path attribute. As a result, the cookie is sent only when requested pages are under /b/.

Expires attribute

As the expires attribute is also not specified, the cookie is deleted when the browser is closed (aka Session Cookie).

FYI

Workaround

The script modified the above issues is as follows.

document.cookie = "_ns=2; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";

If you are using Chrome, after visiting your blog, start Developer Tools (press F12). Paste this script in Console and run (press Enter).

Chrome Developer Tool
(In Chrome Developer Tool, you can see and edit the Cookies at Application -> Cookies.)

  • You can’t set a cookie to never expire. So I set 03:14:07 UTC on Tuesday, 19 January 2038 for the expires attribute, the latest time avoiding the Year 2038 problem.
    I don’t use max-age attribute because IE11 doesn’t support.

If your access to your blog is redirected to blogname.blogspot.ccTLD and you want to keep Don't track my views for this blog. unchecked (which has no practical sense), execute the above script after visiting blogger’s dashboard as well.


Here is another version, which expires date is in a plain way.

var d = new Date("2038-01-19"); document.cookie = "_ns=2; expires=" + d.toGMTString() + "; path=/";

You can modify "2038-01-19" as you like (not to exceed “2038-01-19”).
If you specify the date in the past, you can delete the cookie.

Setup with Smartphones

You may want not to count the pageviews from smartphones, which browser doesn’t have Developer Tools. In such case, after visiting your blog with your smartphone’s browser, clear the address bar and type javascript: and paste the above script just after it, then press Enter.

And, if you input javascript:document.cookie; in the address bar (the last semicolon is probably not necessary), the current page’s cookie can be shown. If _ns=2 is displayed, the cookie is set properly.