Viết lại hàm NTILE của Oracle để chạy query SQL trên Excel

Liên hệ QC

Đào Duy Thức

Thành viên mới
Tham gia
16/12/16
Bài viết
6
Được thích
1
Xin chào mọi người,

Mình hiện đang gặp một vấn đề khó khăn mong mọi người giúp đỡ. Mình đang chuyển đổi từ cách xử lý dữ liệu truyền thống sang cách mới, và đang bế tắc vì gặp lỗi khi tạo lại hàm NTILE của Oracle sang script SQL chạy trên Excel, mặc dù mình đã thành công khi viết lại hàm NTILE chạy được trên Oracle PL/SQL Developer.

Đây là cách truyền thống:
  • Script SQL được lưu trên sheet Excel.
  • Mình viết macro kết nối với cơ sở dữ liệu Oracle, và open recordset với script SQL nói trên, rồi copy recordset đó vào một sheet khác.
Đây là cách mới:
  • Script SQL được lưu trên sheet Excel.
  • Một bảng trong cơ sở dữ liệu Oracle được lưu thành file csv trên ổ cứng.
  • Mình viết macro kết nối với file csv (nhưng không mở nó lên) bằng Microsoft.Jet.OLEDB.4.0, và open recordset với script SQL nói trên, rồi copy recordset đó vào một sheet khác.

Với cách mới, script SQL sẽ được chỉnh lại một chút vì một số hàm và câu lệnh trong Oracle không tồn tại trong SQL trên Excel (ví dụ: NTILE, REMAINDER, TRUNC, CASE). NTILE thì mình viết lại; REMAINDER thay bằng MOD; TRUNC thay bằng INT; CASE thay bằng IIF.

Dưới đây là script SQL của:
  1. Tạo bảng trong Oracle
  2. Kết quả NTILE mong muốn
  3. Hàm NTILE được viết lại chạy trên Oracle (bước trung gian)
  4. Hàm NTILE được viết lại chạy trên Excel SQL


SQL:
CREATE TABLE testntile
             (
             appid NUMBER(2),
             sample VARCHAR2(50),
             score NUMBER(3)
             );
 
INSERT ALL
  INTO testntile (appid, sample, score) VALUES (5, 'A', 4)
  INTO testntile (appid, sample, score) VALUES (2, 'B', 6)
  INTO testntile (appid, sample, score) VALUES (3, 'C', 8)
  INTO testntile (appid, sample, score) VALUES (1, 'C', 1)
  INTO testntile (appid, sample, score) VALUES (4, 'B', 2)
  INTO testntile (appid, sample, score) VALUES (8, 'C', 3)
  INTO testntile (appid, sample, score) VALUES (6, 'C', 8)
  INTO testntile (appid, sample, score) VALUES (9, 'B', 9)
  INTO testntile (appid, sample, score) VALUES (7, 'C', 7)
  INTO testntile (appid, sample, score) VALUES (10, 'B', 5)
  INTO testntile (appid, sample, score) VALUES (14, 'A', 2)
  INTO testntile (appid, sample, score) VALUES (12, 'C', 7)
  INTO testntile (appid, sample, score) VALUES (13, 'C', 7)
  INTO testntile (appid, sample, score) VALUES (11, 'A', 9)
  INTO testntile (appid, sample, score) VALUES (15, 'C', 3)
  INTO testntile (appid, sample, score) VALUES (16, 'C', 12)
  INTO testntile (appid, sample, score) VALUES (18, 'C', 12)
  INTO testntile (appid, sample, score) VALUES (17, 'C', 16)
  INTO testntile (appid, sample, score) VALUES (19, 'C', 12)
  INTO testntile (appid, sample, score) VALUES (20, 'D', 14)
  INTO testntile (appid, sample, score) VALUES (21, 'D', 11)
SELECT * FROM dual;
COMMIT;
 
-- ket qua NTILE mong muon
SELECT
      NTILE(3) OVER (PARTITION BY sample ORDER BY score) RangeList,
      s.*
FROM testntile s;
 
-- Ham NTILE viet lai chay tren Oracle
-- (day la buoc trung gian de viet tiep chay tren Excel SQL)
SELECT
      CASE WHEN REMAINDER(
                           (SELECT count(t2.appid)
                            FROM testntile t2
                            WHERE t2.sample = t1.sample)
                          ,3) <> 0
           THEN CASE WHEN ((SELECT count(t2.appid)
                            FROM testntile t2
                            WHERE t2.sample = t1.sample) / 3) < 1
                     THEN (SELECT COUNT(*) +1
                           FROM testntile t2
                           WHERE t2.sample = t1.sample
                                 AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                          )
                     ELSE 1 + TRUNC((SELECT COUNT(*)
                                     FROM testntile t2
                                     WHERE t2.sample = t1.sample
                                     AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                                    ) / ((SELECT count(t2.appid)
                                          FROM testntile t2
                                          WHERE t2.sample = t1.sample) / 3))
                END
           ELSE CASE WHEN ((SELECT count(t2.appid)
                            FROM testntile t2
                            WHERE t2.sample = t1.sample) / 3) = 1
                     THEN (SELECT COUNT(*) +1
                           FROM testntile t2
                           WHERE t2.sample = t1.sample
                                 AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                          )
                     ELSE 1 + TRUNC((SELECT COUNT(*)
                                     FROM testntile t2
                                     WHERE t2.sample = t1.sample
                                     AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                                    ) / ((SELECT count(t2.appid)
                                          FROM testntile t2
                                          WHERE t2.sample = t1.sample) / 3))
                END
      END RangeList,
   t1.appid,
   t1.sample,
   t1.score
