2009-07-16 13 views
5

Używam OleDb do wybierania danych z arkuszy kalkulacyjnych programu Excel. Każdy arkusz kalkulacyjny może zawierać wiele małych tabel i ewentualnie mebli, takich jak tytuły i etykiety. Więc może wyglądać tak, gdzie mamy dwie tabele i kilka tytułów;Połączenie OleDb z programem Excel; jak wybrać stałą szerokość, nieograniczoną wysokość?

 
      A   B   C   D 
    1 .   .   .   . 
    2 .   .   .   . 
    3 Table1  .   .   . 
    4 Header1  HEADER2 .   . 
    5 h   huey  .   . 
    6 d   dewey  .   . 
    7 l   loius  .   . 
    8 s   scrooge .   . 
    9 .   .   .   . 
    10 .   .   .   . 
    11 .   .   .   . 
    12 .   .   .   . 
    13 .   Table 2 .   . 
    14 .   HEADER1 HEADER2 HEADER3 
    15 .   1   foo  x 
    16 .   2   bar  y 
    17 .   3   baz  z 
    18 .   .   .   . 
    19 .   .   .   . 

W poprzednim kroku użytkownik wybrał nagłówki interesującej go tabeli; w tym przypadku, patrząc na tabelę 2, wybiorą zakres B14:D14.

Ustawienia te są zapisywane, a następnie muszę wysłać zapytanie do tej tabeli. Może się zdarzyć w kółko, ponieważ dane w arkuszu kalkulacyjnym są aktualizowane; więcej wierszy może być dodanych w dowolnym momencie, ale nagłówki są zawsze stałe. Istnieje znacznik (pusty wiersz) oznaczający koniec danych.

Aby wybrać dane w tabeli, piszę takie zapytanie;

SELECT * FROM [Sheet1$B14:D65535] 

w celu wybrania danych w tabeli 2, a następnie ręcznego sprawdzania wiersza wskaźnika, ale wydaje się to niesatysfakcjonujące. W programie Excel 2003 można przeczytać tylko 65 535 wierszy (uint16), ale program Excel 2007 może czytać o wiele więcej (uint32), więc muszę napisać kod, który daje inne zapytanie dla programu Excel 2003 i 2007 na podstawie rozszerzenia pliku (.xls vs. xls?).

Czy ktoś wie o sposobie napisania zapytania, które mówi;

  • "wybierz wszystko w dół i na prawo od B14"?
  • 'zaznaczyć wszystko w kolumnach B-> D'
  • 'wybierz B12: D *', gdzie * oznacza 'wszystko można'
+0

Arkusz programu Excel 2003 może zawierać 65536 wierszy, ponumerowanych od 0 do 65535 wewnętrznie i od 1 do 65536 na zewnątrz. –

Odpowiedz

9

Wymaganie wstępne: w kodzie można łatwo określić, jaka jest maksymalna liczba wierszy.

Zakładając (1) na SELECT jest duży narzut, więc WYBIERANIE wiersza na raz jest wolne (2) WYBIERANIE wierszy 64K lub 8M (nawet jeśli puste) jest wolne ... więc chcesz zobaczyć, czy gdzieś w środek może być szybszy. Wypróbuj to:

Wybierz jeden wiersz CHUNKSIZE (na przykład 100 lub 1000) naraz (mniej, gdy w przeciwnym razie przekroczysz MAX_ROWS). Skanuj każdy fragment pustego wiersza oznaczającego koniec danych.

UPDATE: Właściwie odpowiadając bezpośrednimi pytaniami:

Q: Czy ktoś zna sposób, aby napisać kwerendę, która mówi, albo;

Q1: "wybierz wszystko w dół i w prawo od B14"?

A1: select * from [Sheet1$B12:] nie działa. Trzeba by zrobić ...B12:IV w programie Excel 2003 i cokolwiek to jest w programie Excel 2007. Jednak nie jest to potrzebne, ponieważ wiesz, co jest kolumną po prawej stronie; patrz poniżej.

Q2: 'zaznaczyć wszystko w kolumnach B-> D'

A2: select * from [Sheet1$B:D]

Q3: 'wybierz B12: D *', gdzie 'wszystko można' * znaczy

A3: wybierz * z [Sheet1 $ B12: D]

Testowany przy pomocy Pythona 2.5 za pomocą następującego kodu:

import win32com.client 
import sys 
filename, sheetname, range = sys.argv[1:4] 
DSN= """ 
    PROVIDER=Microsoft.Jet.OLEDB.4.0; 
    DATA SOURCE=%s; 
    Extended Properties='Excel 8.0;READONLY=true;IMEX=1'; 
    """ % filename 
conn = win32com.client.Dispatch("ADODB.Connection") 
conn.Open(DSN) 
rs = win32com.client.Dispatch("ADODB.Recordset") 
sql = (
    "SELECT * FROM [Excel 8.0;HDR=NO;IMEX=1;Database=%s;].[%s$%s]" 
    % (filename, sheetname, range) 
    ) 
