none
VBでは、アクセスでのFirst関数に代わるものがありますか? RRS feed

  • 質問

  • VBでは、アクセスでのFirst関数に代わるものがありますか?
    無いようなのですが、としたら、代用となるようなコードがあれば、教えていただけますか?


    <やりたいこと>
    棚卸資産高を決めるのに、各商品の原価を求めます。

    仕入テーブルの各行には、ItemNo(商品番号),Client_ID(仕入先),Design_ID(デザインID),Metal_ID(金属),Length(寸法)があります。
    各レコード行には、原価(CostUnitPrice)もありますが、この商品番号の原価は、棚卸資産高算出では使用せず、別の方式で決めます。

    そのItemNoの持つ四個の属性(Client_ID,Design_ID,Metal_ID,Length)で、群(クラス)を求めます。
    この群には、複数のItemNoが存在します。で、各ItemNoは固有の原価(CostUnitPrice)を持ちますが、これは使用しません。

    この群(Client_ID,Design_ID,Metal_ID,Length)の中で、
    仕入日(Slip_Invoice_Date)がもっとも新しい日のデータ(CostUnitPrice)を棚卸資産高を求める際の原価とします。
    そのItemNoの原価(CostUnitPrice)でなく、それが所属する群の直近値の原価(CostUnitPrice)を、棚卸資産高を求める際の原価(LastCostUnitPrice)とします。
    仕入日を降順に並べ、その第一行であれば、求める直近値の原価がでます。

    流れとして;
    ItemNo → その所属する群(Client_ID,Design_ID,Metal_ID,Length)→ その群の中でのCostUnitPriceの直近値=LastCostUnitPrice

    <コード>アクセスでは、下記のFirst関数があるので使えますが、VBではそれにあたるものがなく困っています。
        Dim ds as As Nea Dataset
         Dim dt As New DataTable
            ds.Tables.Add(dt)
            Dim sqL As String 
            sqL = "SELECT Client_ID, Metal_ID, Design_ID, Length," _
              & " First(CostUnitPrice) AS LastCostUnitPrice," _
              & " Max(Slip_Invoice_Date) AS Slip_Invoice_Date_MAX" _
              & " FROM ----" _
              & " WHERE Slip_Invoice_Date<'指定日'" _
              & " GROUP BY Client_ID, Metal_ID, Design_ID,Length ORDER BY Max(Slip_Invoice_Date) DESC;"
            adapter.SelectCommand = New SqlClient.SqlCommand(sqL, Con)
            adapter.SelectCommand.CommandType = CommandType.Text
            adapter.Fill(dt)

            'DataRelation作成:親(dt)/ 子(dtTWLG)
            ds.Relations.Add("TEST", New DataColumn() {dt.Columns("Client_ID"), dt.Columns("Design_ID"), dt.Columns("Metal_ID"), dt.Columns("Length")}, _
                                     New DataColumn() {dtTWLG.Columns("Client_ID"), dtTWLG.Columns("Design_ID"), dtTWLG.Columns("Metal_ID"), dtTWLG.Columns("Length")}, False)
            dtTWLG.Columns.Add("LastCostUnitPrice", GetType(Integer), "Isnull(Parent(TEST).LastCostUnitPrice,0)")

    上記、First関数がVBにはないので、これをMaxやMinにしますと、上記Groupの中での最大値や最小値となり、直近値のデータ(CostUnitPrice)にはなりません。
    要するに、仕入日を降順に並べてその第一行のみがほしいのですが、その関数が見当たりません。

    これが出来ないので、VBでは、別コード(For --- Next)を利用していますが、所要時間が20分~25分かかります---データ数12万~15万件。
    上記コードがうまくいくと、5分ほどです---FirstのかわりにMaxを入れて動作---ただ、最大値となり直近値ではないので、目的は果たせていません。
    Groupで選んできたデータの第一行のみのデータ(CostUnitPrice)がほしいのです、第二行目以降は邪魔でこれがあるとDataRelationが張れず目的は果たせません。

    何か、良いコードがあれば、教えてください。


    YKSaila


    • 編集済み yksaila 2015年1月16日 4:18
    2015年1月16日 4:15