FROM
   testntile t1
ORDER BY
   t1.sample,
   t1.score,
   t1.appid
 
-- Ham NTILE viet lai chay tren Excel SQL
SELECT
      IIf(MOD(
              (SELECT count(t2.appid)
               FROM &Selected_Table t2
               WHERE t2.sample = t1.sample)
              ,3) <> 0
              , IIf( ((SELECT count(t2.appid)
                       FROM &Selected_Table t2
                       WHERE t2.sample = t1.sample) / 3) < 1
                     , (SELECT COUNT(*) +1
                        FROM &Selected_Table t2
                        WHERE t2.sample = t1.sample
                              AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                       )
                     , 1 + INT((SELECT COUNT(*)
                                FROM &Selected_Table t2
                                WHERE t2.sample = t1.sample
                                      AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                               ) / ((SELECT count(t2.appid)
                                     FROM &Selected_Table t2
                                     WHERE t2.sample = t1.sample) / 3))
                   )
              , IIf( ((SELECT count(t2.appid)
                       FROM &Selected_Table t2
                       WHERE t2.sample = t1.sample) / 3) = 1
                     , (SELECT COUNT(*) +1
                        FROM &Selected_Table t2
                        WHERE t2.sample = t1.sample
                              AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                       )
                     , 1 + INT((SELECT COUNT(*)
                                FROM &Selected_Table t2
                                WHERE t2.sample = t1.sample
                                      AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))               
                               ) / ((SELECT count(t2.appid)
                                     FROM &Selected_Table t2
                                     WHERE t2.sample = t1.sample) / 3))
                   )
          ) RangeList,
   t1.appid,
   t1.sample,
   t1.score
FROM
   &Selected_Table t1
ORDER BY
   t1.sample,
   t1.score,
   t1.appid


Trong đoạn code SQL trên, cách truyền thống là đoạn -- ket qua NTILE mong muon.
Và cách mới là đoạn -- Ham NTILE viet lai chay tren Excel SQL.

Với cách mới, cần nhớ phải có file testntile.csv như mô tả ở phần INSERT ALL

Cuối cùng, đây là các sheet, sub và function trong VBA.
Với sheet mình dùng code name, không phải sheet name
  • shSQLList : chứa script SQL (cách mới) ở ô A1
  • shResult : chứa dữ liệu recordset trả về sau khi chạy script SQL
Với sub và function, chỉ cần cho hết code dưới đây vào 1 module.

Mã:
Option Explicit
 
Public strPath As String
Public strTable As String
 
Public Contn As New ADODB.Connection
 
Sub test()
 