rs.Open(sql, conn) 
nrows = 0 
while not rs.EOF: 
    nrows += 1 
    nf = rs.Fields.Count 
    values = [rs.Fields.Item(i).Value for i in xrange(nf)] 
    print nrows, values 
    if not any(value is not None for value in values): 
     print "sentinel found" 
     break 
    rs.MoveNext() 
rs.Close() 
conn.Close() 
1

Para możliwych rozwiązań:

  1. Połóż tabele w oddzielnych arkuszach roboczych, po prostu zapytaj cały arkusz roboczy.
  2. Każdej tabeli w programie Excel podaj nazwę (w programie Excel 2007 wybierz tabelę, kliknij prawym przyciskiem myszy i wybierz Nazwij zakres ...), następnie w zapytaniu użyj tej nazwy zamiast "Arkusz1 $ B14: D65535" .

Nadzieję, że pomaga.

EDIT

Oto trzecia idea:

Nie jestem pewien, co używasz do kwerendy bazy danych, ale jeśli silnik zapytań obsługuje zmienne (jak SQL Server, na przykład) można zapisać wynik ...

SELECT COUNT (*) FROM NameOfServer ... Arkusz1 $

... w zmiennej o nazwie @UsedRowCount, który daje liczbę wierszy faktycznie wykorzystanycharkusz roboczy. Tak więc @UsedRowCount = LastRowUsed - InitialBlankRows.

Możliwe, że będziesz mógł użyć konkatenacji ciągów do zamiany "65535" na @UsedRowCount + @InitialBlankRows. Będziesz musiał ustawić @InitialBlankRows na stałą (w twoim przykładzie będzie to 3, ponieważ wiersz nagłówka pierwszej tabeli znajduje się w Wierszu 4).

+0

Dzięki, Dan. Niestety nie mam kontroli nad arkuszami - są one arbitralnie przychodzące od użytkowników i po prostu muszę poradzić sobie z tym, co mi dano. –

+0

Gotcha. Właśnie dodałem trzeci pomysł do mojej odpowiedzi. – devuxer

0

Mówisz, że w poprzednim kroku użytkownicy wybrali nagłówki. Kto może powiedzieć, że poniżej obszaru obecnego zainteresowania nie ma kilku pustych wierszy, a po nich następnej niepowiązanej tabeli? Sugeruję, aby je ustawić na wybrać cały zakres, który jest zainteresowany - który powinien rozwiązać oba problemy.

+0

Odcięcie dodatkowych szczegółów jest obsługiwane przez aplikację; Odczytuję wiersze z tabeli danych, aż do wyczerpania danych; tj. otrzymuję pełny pusty wiersz. Potem kończę czytanie samemu. Niestety, nie zawsze mogę poprosić użytkownika o wybranie całego zakresu. W niektórych przypadkach użytkownik wybiera nagłówek arkusza kalkulacyjnego, w którym dane mogą się zmieniać, ale format nie może tego zrobić. Np. Mogą mieć notowania giełdowe ze stałymi nagłówkami (firma, cena otwarcia, cena zamknięcia, data), ale faktyczna liczba rekordów zmieni się z czasem. Jeśli wybierze 50 wierszy, to przy liczbie wierszy> 50 import będzie podawał zbyt mało danych. –

+0

W twoim pytaniu wymagane są wyjaśnienia (edytuj je): (1) "wybierz raz, uruchom wiele" nie "(wybierz, a następnie uruchom) * wiele". (2) znajduje się wartownik (pusty wiersz) oznaczający koniec danych (3) Kiedy mówisz "Czytam wiersze z tabeli danych, aż do wyczerpania danych" oznacza to "Czytałem z wyniku" SELECT * FROM " [Sheet1 $ B14: D65535] 'until" lub czy to znaczy "Robię jednorzędowe WYBIERA do"? (4) Jak łatwo można ustalić w kodzie, czy maksymalna liczba wierszy wynosi 2^16 czy 2^20? –

0

Chciałbym pójść z rozwiązaniem od Johna (czytając 1000 wierszy na raz).

Jeśli masz zainstalowany program Excel, możesz również użyć automatyzacji OLE.

Zapisałem proste makro w programie Excel, które wybiera ostatnią komórkę w bieżącej tabeli.


Sub Macro2() 
    Range("B14").Select 
    Selection.End(xlDown).Select 
    //MsgBox ActiveCell.Address, vbOKOnly 
End Sub 
 

Teraz wystarczy przetłumaczyć to w C# i odczytać adres aktywnej komórki.

0

Czytamy cały arkusz kalkulacyjny (np .: SELECT * FROM [Sheet1 $]) i obsługujemy wszystko inne w naszym kodzie aplikacji. Łatwo jest prześcignąć wynikowy OleDbDataReader, aby dostać się do punktu początkowego danych i rozpocząć przetwarzanie.

Może to nie być absolutnie najszybszy sposób na pobieranie danych z programu Excel, ale jest niezawodny.