回答

  • WITHは共通テーブル式と呼ばれるものです。一時的なテーブルを定義して結果セットを抽出し、それを利用することができます。
    大まかには、

    with 一時テーブルの定義
    as
    (
        一時テーブルにデータを入れるSQL文
    )
    一時テーブルを利用するSQL文

    になります。よって、上で私がご紹介したSQLは、

    with CTE (Client_ID, Metal_ID, Design_ID, Length, 累計仕入数)
    as
    (
       
    select Client_ID, Metal_ID, Design_ID, Length, SUM(仕入数)
       
    from Metal m
       
    group by Client_ID, Metal_ID, Design_ID, Length
    )
    select
        C
    .*,
       
    (select top(1) CostUnitPrice from Metal t
               
    where t.Client_ID = C.Client_ID and
                      t
    .Metal_ID = C.Metal_ID and
                      t
    .Design_ID = C.Design_ID and
                      t
    .Length = C.Length
                
    order by Slip_Invoice_Date desc
        
    ) as LastCostUnitPrice

    from CTE C

    は、

        select Client_ID, Metal_ID, Design_ID, Length, SUM(仕入数)
       
    from Metal m
       
    group by Client_ID, Metal_ID, Design_ID, Length

    という一時テーブルをまず作成しています。この一時テーブルは、Client_ID, Metal_ID, Design_ID, Lengthでグループ化していますので、Client_ID, Metal_ID, Design_ID, Lengthが異なる全種類の一覧を抽出していることになります。
    この一覧の各レコードについて、最新の仕入単価を続くSQLで求めています。

    質問のsql文だと、どのように書いたら良いのでしょうか?

    あと、「合計計算(Sum(----))をしています」は無視してください、これは別のsql文で実行しますので。

    Group By句は、外せません。

    ご質問で掲載されたSQLを自分なりに解釈して共通テーブル式のSQLをご紹介したのですが、上記の説明と食い違っている点があればお知らせください。

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク yksaila 2015年2月21日 22:45
    2015年2月18日 2:48
    モデレータ
  • 複雑にしてしまいましたね。前述のコードほぼそのままで動作するはずなのですが。

    sqLLCUP = "With ORS AS" _
            & " (SELECT ROW_NUMBER() OVER(PARTITION BY Client_ID, Metal_ID, Design_ID, Length ORDER BY Slip_Invoice_Date DESC) AS RowNo," _
            & " Client_ID, Metal_ID, Design_ID, Length, CostUnitPrice AS LastCostUnitPrice " _
            & " FROM T_ItemStock Where Slip_Invoice_Date<'" & CLSDate_Nxt & "')" _
            & " SELECT * FROM ORS Where RowNo = 1"
    WithはSELECTで記述したクエリーの結果に名前を付けていると考えると理解しやすいかもです。

    Withを使わないで同等のSQLを書くことはできます。


    http://systemartlaboratory.com/

    • 回答としてマーク yksaila 2015年2月23日 9:06
    2015年2月22日 6:58