Dim SQL As String
Dim rRange As Range
 
    'let the user choose files needed for making a report
    strPath = BrowseFileExplorer(, , ThisWorkbook.Path)
    If strPath = vbNullString Then 'user cancelled
        MsgBox "No file selected."
    Else 'user picked a file
        strPath = RemoveTrailingSlash(strPath)
        strTable = Right(strPath, Len(strPath) - InStrRev(strPath, "\"))
  
        'if file name contains underscore, ask the user to remove underscore
        If InStr(1, strTable, "_", vbTextCompare) Then
            MsgBox "SQL in Excel cannot run with underscore(s) in file name." & vbCrLf & _
                    "Please remove all underscores from the csv file, then run the macro again.", _
                    vbCritical + vbOKOnly, "Error"
            Exit Sub
        End If
    End If
 
Application.ScreenUpdating = False
 
    SQL = ""
    Set rRange = Nothing
  
    SQL = shSQLList.Range("A1").Value
    SQL = Replace(SQL, "&Selected_Table", "[" & strTable & "]")
    'table name needs enclosing in [ ] if it has space or special characters
    'to run SQL in Excel, table name cannot contain underscore
    Set rRange = shResult.Range("A1")
  
    Call ImportCSVfile(SQL, strPath, rRange.Cells(1, 1))
    Application.CutCopyMode = False
 
Application.ScreenUpdating = True
 
End Sub
 
Function BrowseFileExplorer(Optional DialogTitle As String = "Select a file", _
    Optional ViewType As Office.MsoFileDialogView = MsoFileDialogView.msoFileDialogViewSmallIcons, _
    Optional InitialDirectory As String) As String
'Other option:
'ViewType As Office.MsoFileDialogView = msoFileDialogViewList
 
Dim fDialog As Office.FileDialog
Dim varFile As Variant
 
Set fDialog = Application.FileDialog(msoFileDialogFilePicker)
 
With fDialog
    .Title = DialogTitle
    .InitialView = ViewType
    .ButtonName = "&Select"
    .AllowMultiSelect = False
    .Filters.Clear
    .Filters.Add "CSV", "*.csv"
  
    If Dir(InitialDirectory, vbDirectory) <> vbNullString Then
        If Right(InitialDirectory, 1) <> "\" Then
            InitialDirectory = InitialDirectory & "\"
        End If
        .InitialFileName = InitialDirectory
    Else
        .InitialFileName = CurDir
    End If
  
    If .Show = True Then
        ' user picked a file
        BrowseFileExplorer = .SelectedItems(1)
    Else
        ' user cancelled
        BrowseFileExplorer = vbNullString
    End If
End With
 
End Function
 
Public Function TrailingSlash(strFolder As String) As String
 
If Len(strFolder) > 0 Then
    If Right(strFolder, 1) = "\" Then
        TrailingSlash = strFolder
    Else
        TrailingSlash = strFolder & "\"
    End If
End If
 
End Function
 
Public Function RemoveTrailingSlash(strFile As String) As String
 
If Len(strFile) > 0 Then
    If Right(strFile, 1) = "\" Then
        RemoveTrailingSlash = Left(strFile, Len(strFile) - 1)
    Else
        RemoveTrailingSlash = strFile
    End If
End If
 
End Function
 
Sub ImportCSVfile(SQL As String, sPath As String, Destination As Range)
 
'---------------------------------------------------------------------------------------
'  NOTE: Requires reference to ADO library:
'     1. Open the Visual Basic Editor (Alt + Fll)
'     2. Choose Tools | References
'     3. Select the checkbox for Microsoft ActiveX Data Object 2.5 Library (or higher)
'---------------------------------------------------------------------------------------
 
Dim RcdSet As ADODB.Recordset
Dim fldRS As ADODB.Field
Dim i As Integer
 
    Set Contn = New ADODB.Connection
    'for before Excel 2007 to open .xls file
'    Contn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 8.0;HDR=YES"";"
              
    'for Excel 2007 or later to open .xls file
'    Contn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 8.0;HDR=YES"";"
              
    'for Excel 2007 or later to open .xlsx file
'    Contn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
  
    'for all Excel versions to open .csv file
    'get the folder path with trailing slash
    sPath = Left(sPath, InStrRev(sPath, "\", , vbTextCompare))
    Contn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Text;HDR=YES;FMT=Delimited"";"
    'HDR=Yes indicates that the first row contains column names, not data
 
    Set RcdSet = New ADODB.Recordset
  
    'If you are using an SQL statement with an ADO recordset,
    'make sure that the final parameter for the Open method is adCmdText (not adCmdTable)
    RcdSet.Open SQL, Contn, CursorTypeEnum.adOpenForwardOnly, _
                LockTypeEnum.adLockReadOnly, CommandTypeEnum.adCmdText
  
    Destination.CopyFromRecordset RcdSet
  
    RcdSet.Close
    Set RcdSet = Nothing
 
    Contn.Close
    Set Contn = Nothing
  
End Sub

Mặc dù mình đã cho ra kết quả giống hệt hàm NTILE với script SQL viết lại cho trường hợp chạy trên Oracle PL/SQL Developer, nhưng khi mình chạy script SQL viết lại cho trường hợp chạy trên Excel thì luôn bị báo lỗi. Mình đã thử rút gọn query SQL lại chỉ còn một đoạn SELECT IIf( ) đơn giản nhất như dưới đây nhưng vẫn gặp lỗi. Mọi người chạy thử lại sẽ thấy.

SQL:
SELECT
   IIf( ((SELECT count(t2.appid)
           FROM &Selected_Table t2
           WHERE t2.sample = t1.sample) / 3) < 1
         , 1, 0
      ) RangeList
   t1.appid,
   t1.sample,
   t1.score
FROM
    &Selected_Table t1

Mình đã tốn rất nhiều thời gian cho việc này nhưng vẫn không có kết quả. Vì vậy mình rất biết ơn nếu mọi người có thể giúp mình được.
 
Mình viết macro kết nối với file csv
Bạn chỉ cần làm những việc sau khi tới GPE:
- Đính kèm tập tin *.csv
- Nêu yêu cầu cần lấy những dữ liệu gì trong tập tin trên, điều kiện là gì, kết quả trả về để ở đâu/ trình bày ra sao?
- Minh họa kết quả.

Hết!
 
Thấy bạn viết cái hàm NTILE hoành tráng như thế đáng lẽ phải là rất có căn bản về SQL chứ!

Căn bản SQL:
- Mỗi phần mềm CSDL có một phiên bản SQL riêng của nó. Tuy hầu hết chúng ôm đồm tiêu chuẩn SQL nhưng chúng cũng có những tính chất riêng. Nói cách khác, SQL tiêu chuẩn thường dùng được trong các phiên bản riêng, nhưng ngược lại chưa hẳn đúng.
- Mỗi phiên bản phần mềm CSDL có một luật đặt tên biến và tham số khác nhau.

Dấu & trong SQL-Plus là substitute variable. Nhưng nó có nghĩa khác trong các phần mềm CSDL của Windows.

Căn bản ADO:
- Với VBA, khi kết nối với file Excel (hoặc csv) ADO dùng cố máy ACE hoặc JET để đọc.
- Cổ máy ACE và JET dùng phiên bản SQL của Access

Trong Access SQL, dấu & có nghĩa là nối chuỗi (tương đương với || trong sql-plus)

Bạn nên tìm hiểu về cách chuyền tham số cho Access Query.
 
Lần chỉnh sửa cuối:
Mặc dù mình đã cho ra kết quả giống hệt hàm NTILE với script SQL viết lại cho trường hợp chạy trên Oracle PL/SQL Developer, nhưng khi mình chạy script SQL viết lại cho trường hợp chạy trên Excel thì luôn bị báo lỗi. Mình đã thử rút gọn query SQL lại chỉ còn một đoạn SELECT IIf( ) đơn giản nhất như dưới đây nhưng vẫn gặp lỗi. Mọi người chạy thử lại sẽ thấy.

SQL:
SELECT
   IIf( ((SELECT count(t2.appid)
           FROM &Selected_Table t2
           WHERE t2.sample = t1.sample) / 3) < 1
         , 1, 0
      ) RangeList
   t1.appid,
   t1.sample,
   t1.score
FROM
    &Selected_Table t1

Mình đã tốn rất nhiều thời gian cho việc này nhưng vẫn không có kết quả. Vì vậy mình rất biết ơn nếu mọi người có thể giúp mình được.

Theo tôi, lỗi là do cái hàm IIF() trong câu lệnh SQL của bạn.
Nguồn dữ liệu mà bạn kết nối tới là dạng Text (CVS) chứ không phải Excel, accdb (JET, ACE) nên nó không hỗ trợ cái hàm VBA này đâu.
Nếu muốn truy vấn có lồng hàm, subquery thì tốt nhất nên đổ dữ liệu về Access hoặc Excel.
Cách khác là làm trên Access, dùng Linked table tới file CSV nguồn của bạn và xử lý code trong Access thì nó sẽ hỗ trợ.
 
Thấy bạn viết cái hàm NTILE hoành tráng như thế đáng lẽ phải là rất có căn bản về SQL chứ!

Căn bản SQL:
- Mỗi phần mềm CSDL có một phiên bản SQL riêng của nó. Tuy hầu hết chúng ôm đồm tiêu chuẩn SQL nhưng chúng cũng có những tính chất riêng. Nói cách khác, SQL tiêu chuẩn thường dùng được trong các phiên bản riêng, nhưng ngược lại chưa hẳn đúng.
- Mỗi phiên bản phần mềm CSDL có một luật đặt tên biến và tham số khác nhau.

Dấu & trong SQL-Plus là substitute variable. Nhưng nó có nghĩa khác trong các phần mềm CSDL của Windows.

Căn bản ADO:
- Với VBA, khi kết nối với file Excel (hoặc csv) ADO dùng cố máy ACE hoặc JET để đọc.
- Cổ máy ACE và JET dùng phiên bản SQL của Access

Trong Access SQL, dấu & có nghĩa là nối chuỗi (tương đương với || trong sql-plus)

Bạn nên tìm hiểu về cách chuyền tham số cho Access Query.

Chào bạn,

Mình đã biết về hai cái căn bản SQL và ADO mà bạn nêu trên. Và mình hiểu là có lẽ bạn mới đọc script SQL mà chưa đọc script VBA của mình nên mới viết như vậy.

Bạn muốn nói đến chỗ này đúng không:
FROM &Selected_Table t2

Nếu bạn đọc script VBA thì sẽ thấy dòng này:
SQL = shSQLList.Range("A1").Value
SQL = Replace(SQL, "&Selected_Table", "[" & strTable & "]")

Tức là script SQL trước khi chạy đã được thay &Selected_Table thành [testntile.csv]
Mình thêm dấu & là để dễ nhận diện và khi cần find or replace thì không bị nhầm thôi.
 
Xin lỗi mọi người vì mình không thấy chữ Edit nào trong bài viết đầu nên mình xin upload bổ sung dưới đây.

ham NTILE viet lai.xlsm
Đây là file Excel để chạy macro. Script SQL đã có sẵn ở ô A1 của Sheet1. Kết quả sẽ được trả về ô A1 của Sheet2.
Mọi người chỉ cần chạy macro tên là test, rồi chọn file testntile.csv là xong. Không có kết nối gì với Oracle cả.

testntile.csv
Đây là file dữ liệu nguồn.

ket qua NTILE mong muon.csv
Đây là kết quả cần đạt được sau khi chạy script SQL.

Vì mình tải file csv lên thì diễn đàn không cho phép nên mình đổi đuôi thành txt. Mọi người tải về rồi sửa lại đuôi thành csv nhé.

Hy vọng mọi người có thể giúp mình được vấn đề này. Mình có trả lời bạn ongke0711 về chi tiết lỗi, mọi người tham khảo giùm mình nha.
Bài đã được tự động gộp:

Theo tôi, lỗi là do cái hàm IIF() trong câu lệnh SQL của bạn.
Nguồn dữ liệu mà bạn kết nối tới là dạng Text (CVS) chứ không phải Excel, accdb (JET, ACE) nên nó không hỗ trợ cái hàm VBA này đâu.
Nếu muốn truy vấn có lồng hàm, subquery thì tốt nhất nên đổ dữ liệu về Access hoặc Excel.
Cách khác là làm trên Access, dùng Linked table tới file CSV nguồn của bạn và xử lý code trong Access thì nó sẽ hỗ trợ.

Xin chào bạn, cảm ơn bạn đã trả lời mình.

Rất tiếc là do công ty mình không có cài Access nên mình phải chạy SQL trên Excel.
Về lỗi do bản thân hàm IIf thì mình nghĩ cũng có thể. Mình đã rút query SQL lại chỉ còn thế này mà vẫn bị báo lỗi:

SELECT
IIf( (3 + 3) < 2, 1, 0 ) RangeList
t1.appid,
t1.sample,
t1.score
FROM
&Selected_Table t1

Lỗi như sau:
Run-time error '-2147217900 (80040e14)':
Syntax error (missing operator) in query expression


Thậm chí mình viết SQL chỉ có nhiêu đây mà vẫn bị lỗi, nhưng là lỗi khác:

SELECT appid
FROM &Selected_Table

Lỗi như sau:
Run-time error '-2147217904 (80040e10)':
No value given for one or more required parameters.


Chỉ có viết như thế này thì không bị lỗi, nhưng mà vầy thì chạy SQL làm gì nữa.

SELECT *
FROM &Selected_Table


Ghi chú: Trong các trường hợp trên thì trước khi chạy script SQL, macro đã thay &Selected_Table thành [testntile.csv]. Nhưng dù là [testntile.csv] hay testntile.csv thì vẫn bị lỗi.
 

File đính kèm

  • ham NTILE viet lai.xlsm
    22.4 KB · Đọc: 5
  • testntile.txt
    320 bytes · Đọc: 6
  • ket qua NTILE mong muon.txt
    416 bytes · Đọc: 4
Lần chỉnh sửa cuối:
Tức là túm lại, vấn đề của thớt là không thể truy xuất dữ liệu trong file csv theo từng trường một?
SELECT appid
FROM bảng_gì_đó
SELECT *
FROM bảng_gì_đó
Hai câu lệnh trên chỉ khác nhau chỗ đấy thôi.
 
Thậm chí mình viết SQL chỉ có nhiêu đây mà vẫn bị lỗi, nhưng là lỗi khác:

SELECT appid
FROM &Selected_Table

Lỗi như sau:
Run-time error '-2147217904 (80040e10)':
No value given for one or more required parameters.


Chỉ có viết như thế này thì không bị lỗi, nhưng mà vầy thì chạy SQL làm gì nữa.

SELECT *
FROM &Selected_Table

File CSV vẫn tuỳ chọn được tên Field nhé. Tôi xem file file testnfile.txt của bạn rồi. Bạn bỏ hết dấu nháy kép đi là được.
Riêng vụ lồng hàm VBA vô câu lệnh SQL truy vấn là không được thôi.

Một cách giải quyết khác nếu Cty không cho xài Access thì bạn dùng công cụ có sẳn của Excel là "Get External Data", kết nối với file CSV vào sheet Excel. Đặt Name range cho nó. Sau đó là có thể dùng câu lệnh SQL có lồng hàm VBA trên dữ liệu Excel này.
Một chú ý là câu lênh SQL của bạn bị sai: (chỗ font chữ mày đỏ)

SELECT
IIf( (3 + 3) < 2, 1, 0 ) As RangeList,
t1.appid,
t1.sample,
t1.score
FROM
&Selected_Table t1
 
Lần chỉnh sửa cuối:
File CSV vẫn tuỳ chọn được tên Field nhé. Tôi xem file file testnfile.txt của bạn rồi. Bạn bỏ hết dấu nháy kép đi là được.
Riêng vụ lồng hàm VBA vô câu lệnh SQL truy vấn là không được thôi.

Một cách giải quyết khác nếu Cty không cho xài Access thì bạn dùng công cụ có sẳn của Excel là "Get External Data", kết nối với file CSV vào sheet Excel. Đặt Name range cho nó. Sau đó là có thể dùng câu lệnh SQL có lồng hàm VBA trên dữ liệu Excel này.
Một chú ý là câu lênh SQL của bạn bị sai: (chỗ font chữ mày đỏ)

SELECT
IIf( (3 + 3) < 2, 1, 0 ) As RangeList,
t1.appid,
t1.sample,
t1.score
FROM
&Selected_Table t1

Cảm ơn bạn, mình quên không để ý vụ dấu ngoặc kép. File csv đó export từ bảng trên Oracle.
Tuy nhiên dù mình xóa hết ngoặc kép rồi, mình cũng thêm AS và dấu phẩy nhưng vẫn gặp lỗi cũ:

Run-time error '-2147217904 (80040e10)':
No value given for one or more required parameters.

Gợi ý của bạn get external data và đặt table cho nó (cũng là name range) thì để mình làm thử xem sao.
Bài đã được tự động gộp:

Tức là túm lại, vấn đề của thớt là không thể truy xuất dữ liệu trong file csv theo từng trường một?
SELECT appid
FROM bảng_gì_đó
SELECT *
FROM bảng_gì_đó
Hai câu lệnh trên chỉ khác nhau chỗ đấy thôi.

Vấn đề của mình là viết lại hàm NTILE cho ra kết quả như khi chạy trên Oracle.
Vì gặp lỗi nên mình mới tìm xem nguyên nhân ở đâu, do chuyển từ các hàm Oracle sang (REMAINDER thay bằng MOD; TRUNC thay bằng INT; CASE thay bằng IIF) hay do gì khác.

Và mình thấy là ngay cả khi đơn giản hóa script SQL chỉ còn thế này
SELECT appid
FROM &Selected_Table
mà vẫn bị lỗi nên mình không hiểu tại sao.

Chứ mình cần là chạy được script của phần này đây
-- Ham NTILE viet lai chay tren Excel SQL
Bài đã được tự động gộp:

Bao giờ có file *.csv thì mới bắt đầu tính giờ.

Cái này diễn đàn phải xem lại sao không cho upload file csv lên chứ. Mình đã ghi rõ là do không được upload file csv nên mình đổi đuôi thành txt thôi. Bạn tải về rồi sửa lại thành csv là bình thường.
 
Lần chỉnh sửa cuối:
Cảm ơn bạn, mình quên không để ý vụ dấu ngoặc kép. File csv đó export từ bảng trên Oracle.
Tuy nhiên dù mình xóa hết ngoặc kép rồi, mình cũng thêm AS và dấu phẩy nhưng vẫn gặp lỗi cũ:

Run-time error '-2147217904 (80040e10)':
No value given for one or more required parameters.

Gợi ý của bạn get external data và đặt table cho nó (cũng là name range) thì để mình làm thử xem sao.

Tôi test trên cái file bạn gửi thì không bị lỗi trên nhé.

Bổ sung là tôi mới test thêm một cách mới là thêm khai báo cho file text bằng file : schema.ini thì chạy tốt luôn cái truy vấn có IIF().
- Tạo file schema.ini. Lưu cùng thư mục file CSV của bạn.
- Nội dung: tuỳ chỉnh tham số theo định dạng file csv của bạn
SCHEMA.INI
Mã:
[testntile.csv]
ColNameHeader=True
Format=Delimited(,)
MaxScanRows=0
CharacterSet=ANSI
Col1=appid Integer
Col2=sample Text
Col3=score Integer

Tôi test với câu SQL này chạy OK.

Mã:
SELECT
   IIf(((SELECT count(t2.appid)
           FROM &Selected_Table t2
           WHERE t2.sample = t1.sample) / 3) < 1
         , 1, 0
      ) As RangeList,
   t1.appid,
   t1.sample,
   t1.score
FROM
    &Selected_Table t1

Còn cái hàm NTILE cũng đã test nhưng báo lỗi cú pháp. Nhìn vô cái hàm lùng bùng quá nên bạn tự kiểm tra lại xem các dấu ngoặc có bỏ thiếu không nhé.
 
Trong bài #6, bạn nói
Select appid không được

Select * thì được

Dựa trên việc ấy, tôi phân tích rằng: 2 cái trên chỉ khác nhau ở chỗ cái tên của trường.
Xác định được rồi mới định hướng giải quyết chứ.

Thử bước 1: Select [appid] -- gói tên trường trong dấu ngoặc.

Bổ sung thêm:
Giải đáp ở bài #14 có lẽ là đúng nhất cho trường hợp của bạn.
Schema.ini là điều tất yếu nếu file csv có nhiều kiểu dữ liệu khác nhau.
 
Lần chỉnh sửa cuối:
- Nêu yêu cầu cần lấy những dữ liệu gì trong tập tin trên, điều kiện là gì,
Đợi mãi chẳng thấy chủ thớt nêu đoạn này.. ngồi đọc mớ 'lùng bùng' chắc đuối luôn.

Chỉ ra lỗi do đâu, chủ thớt tự xử tiếp..
 

File đính kèm

  • abc_csv.xlsb
    16 KB · Đọc: 8
Xin chào mọi người,

Trước hết mình xin cảm ơn mọi người đã nhiệt tình giúp đỡ, đặc biệt là bạn ongke0711.

Nhờ những góp ý của mọi người mà mình đã viết lại thành công hàm NTILE của Oracle để chạy SQL trên Excel. Trước khi hỏi trên giaiphapexcel, mình đã hỏi vấn đề này trên các trang nước ngoài khác như stackoverflow.com, mrexcel.com, community.oracle.com nhưng hoặc là không có ai trả lời hoặc là có nhưng không giúp mình giải quyết được vấn đề này. Mình biết diễn đàn giaiphapexcel từ 2007 nhưng đây là lần đầu tiên mình đặt câu hỏi và rất vui vì mình có được kết quả mong đợi.

Mình đính kèm file ham NTILE viet lai.xlsm và testntile.csv (sau khi giải nén). Trong Module1 là toàn bộ code hoàn chỉnh để chọn file csv bất kỳ, tự động tạo file schema.ini dựa trên file csv vừa chọn, và tự chạy câu lệnh SQL để ra kết quả như hàm NTILE. Mọi người chỉ cần chạy sub tên là test.

Cuối cùng là một số chú ý của mình về vấn đề này:
- Với file csv thì các giá trị nằm trong dấu ngoặc kép hay không có dấu ngoặc kép đều sử dụng được.
- Câu lệnh SQL phải có từ AS trước alias của field
- Phải có file schema.ini trong cùng thư mục với file macro (cái này thì macro tự động tạo schema.ini)

Mình post code lên đây luôn để mọi người dễ tham khảo.


Câu lệnh SQL trong Sheet1 (tương đương với hàm NTILE của Oracle):

SQL:
SELECT
      IIf( ((SELECT COUNT(t2.appid)
             FROM &Selected_Table t2
             WHERE t2.sample = t1.sample) / 3) <= 1
          , (SELECT COUNT(*) +1
             FROM &Selected_Table t2
             WHERE t2.sample = t1.sample
                   AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))              
            )
          , 1 + INT((SELECT COUNT(*)
                     FROM &Selected_Table t2
                     WHERE t2.sample = t1.sample
                           AND (t2.score < t1.score OR (t2.score = t1.score AND t2.appid < t1.appid))              
                    ) / ((SELECT COUNT(t2.appid)
                          FROM &Selected_Table t2
                          WHERE t2.sample = t1.sample) / 3))
         ) AS RangeList,
   t1.appid,
   t1.sample,
   t1.score
FROM
   &Selected_Table t1
ORDER BY
   t1.sample,
   t1.score,
   t1.appid



Toàn bộ code trong Module1:

Mã:
Option Explicit

Public Enum StringTypes
            Alphabetical = 1
            AlphaNumeric = 2
            Numerical = 3
            Special = 4
End Enum

Public strPath As String
Public strTable As String

Public Contn As New ADODB.Connection

Sub test()

Dim SQL As String
Dim rRange As Range

    'let the user choose files needed for making a report
    strPath = BrowseFileExplorer(, , ThisWorkbook.Path)
    If strPath = vbNullString Then 'user cancelled
        MsgBox "No file selected."
    Else 'user picked a file
        strPath = RemoveTrailingSlash(strPath)
        strTable = Right(strPath, Len(strPath) - InStrRev(strPath, "\"))

        'if file name contains underscore, ask the user to remove underscore
        If InStr(1, strTable, "_", vbTextCompare) Then
            MsgBox "SQL in Excel cannot run with underscore(s) in file name." & vbCrLf & _
                    "Please remove all underscores from the csv file, then run the macro again.", _
                    vbCritical + vbOKOnly, "Error"
            Exit Sub
        End If
       
        Call CreateSchemaINI(Left(strPath, InStrRev(strPath, "\", , vbTextCompare)))
    End If

Application.ScreenUpdating = False

    SQL = ""
    Set rRange = Nothing

    SQL = shSQLList.Range("A1").Value
    SQL = Replace(SQL, "&Selected_Table", "[" & strTable & "]")
    'table name needs enclosing in [ ] if it has space or special characters
    'to run SQL in Excel, table name cannot contain underscore
    Set rRange = shResult.Range("A1")

    Call ImportCSVfile(SQL, strPath, rRange.Cells(1, 1))
    Application.CutCopyMode = False

Application.ScreenUpdating = True

End Sub

Function BrowseFileExplorer(Optional DialogTitle As String = "Select a file", _
    Optional ViewType As Office.MsoFileDialogView = MsoFileDialogView.msoFileDialogViewSmallIcons, _
    Optional InitialDirectory As String) As String
'Other option:
'ViewType As Office.MsoFileDialogView = msoFileDialogViewList

Dim fDialog As Office.FileDialog
Dim varFile As Variant

Set fDialog = Application.FileDialog(msoFileDialogFilePicker)

With fDialog
    .Title = DialogTitle
    .InitialView = ViewType
    .ButtonName = "&Select"
    .AllowMultiSelect = False
    .Filters.Clear
    .Filters.Add "CSV", "*.csv"

    If Dir(InitialDirectory, vbDirectory) <> vbNullString Then
        If Right(InitialDirectory, 1) <> "\" Then
            InitialDirectory = InitialDirectory & "\"
        End If
        .InitialFileName = InitialDirectory
    Else
        .InitialFileName = CurDir
    End If

    If .Show = True Then
        ' user picked a file
        BrowseFileExplorer = .SelectedItems(1)
    Else
        ' user cancelled
        BrowseFileExplorer = vbNullString
    End If
End With

End Function

Public Function TrailingSlash(strFolder As String) As String

If Len(strFolder) > 0 Then
    If Right(strFolder, 1) = "\" Then
        TrailingSlash = strFolder
    Else
        TrailingSlash = strFolder & "\"
    End If
End If

End Function

Public Function RemoveTrailingSlash(strFile As String) As String

If Len(strFile) > 0 Then
    If Right(strFile, 1) = "\" Then
        RemoveTrailingSlash = Left(strFile, Len(strFile) - 1)
    Else
        RemoveTrailingSlash = strFile
    End If
End If

End Function

Sub ImportCSVfile(SQL As String, sPath As String, Destination As Range)

'---------------------------------------------------------------------------------------
'  NOTE: Requires reference to ADO library:
'     1. Open the Visual Basic Editor (Alt + Fll)
'     2. Choose Tools | References
'     3. Select the checkbox for Microsoft ActiveX Data Object 2.5 Library (or higher)
'---------------------------------------------------------------------------------------

Dim RcdSet As ADODB.Recordset
Dim fldRS As ADODB.Field
Dim i As Integer

    Set Contn = New ADODB.Connection
    'for before Excel 2007 to open .xls file
'    Contn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 8.0;HDR=YES"";"
             
    'for Excel 2007 or later to open .xls file
'    Contn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 8.0;HDR=YES"";"
             
    'for Excel 2007 or later to open .xlsx file
'    Contn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"

    'for all Excel versions to open .csv file
    'get the folder path with trailing slash
    sPath = Left(sPath, InStrRev(sPath, "\", , vbTextCompare))
    Contn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=" & sPath & ";" & _
                "Extended Properties=""Text;HDR=YES;FMT=Delimited"";"
    'HDR=Yes indicates that the first row contains column names, not data

    Set RcdSet = New ADODB.Recordset

    'If you are using an SQL statement with an ADO recordset,
    'make sure that the final parameter for the Open method is adCmdText (not adCmdTable)
    RcdSet.Open SQL, Contn, CursorTypeEnum.adOpenForwardOnly, _
                LockTypeEnum.adLockReadOnly, CommandTypeEnum.adCmdText

    Destination.CopyFromRecordset RcdSet

    RcdSet.Close
    Set RcdSet = Nothing

    Contn.Close
    Set Contn = Nothing

End Sub

Sub CreateSchemaINI(strFolderPath As String)

'---------------------------------------------------------------------------------------
'  NOTE: Requires reference to Microsoft Scripting Runtime object library:
'     1. Open the Visual Basic Editor (Alt + Fll)
'     2. Choose Tools | References
'     3. Select the checkbox for Microsoft Scripting Runtime
'---------------------------------------------------------------------------------------

Dim iniFSO As Scripting.FileSystemObject
Dim csvFSO As Scripting.FileSystemObject
Dim iniStream As Scripting.TextStream
Dim csvStream As Scripting.TextStream
Dim strLine1 As String, strLine2 As String
Dim lngPosition1 As Long, lngPosition2 As Long
Dim lngLength1 As Long, lngLength2 As Long
Dim strValue1 As String, strValue2 As String
Dim intColumn As Integer
Dim strType As String
   
    Set csvFSO = New Scripting.FileSystemObject
    Set csvStream = csvFSO.OpenTextFile(strPath, ForReading, False, TristateMixed)
   
    With csvStream
        strLine1 = .ReadLine 'line #1
        strLine2 = .ReadLine 'line #2
        .Close
    End With
   
    'remove  (Byte Order Mark) from the first line
    strLine1 = Replace(strLine1, "", "")
   
'Debug.Print "strLine1: " & strLine1
'Debug.Print "strLine2: " & strLine2

    Set iniFSO = New Scripting.FileSystemObject
    Set iniStream = iniFSO.CreateTextFile(strFolderPath & "schema.ini", True)
    'True means overwriting any existing file by the same name in the specified file path
    'hint: strFolderPath = Left(strPath, InStrRev(strPath, "\", , vbTextCompare))
   
    With iniStream
        .WriteLine "[" & strTable & "]"
        .WriteLine "ColNameHeader = True"
        .WriteLine "Format=Delimited(,)"
        .WriteLine "MaxScanRows = 0"
        .WriteLine "CharacterSet = ANSI"
       
        lngPosition1 = 1
        lngPosition2 = 1
        intColumn = 0
        While InStr(lngPosition1, strLine1, ",") > 0
            intColumn = intColumn + 1
           
            If lngPosition1 > 1 Then
                lngPosition1 = InStr(lngPosition1, strLine1, ",")
            End If
            lngLength1 = InStr(lngPosition1 + 1, strLine1, ",")
           
            If lngLength1 > 0 Then
                lngLength1 = lngLength1 - (lngPosition1 + 1)
            Else
                lngLength1 = Len(strLine1) - lngPosition1
            End If
           
            'remove double quote
            strValue1 = Replace(Mid(strLine1, lngPosition1 + 1, lngLength1), """", "")
                     
            If lngPosition2 > 1 Then
                lngPosition2 = InStr(lngPosition2, strLine2, ",")
            End If
            lngLength2 = InStr(lngPosition2 + 1, strLine2, ",")
           
            If lngLength2 > 0 Then
                lngLength2 = lngLength2 - (lngPosition2 + 1)
            Else
                lngLength2 = Len(strLine2) - lngPosition2
            End If
           
            'remove double quote
            strValue2 = Replace(Mid(strLine2, lngPosition2 + 1, lngLength2), """", "")

            Select Case GetType(strValue2)
                Case 1, 2, 4
                    strType = "Text"
                Case 3
                    strType = "Integer"
            End Select
           
            'write each column header to schema.ini file
            .WriteLine "Col" & intColumn & "=" & strValue1 & " " & strType
           
            lngPosition1 = lngPosition1 + 1
            lngPosition2 = lngPosition2 + 1
        Wend

        .Close
    End With

    'Open text file in Notepad
'    Call Shell("notepad.exe """ & strFolderPath & "schema.ini" & """", vbNormalFocus)
   
End Sub

Function GetType(aString As String) As StringTypes

If UCase(aString) Like "*[A-Z]*" And _
Not UCase(aString) Like "*[!A-Z]*" Then
    GetType = Alphabetical
ElseIf aString Like "*[0-9]*" And _
UCase(aString) Like "*[A-Z]*" Then
    GetType = AlphaNumeric
ElseIf aString Like "*[0-9]*" And _
Not UCase(aString) Like "*[!0-9]*" Then
    GetType = Numerical
Else
    GetType = Special
End If

'Debug.Print aString & " = " & GetType
End Function
 

File đính kèm

  • ham NTILE viet lai.xlsm
    30.2 KB · Đọc: 5
  • testntile.zip
    295 bytes · Đọc: 7
  • ket qua NTILE mong muon.txt
    240 bytes · Đọc: 7
Web KT
Back
Top Bottom