すべての返信

  • こんにちは。

    VBというよりSQLServerでしょうか。
    TOPで対応できる範囲かわかりませんが・・・。

    http://msdn.microsoft.com/ja-jp/library/ms189463.aspx

    2015年1月16日 4:44
    モデレータ
  • データベースから取得した値がすでにデータテーブルにセットされた状態でその先頭や末尾のデータを取得したいのであれば次のようにしてはいかがでしょうか?

    先頭:dt.Rows(0)

    末尾:dt.Rows(dt.Rows.Count-1)

    2015年1月18日 9:55
  • yksaila さま よろしく。

    ExecuteScalar もしくは 単一値を返すストアド を別途作れば良いのではないでしょうか。

    SqlCommand.ExecuteScalarメソッド
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand.executescalar%28v=vs.110%29.aspx

    方法 : 単一の値を返すストアド プロシージャを実行する
    http://msdn.microsoft.com/ja-jp/library/37hwc7kt.aspx
    2015年1月20日 7:31
  • SqlCommandを使用されていますから、接続先はSQL Serverでしょうか?

    とりあえず実現されたいことを行うには、グループ化する必要はなく、単純にソートして1行目のみを取り出せばいいんじゃないかと思います。その方がパフォーマンス的にも有利であるように思います。実現にはサブクエリを使います。以下、SQL Serverの例ですが、サブクエリはAccessでも使用できます。以下に掲載するコードは未検証ですので、ケアレスミスがあるかもしれません。また、SQL文はSQLインジェクションを防ぐために、パラメーターを使っています。@param1など、@で始まるものです。

    select Client_ID, Metal_ID, Design_ID, Length,
         
         (select top(1) CostUnitPrice from ----
                 where  Client_ID = @param1 and
                        Metal_ID = @param2 and
                        Design_ID = @param3 and
                        Length = @param4
                 order by Slip_Invoice_Date desc
         ) as LastCostUnitPrice,
         
         (select top(1) Slip_Invoice_Date from ----
                 where  Client_ID = @param1 and
                        Metal_ID = @param2 and
                        Design_ID = @param3 and
                        Length = @param4
                 order by Slip_Invoice_Date desc
         ) as Slip_Invoice_Date_MAX,
         
         from ----
    


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答の候補に設定 星 睦美 2015年2月4日 8:01
    2015年1月21日 5:53
    モデレータ
  • trapemiya様へ

    ありがとうございます。

    出張中で、読むことができませんでした。

    ご教示の内容でやってみようと思います。

    ただ、また出張が入りまして、挑戦は今月末か二月初めになります。

    結果は、その時ご報告いたします。

    YKsaila

    2015年1月22日 0:55
  • 1月末までは、出張中でした。
    帰宅後の自宅環境内のサーバー(SQL2005)が故障し、数日前(5日)にやっと復旧しました。

    で、いろいろと、やってみました。
    質問のSQL文はGropup BY 句を使用して、12個の合計計算(Sum(----))をしています。

    これは必須なので、Gropup BY句無しでのSQL文は考えられません。

    Max・Min・Avg関数は、正常にこのSQL文で使用できます。

    ただ、サブクエリでのTop(1)は、このSQL文では使用できないようです。 
    エラーは発生しませんが、表示データは正しくありません。
    全体で選ぶようです(すべて、同一の値です)。
    Top(1)のサブクエリの中に、さらにGropup BY を入れるとエラー表示します。

    Gropup BYを使用したSQL文でのサブクエリでは、SELECT Top(1) ---- は、使えないのでしょうか?
    というか、Gropup BYを使用したSQL文では、サブクエリは使用できないとか?


    私の採った別の方法:
    そこで、質問のSQL文内でのTop(1)、First(--)を外し、別のdatatable(dt)を作成し、この中でTop(1)を使ってみました。
    ところが、下記コード(A)の方が、(B)より所要時間が長いのです。 10~15%長いです。
    ということは、サブクエリ<SELECT Top(1)--> の使用が意味を成しません。
    このような状況では、アクセスで使用できていたFirst関数は、VBでは諦めるしかないのでしょうか?

    YKsaila

    参考コード:
     NZint--自作関数(Cintに追加したもの、DBNullの時は、0とする関数)
    (A)サブクエリ<SELECT Top(1)--> を使用
         For i = 0 To dt.Rows.Count - 1
                ITN2 = NZint(dt.Rows(i).Item("ItemNo").ToString)
                CLID = Nzint(dt.Rows(i).Item("Client_ID").ToString)
                DID = NZint(dt.Rows(i).Item("Design_ID").ToString)
                LN2 = NZdec(dt.Rows(i).Item("Length").ToString)
                MID = NZint(dt.Rows(i).Item("Metal_ID").ToString)

                Dim sqlTWLG As String
                sqlTWLG = "UPDATE T_WK_StockTaking_Log SET"
                sqlTWLG &= " LastCostUnitPrice="
                'サブクエリ
                sqlTWLG &= " (SELECT Top(1) T_ItemStock.CostUnitPrice FROM T_ItemStock"
                sqlTWLG &= " Where (T_ItemStock.Slip_Invoice_Date<'" & CLSDate_Nxt & "' or T_ItemStock.Slip_Invoice_Date is Null)"
                sqlTWLG &= " and T_ItemStock.Client_ID=" & CLID & " and T_ItemStock.Design_ID=" & DID & " and T_ItemStock.Length=" & LN2 & ""
                sqlTWLG &= " and T_ItemStock.Metal_ID=" & MID & " Order By T_ItemStock.Slip_Invoice_Date desc)"

                sqlTWLG &= " WHERE T_WK_StockTaking_Log.CloseControl_ID=" & CLCTR_ID_New & " and ItemNo=" & ITN2 & ""
                Dim sqlLCP As New System.Data.SqlClient.SqlCommand(sqlTWLG, Con)
                sqlLCP.ExecuteNonQuery()

                If i = dt.Rows.Count - 1 Then
                    sqlLCP.Dispose()
                End If
            Next

    (B)サブクエリ<SELECT Top(1)--> を使用しない方法
        For i = 0 To dtTWLG.Rows.Count - 1
                ITN2 = NZint(dt.Rows(i).Item("ItemNo").ToString)
                CLID = NZint(dt.Rows(i).Item("Client_ID").ToString)
                DID = NZint(dt.Rows(i).Item("Design_ID").ToString)
                LN2 = NZdec(dt.Rows(i).Item("Length").ToString)
                MID = NZint(dt.Rows(i).Item("Metal_ID").ToString)
              
                Dim ds5 = New DataSet
                Dim dt5 As New DataTable  '直近のCostUnitPrice決定  
                adapter.SelectCommand = SQL
                Dim sb5 As New System.Text.StringBuilder
                sb5.Append("SELECT T_ItemStock.CostUnitPrice,T_ItemStock.ValuePrice")
                sb5.Append(" FROM T_ItemStock") ' Inner Join T_ItemTrade On T_ItemStock.ItemNo=T_ItemTrade.ItemNo")
                sb5.Append(" Where (T_ItemStock.Slip_Invoice_Date<'" & CLSDate_Nxt & "' or T_ItemStock.Slip_Invoice_Date is Null)") 
                sb5.Append(" and T_ItemStock.Client_ID=" & CLID & " and T_ItemStock.Design_ID=" & DID & " and T_ItemStock.Length=" & LN2 & "")
                sb5.Append(" and T_ItemStock.Metal_ID=" & MID & " Order By T_ItemStock.Slip_Invoice_Date Desc")  'Desc--重要 直近日のデータが必要。 
                SQL.CommandText = sb5.ToString()
                adapter.SelectCommand = SQL
                adapter.Fill(ds5)
                dt5 = ds5.Tables(0)

                If dt5.Rows.Count > 0 Then
                   LCUP = Cint(dt5.Rows(0).Item("CostUnitPrice").ToString)  'desc--重要 直近日のデータが必要:第一行<Rows(0)>で選択。
                Else
                  LCUP = 0
                End If
                               
                    Dim sqlTWLG As String
                    sqlTWLG = "UPDATE T_WK_StockTaking_Log SET "
                    sqlTWLG &= "LastCostUnitPrice= @LastCostUnitPrice "
                    sqlTWLG &= "WHERE T_WK_StockTaking_Log.CloseControl_ID=" & CLCTR_ID_New & " and ItemNo=" & ITN2 & ""
                    Dim sqlLCP As New System.Data.SqlClient.SqlCommand(sqlTWLG, Con)
                    sqlLCP.Parameters.AddWithValue("@LastCostUnitPrice", LCUP)
                    sqlLCP.ExecuteNonQuery()

                    If i = dtTWLG.Rows.Count - 1 Then
                        dt5.Dispose()
                        sqlLCP.Dispose()
                    End If
            Next

    2015年2月9日 11:28
  • このスレッドを見逃していました。すみません、遅くなりました。

    なんとなく実現されたいことが見えたような気がします。以下のようなSQLでしょうか?
    sumする項目がわからなかったので、適当に「仕入数」という列を追加しています。

    with CTE (Client_ID, Metal_ID, Design_ID, Length, 累計仕入数)
    as
    (
        select Client_ID, Metal_ID, Design_ID, Length, SUM(仕入数)
        from Metal m
        group by Client_ID, Metal_ID, Design_ID, Length
    )
    select
        C.*,
        (select top(1) CostUnitPrice from Metal t
                where t.Client_ID = C.Client_ID and
                      t.Metal_ID = C.Metal_ID and
                      t.Design_ID = C.Design_ID and
                      t.Length = C.Length
                 order by Slip_Invoice_Date desc
         ) as LastCostUnitPrice
    
    from CTE C


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年2月12日 5:05
    モデレータ
  • 最終仕入単価法で在庫評価すると理解しました。
    分析関数を使うと可能ではないかと思います。

    WITH OrderedSlips AS
    (
      SELECT ROW_NUMBER() OVER(PARTITION BY Client_ID, Metal_ID, Design_ID, Length ORDER BY Slip_Invoice_Date DESC) AS RowNo,
      Client_ID, Metal_ID, Design_ID, Length, CostUnitPrice, Slip_Invoice_Date
      FROM Slips
      WHERE Slip_Invoice_Date<'2015-2-28'
    )
    SELECT * FROM OrderedSlips
    WHERE RowNo = 1
    

    テーブル名は適宜変えてください。
    必要でしたら最終行に適宜ORDER BYを追加してください。
    同じ日に同じクラスの仕入が2件あるとどちらを1行目にするか不定になりますが問題ないのですね?

    参考:
    https://msdn.microsoft.com/ja-jp/library/ms186734.aspx

    http://systemartlaboratory.com/

    2015年2月13日 14:25
  • trapemiya様

    ご回答、ありがとうございます。

    CTE/Metal m/ C./ t. の意味について、もう少し教えていただけますか。

    質問のsql文だと、どのように書いたら良いのでしょうか?

    あと、「合計計算(Sum(----))をしています」は無視してください、これは別のsql文で実行しますので。

    Group By句は、外せません。

    YKsaila

    2015年2月17日 15:06
  • 三輪の牛様

    ご回答ありがとうございます。

    初めて見るコードなので、手こずっています。 Group ByにあたるのがPARTITION BY なのでしょうか?

    あてはめてみたのですが、うまくいきません。

    質問のsql文だと、具体的にはどうなりますか? 私のあてはめ方が良くないのかも?

    参考資料にも当たりましたが、うまくいきません。

    YKsaila

    2015年2月17日 15:10
  • WITHは共通テーブル式と呼ばれるものです。一時的なテーブルを定義して結果セットを抽出し、それを利用することができます。
    大まかには、

    with 一時テーブルの定義
    as
    (
        一時テーブルにデータを入れるSQL文
    )
    一時テーブルを利用するSQL文

    になります。よって、上で私がご紹介したSQLは、

    with CTE (Client_ID, Metal_ID, Design_ID, Length, 累計仕入数)
    as
    (
       
    select Client_ID, Metal_ID, Design_ID, Length, SUM(仕入数)
       
    from Metal m
       
    group by Client_ID, Metal_ID, Design_ID, Length
    )
    select
        C
    .*,
       
    (select top(1) CostUnitPrice from Metal t
               
    where t.Client_ID = C.Client_ID and
                      t
    .Metal_ID = C.Metal_ID and
                      t
    .Design_ID = C.Design_ID and
                      t
    .Length = C.Length
                
    order by Slip_Invoice_Date desc
        
    ) as LastCostUnitPrice

    from CTE C

    は、

        select Client_ID, Metal_ID, Design_ID, Length, SUM(仕入数)
       
    from Metal m
       
    group by Client_ID, Metal_ID, Design_ID, Length

    という一時テーブルをまず作成しています。この一時テーブルは、Client_ID, Metal_ID, Design_ID, Lengthでグループ化していますので、Client_ID, Metal_ID, Design_ID, Lengthが異なる全種類の一覧を抽出していることになります。
    この一覧の各レコードについて、最新の仕入単価を続くSQLで求めています。

    質問のsql文だと、どのように書いたら良いのでしょうか?

    あと、「合計計算(Sum(----))をしています」は無視してください、これは別のsql文で実行しますので。

    Group By句は、外せません。

    ご質問で掲載されたSQLを自分なりに解釈して共通テーブル式のSQLをご紹介したのですが、上記の説明と食い違っている点があればお知らせください。

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク yksaila 2015年2月21日 22:45
    2015年2月18日 2:48
    モデレータ
  • Group ByにあたるのがPARTITION BYです。
    該当SQL文のやっていることは、クラス毎に日付の降順で連番を振って連番の1のみを抽出することで
    各クラスの最終仕入を抽出しています。


    http://systemartlaboratory.com/

    2015年2月18日 4:19
  • trapemiya様

    出来たようです。

    劇的な速さになりました!
    ありがとうございました。

    ただ、完全な理解のためにいくつか、質問があります。
    下記に記しますので、よろしくお願いします。

    YKsaila

    <完成コード>
    sqLLCUP = "With CTE (Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date)" _
            & " as (select Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date" _
            & "  From T_ItemStock ① Group By Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date" _
            & " Having Max(Slip_Invoice_Date)<'" & CLSDate_Nxt & "')" _  
        'ここまで、共通テーブル作成 Max(Slip_Invoice_Date)---Max無しより動作が速い。
        
            & " SELECT T_ItemStock.Client_ID, T_ItemStock.Metal_ID, T_ItemStock.Design_ID, T_ItemStock.Length," _ 

        ’(**)
            & " (select top(1) CostUnitPrice from T_ItemStock CTE where CTE.Client_ID = T_ItemStock.Client_ID and" _   
            & " CTE.Metal_ID = T_ItemStock.Metal_ID and CTE.Design_ID = T_ItemStock.Design_ID and" _
            & " CTE.Length = T_ItemStock.Length order by CTE.Slip_Invoice_Date DESC) AS LastCostUnitPrice" _

            & " FROM T_ItemStock ② where Slip_Invoice_Date<'" & CLSDate_Nxt & "' Group By Client_ID, Metal_ID, Design_ID, Length "
       
     
    質問(確認?)です:
    ①②には、英文字なし----入れても入れなくても変化無し。正常動作します。
    なので入れませんでした---これで良いのでしょうか?


    '(**)で始まるサブクエリ内の<CTE>について:
    文字を変えてもOK(T,Pでも可)---正常動作します。

    from T_ItemStock CTE の意味について:  --- 順序の逆は、不可! エラーコメントが出ます。
    共通テーブル(CTE)とT_ItemStockの二個のテーブルのデータバインドを実施するので、Fromの後に二個のテーブル名を置く。
    T_ItemStockの後に来るのは共通テーブル -- 命名は自由。 ただし、その後のバインドにはその文字を使用のこと。

    こういう理解でよいですか?


    ついでに、ご報告:棚卸残高計算のための資料作成コードでした。
    最終原価法採用(ただし、商品そのものでなく、それが属するグループで決定される原価です。)

    このコードで(①②なし)、順調に動いています、表示内容も正しいようです。
    大量のデータ処理で当初は24分くらいかかっていましたが、現在は10分強で済んでいます。
    しかも、この10分は別処理のコードで無関係、これはやむを得ないと考えています(状況次第で、省略可。)。

    この資料作成コード関係だけでみると、14分だったのが、上記のコード利用ですと25~35秒前後になりました!
    (作成したdatatableから、DataRelation → SqlBulkCopy利用でサーバーテーブル(T_WK_StockTaking_Log)に一括追加)

    2015年2月21日 22:45
  • 三輪の牛様

    下記コードで、やってみました。

    sqLLCUP = "With ORS AS" _
            & " (SELECT ROW_NUMBER() OVER(PARTITION BY Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date ORDER BY Slip_Invoice_Date DESC) AS RowNo," _
            & " Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date" _
            & " FROM T_ItemStock Where Slip_Invoice_Date<'" & CLSDate_Nxt & "')" _
            & " SELECT T_ItemStock.Client_ID, T_ItemStock.Metal_ID, T_ItemStock.Design_ID, T_ItemStock.Length," _
            & " (select CostUnitPrice from T_ItemStock ORS where ORS.RowNo = 1 and ORS.Client_ID = T_ItemStock.Client_ID and" _
            & " ORS.Metal_ID = T_ItemStock.Metal_ID and ORS.Design_ID = T_ItemStock.Design_ID and" _
            & " ORS.Length = T_ItemStock.Length) AS LastCostUnitPrice" _
            & " FROM T_ItemStock Where Slip_Invoice_Date<'" & CLSDate_Nxt & "'Group By Client_ID, Metal_ID, Design_ID, Length "


    「列名’RowNo’が無効です。」、とエラーコメントが出ます。

    で、With ORS の後に、(Client_ID, Metal_ID, Design_ID, Length,Slip_Invoice_Date)を入れると、
    「’ORS’の後には、列リストより多くの列が指定されています。」、とエラーコメントが出ます。

    どこがいけないのでしょうか。よろしく、お願いします。

    YKsaila

    2015年2月22日 0:18
  • 複雑にしてしまいましたね。前述のコードほぼそのままで動作するはずなのですが。

    sqLLCUP = "With ORS AS" _
            & " (SELECT ROW_NUMBER() OVER(PARTITION BY Client_ID, Metal_ID, Design_ID, Length ORDER BY Slip_Invoice_Date DESC) AS RowNo," _
            & " Client_ID, Metal_ID, Design_ID, Length, CostUnitPrice AS LastCostUnitPrice " _
            & " FROM T_ItemStock Where Slip_Invoice_Date<'" & CLSDate_Nxt & "')" _
            & " SELECT * FROM ORS Where RowNo = 1"
    WithはSELECTで記述したクエリーの結果に名前を付けていると考えると理解しやすいかもです。

    Withを使わないで同等のSQLを書くことはできます。


    http://systemartlaboratory.com/

    • 回答としてマーク yksaila 2015年2月23日 9:06
    2015年2月22日 6:58
  • 劇的な速さになりました!
    ありがとうございました。

    SQLに長けていると、アプリケーションのコーディングが楽になったり、速度的に有利になったりします。データベースを扱うアプリケーションの開発において、SQLのスキルを上げることは重要です。

    質問(確認?)です:
    ①②には、英文字なし----入れても入れなくても変化無し。正常動作します。
    なので入れませんでした---これで良いのでしょうか?

    説明のために、「商品マスター」というテーブルがあるとしましょう。このテーブルは、商品ID、商品名、金額、登録日という4つの列を持っているとします。

    select 商品ID from 商品マスター

    基本的なSQLですので説明は要らないと思います。
    さて、列名にはテーブル名を修飾として付けることができます。

    select 商品マスター.商品ID from 商品マスター

       (参考)

       オブジェクト名としての識別子の使用
       https://technet.microsoft.com/ja-jp/library/ms187879(v=sql.105).aspx

       の、「識別子を使用した列の参照」をご覧下さい。


    実際、最初の例のように商品IDだけでも動作するのは、商品マスターと修飾しなくても、商品マスターというテーブルしかないので、修飾を省略できるからです。

    また、テーブルには別名を付けることができます。

    select 商品ID from 商品マスター as T

    この場合、テーブル名で列を修飾すると、

    select T.商品ID from 商品マスター as T

    となります。ここで、asは省略できます。よって、

    select T.商品ID from 商品マスター T

    と書けます。もちろん、

    select 商品ID from 商品マスター T

    でも、OKです。
    別名(エイリアス)を付けるのは、複数のテーブルが出てきたときに、どのテーブルの列かを明示しなければならない時に便利です。
    例を示します。ここでもう一つ、「仕入テーブル」を用意します。このテーブルは、商品ID、仕入れ日、登録日の3つの列を持っているとします。

    select t1.商品ID, t1.商品名, t2.登録日
    from 商品マスター as t1
    inner join 仕入テーブル as t2 on t1.商品ID = t2.商品ID

    asは省略できますから、

    select t1.商品ID, t1.商品名, t2.登録日
    from 商品マスター t1
    inner join 仕入テーブル t2 on t1.商品ID = t2.商品ID

    となります。
    ここで、商品名はテーブル名の修飾が無くても問題ありません。商品名があるのは商品マスターにしかないからです。
    一方、商品IDと登録日はテーブル名の修飾を外せません。これらの列はいずれも商品マスターにも仕入テーブルにもあり、どちらのテーブルの列を表示するのか明示する必要があるからです。よって、以下のように書けます。

    select t1.商品ID, 商品名, t2.登録日
    from 商品マスター t1
     inner join 仕入テーブル t2 on t1.商品ID = t2.商品ID

    以上の説明で別名を理解いただければ、それがご質問の答えになっていると思います。。

    '(**)で始まるサブクエリ内の<CTE>について:
    文字を変えてもOK(T,Pでも可)---正常動作します。

    共通テーブル式(CTE)は一般的に以下の形をしています。

    with 一時テーブル名(一時テーブルの列の定義)
    as
    (
        一時テーブルにデータを注入するSQL文
    )
    一時テーブルを利用して処理を行うSQL文

    ここで、一時テーブル名に付ける名前は任意です。その付けた名前を「一時テーブルを利用して処理を行うSQL文」で使用することができます。また、

    with T(・・・・・・・)
    as
    (
        ・・・・・・・・・・・・
    )
    select T.列A from Aテーブル T

    とした場合、一時テーブルの別名であるTと、Aテーブルの別名Tは同じ名前ですが、別物になります。
    T.列AはAテーブルの列になります。

    #ご質問の意味を取り違えているところがあるかもしれません。その場合は、忌憚なくご指摘下さい。

    #ちなみに、

    select * from Aテーブル, T

    と、カンマが入ると、

    select * from Aテーブル cross join T

    と同じことになり、デカルト積が生成されます。つまり、両方のテーブルのレコードの全ての組み合わせが生成されます。

    #少し難しい話になるかもしれませんが、共通テーブル式(CTE)は再帰処理を行うことができます。今回のご質問では再帰ではなく、単に一時テーブルを作成してそれを利用しているだけというシンプルな方法でした。三輪の牛さんの「Withを使わないで同等のSQLを書くことはできます。」を少し補足すると、共通テーブル式(CTE)は単に普通のSQLの書き方の置き換えだけではないということにご注意下さい。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年2月23日 0:46
    モデレータ
  •         & " (select CostUnitPrice from T_ItemStock ORS where ORS.RowNo = 1 and

    ここですね。

    ※T_ItemStockテーブルに対して「ORS」という別名を付けています。T_ItemStockテーブルにはRowNo列は無いと言われています。

    2015年2月23日 4:10
  • 三綸の牛様

    正常動作しました。

    おっしゃる通り、複雑にしてしまったようです。 というか、その時はまだ、コードが十分に<理解できていなかった>からでしょう。

    ご教示のコードを見て、意味が理解できました。 まだ、完璧には、ほど遠いですけど。 

    少しずつ、理解できているようです。 

    SQL文は難しいものですね、コードによっては10数分かかるもの(初歩的コード)が、20~30秒で終了するなんて !!

    teapemiyaさんがおっしゃるように、SQL文のレベルを上げることはすごく重要なことなんですね。

    これからも、勉強は続くようです。

    ありがとうございました。

    YKsaila

    2015年2月23日 9:16
  • trapemiya様へ

    丁寧な解説、ありがとうございます。 勉強になります。

    識別子のことは理解しています、ただ癖として不要なところにもつける癖が、以前からあります(分かっていて)。

    不要なときは書かない方が良いかもしれませんね?--- 動作速度には影響ないでしょうし。 不要なときは、ない方が見やすい?

    後半についても、助かりました。 今、一読しただけですので、あとでじっくり読んで理解したいと思います。

    それに”再帰処理”ですね、名前だけはネットで検索し知った次第ですが、まだ無知の段階です。

    近いうちに勉強すべきでしょうね。

    今回は、共通(一時)テーブルを知り、一つ技術が増えました。

    最近、datarelationやSQL BulkCopy も学ぶことができ、大いに(客観的には、少しですが。 私にとっては、大きくです)進歩できました。

    当初は所用時間の長さについては、あきらめかけたのですが、やはり ”何事も” あきらめずに頑張ることが大事ですね。

    ありがとうございました。

    YKsaila

    2015年2月23日 9:39
  • ご報告ありがとうございました。
    最初のご質問に書かれている SQL 文は実際に Access で動いているものではないと考えて良いでしょうか。ORDER BY の記述がおかしい以外の点として、
    Access の First 関数は ORDER BY とは無関係に値を返しますので、目的に沿った動作はしません。
    参考:http://support.microsoft.com/kb/208190/ja

    http://systemartlaboratory.com/

    2015年2月24日 3:45
  • >質問に書かれている SQL 文は実際に Access で動いているものではないと考えて良いでしょうか

    はい、その通りです。 Access ソフト内のコードを参考に、こうかなあ? と考えて書いたものでした。

    (ただ、Access ソフト内に、First 関数は使用されています。)

    >Access の First 関数は ORDER BY とは無関係に値を返しますので、目的に沿った動作はしません。

    はい、最近気づきました。 

    幾通りも実験をやっていると、合わない例を発見し(発見までが手数でした)、ORDER BY とは無関係かもしれないと思い始めたところでした。

    気づいたのは、10日くらい前です。 First 関数のFirstは違う基準で動いているように思いました。 つまり、ORDER BY には従わない別基準でのFirstなのかもしれないと。

    >ORDER BY Max(Slip_Invoice_Date) Desc

    このコードですよね? 私も変だと思います。

    ただ、Accessでのクエリ実験だと、Max(最大)をつけないとうまくいかなかったのです(Group by との連携で)。 

    Max(最大)を外すと望みの結果が出なかったのですが? 私のやり方が間違っていたのかもしれませんが。

    もちろん、VBの完成コードでは、Maxは出現しません。

    御教示、ありがとうございました。

    今後も、よろしくおねがいします。

    追加コメント:

    その後、ご紹介のMicrosoftサポート記事を読みました。 First 関数について、です。

    追加書き込み:2014/02/27

    First 関数が使われているのは、私が使用しているAccess ソフトでは、どうやら一時テーブルみたいでした。

    YKsaila




    • 編集済み yksaila 2015年2月27日 4:59
    2015年2月26日 14:09
  • >ORDER BY Max(Slip_Invoice_Date) Desc

    このコードですよね? 私も変だと思います。

    ただ、Accessでのクエリ実験だと、Max(最大)をつけないとうまくいかなかったのです(Group by との連携で)。 

    このコード自体は特に変ではありません。

    Group by句に含まれない項目は、MaxやMin等の集計関数を利用してselectできることはご存知だと思います。この集計関数は、本来の集計という目的以外に、Group by句に含まれない項目をSQL文中で使う際にも用いられます。
    例えば、

    社員テーブル
    --------------------------
    社員ID    氏名
    1           山田太郎
    2           佐藤一郎
    3           山田花子

    支払金額テーブル
    --------------------------
    社員ID    金額
    1          1000
    2           2000
    2           1500
    3          1200
    1           2300
    1           1700

    という、2つのテーブルがある時に、

    select SUM(金額) 金額, 氏名 from 社員テーブル t
    inner join 支払金額テーブル s on s.社員ID = t.社員ID
    group by t.社員ID
    order by 氏名

    は、氏名がgroup byに含まれていませんからエラーになりますが、以下のように氏名にmax関数を使うと成功します。

    select SUM(金額) 金額, max(氏名) 氏名 from 社員テーブル t
    inner join 支払金額テーブル s on s.社員ID = t.社員ID
    group by t.社員ID
    order by max(氏名)

    この場合の氏名は全て同じ値なのでMax関数を使用することに意味はありませんが、group byに含まれない項目をSQL文中で使うためにMax関数を使用しています。この理由から、Max関数の代わりにMin関数も使うことができます。



    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年2月27日 5:05
    モデレータ
  • SQL Server 対 Access の話ですよね?

    表題を訂正(VB ⇒ SQL Server)していただけないでしょうか?

    2015年2月27日 5:13
  • 最終仕入を先頭行に置くためには ORDER BY Slip_Invoice_Date DESC を指定したいのですが、そもそも ORDER BY はグループ化した後に並べ替えるものなので最終仕入を先頭行に置くようには動作しません。Max(Slip_Invoice_Date) Desc でうまくいっている理由はわかりません。
    また、このことから、Microsoftサポート記事を見るまでもなく、 First 関数は ORDER BY と無関係に働くことがわかります。
    現行使っている First は一時表ということなので「レコードがテーブルに入力されたときの順序に基づいて、削除されていない先頭または最後のレコードを返します。 」との仕様?に従った結果、目的に合っていたのかもわかりませんね。
    私が First を使うのはグループ内のすべての行の該当列が同じ値であることがわかっている場合だけです。ですので Last は使っていないです。また、過去に行った実験では最適化によっても First が返す値が変わりました。

    http://systemartlaboratory.com/

    2015年2月27日 23:19
  • trapemiya様へ

    御教示、ありがとうございます。 しばらく、ここにアクセスしていませんでした。

    良く理解できました。 なるほど、そうだったのですね。 勉強になりました。

    ORDER BY Max(Slip_Invoice_Date) Desc句を使用しないとうまくいかないので、使用していました。

    なぜかは、本人は良く分かっていなかったのです、うまく実行させるために入れていました。

    >この場合の氏名は全て同じ値なのでMax関数を使用することに意味はありませんが、group byに含まれない項目をSQL文中で使うため>にMax関数を使用しています。この理由から、Max関数の代わりにMin関数も使うことができます。

    ORDER BY Max(Slip_Invoice_Date) も、同じことですね。

    YKsaila

    2015年4月7日 1:26
  • 三輪の牛様へ

    > First 関数は ORDER BY と無関係に働くことがわかります

    そのようですね、経験が少ないとそう思ってしまいます。 気づくまでが大変で時間がかかりました。

    >現行使っている First は一時表ということなので「レコードがテーブルに入力されたときの順序に基づいて、削除されていない先頭または最>後のレコードを返します。 」との仕様?に従った結果、目的に合っていたのかもわかりませんね

    実験結果をみると、どうやらそのような気がします。 でも、本当のところは、良くわかりません。

    実験によっては、違う結果も出たことがあるような? だいぶ前ですので、忘れましたが。

    アクセス版のソフト(自作ソフトではないです)で本当にうまくいっているかは、いまだに少し疑問・不安があります。

    この種の目的で、First関数を使うのはアクセスでも避けるべきなのかも?

    YKsaila

    2015年4月7日 1:40