Bu siteye giriş yaparak Çerez kullanımını kabul etmiş oluyorsunuz. İşbu sitede; çerez olarak, sadece son giriş tarihiniz ve eğer üye olursanız oturum statünüz tutulacaktır. Bunlar dışında başka hiçbir bilgi tutulmamaktadır. Çerezler için detaylı bilgi için buraya tıklayınız.
ANLADIM

DUYURULAR

Yeni eklenen ve/veya güncellenen sayfaları görmek için buraya tıklayınız.

Güncel ödev ve test listesini görmek için buraya tıklayınız.

Sitede yapılan iyileştirmeAer ve hata düzeltmelerine ait tüm bilgilendirmeleri görmek içinburaya tıklayınız.

Baş
Udemy
Konular
Son
Konular
VeriTabani-ConnectionListObjectveQueryTable
DigerUygulamalarlailetisim
VBAMAkro
VBAMakro Diğer Uygulamalarla iletişim 4

Connection, ListObject ve QueryTable nesneleri

Bu bölümde, DAO ve ADO'dan yöntem olarak biraz farklılaşan, refreshlenebilir bağlantı tekniklerine değineceğiz. Bu sayfada anlatılanlar için Dış veri kaynaklarıyla çalışmak sayfasında anlatılan konuları iyi bilmek gerekiyor. Eğer bunları bilmiyorsanız öncelikle o sayfayı incelemenizi sonra buraya gelmenizi tavsiye ederim.

Örneklerdeki kodların olduğu dosyayı ve veritabanını buradan indirebilirsiniz. 

Giriş

Önceki bölümde DAO ve ADO ile bir veritabanına bağlanıp nasıl veri çekileceğini gördük. Bunların ortak noktası datayı değişkenler/diziler içinde depolayarak veya metodlar aracılığı ile Excele yapıştırmak şeklindeydi. Bu yöntemlerle elde edilen veri, Data menüsünden veya sağ tıklanarak refreshlenemez. Yani DAO/ADO refresh yapılamayan ve statik data getiren yöntemlerdir.  Ancak ilgili kod tekrar çalıştırılarak(ilgili kod bir butona atanabilir), güncel data getirilebilir, yani dolaylı yoldan bir nevi refresh uygulanabilir. Refresh yapılabilirliğin VBA açısından önemi Refresh ve RefreshAll metodlarının kullanım kolaylığındandır.

Excel’den veritabanlarına ulaşmanın bir yolu da MS Query veya Data Connection Wizard aracılığı ile refreshelenbilir bir Tablo(VBA karşılığı ListObject) yaratmaktır. Bu kısımda bu yöntemlere bakıyor olacağız, tabi sadece VBA ile ilgili kısmına. Bu tür bağlantı yaratım tekniklerini biliyor olduğunuzu vasrsayacağım, bilmeyenler buradan detay bilgi edinebilir.

Refreshlenebilir bir dosyayı ilk kez oluşturma işini genelde Excel içinden manuel yaparız. Ancak bazı durumlarda VBA ile de yapmamız gerekebilmektedir. Yine de ilk Connection String bilgilerini v.s ilk başta Excel’den yapmak daha kolaydır, hatta macro recorder ile oluşan kodu alıp, aradaki gereksiz kodu temizledikten sonra VBA kodunu hazırlayabilrsiniz.

Bunun refresh işlemini ise VBA’e bırakabiliriz, tabiki manuel olarak refresh edilebilir ancak bu sayfada VBA ile bunun yapılış yollarını öğreneceğiz. Zira bu bilgiler schedule işlemlerinde kritik öneme sahiptir.

Önemli Classlar

DAO ve ADO’dan farklı olarak bu sefer herhangi bir library’yi referans olarak eklememize gerek bulunmamaktadır. Bunun yerine Excel'in açılmasıyla mevcut bulunan 3 sınıf/nesne üzerinde çalışıyor olacağız.

  • Connection(s) sınıfı: Varolan bağlantılarla ilgili işlem yapılmasını veya bilgi edinilmesini sağlar.
  • ListObject(s) sınıfı: Yeni connection kurmamızı sağlar. Nihai liste Table şeklindedir.
  • QueryTable(s) sınıfı: Yeni connection kurmamızı sağlar. Nihai liste Table şeklinde değildir.

Şimdi bunlara detaylıca bakalım.

Connections sınıfı

Connections’ın sonundaki s harfinden bunun bi collection olduğunu anlıyoruz. Normalde hemen her collectiona ait bir de sonunda “s” olmayan nesnesi vardır, Workbooks/Workbook gibi. Ancak Connections için bu tipte bir nesne yok. Bununla ilgili hiçbir kaynak bulamadım ancak benim tahminim tek bir tür connection olmaması. Bizim burada ilgileneceğimiz nesne WorkbookConnection’dır. WorkbookConnection nesnesi tüm dış bağlantıları yöneten nesnedir. Bunun da aşağıdaki gibi kendi içinde alt türleri vardır:

(Yandaki rakamlar Type propertysinin değeridir.)
2, xlConnectionTypeODBC
1, xlConnectionTypeOLEDB
4, xlConnectionTypeTEXT
5, xlConnectionTypeWEB
Diğer bikaç tip daha var, ama onlar bizi şu aşamada hiç ilgilendirmiyor.

Connection’ların güzelliği bi kere yaratıldıklarında Excel içinde depolanabilir olmalarıdır. Connectionlar, ayrı bir connection dosyasında da tutulabilir. Bunlar genelde ODC uzantılı dosyalardır. Ama biz bu şekilde çalışmaycağız, Excel içinde depolayacağız. Daha önceden bahsettiğim gibi bazen de direkt kodun içinde connection yaratımı olabilir, dolayısıyla dosya ilk açıldığında connection yoktur, onu yaratan kod tetiklenince oluşur. Her ne kadar çok sık ihtiyacımız olmayacaksa da bu yöntemi de göreceğiz.

Yeni yaratım

Her bir QueryTable, ListObject ve harici dataya başvuran bir PivotCache yaratıldığında yeni bir WorkbookConnection nesnesi yaratılır. Bu şekilde dolayı olarak yaratılan connectionlara biraz aşağıda bakacağız.

Sonradan Existing Connections içinden kullanılmak üzere standalone(tek başına duran) WorkbookConnection nesnesi de yaratılabilir, ama buna çok gerek olacağını sanmıyorum, o yüzden yeni yaratım yerine varolan connectionlar üzerinde duracağız.

ConnectionString

Bir connection’ın en önemli öğesi Connection Stringdir. DAO ve ADO’da gördüğümüz gibi bu öğe, dataya nasıl bağlanılacağını belirten yönergeyi içerir. Bunun içinde genellikle bağlantı türünün ODBC/OLEDB mi olduğu, Driver/Provider’in ne olduğu, varsa user ve şifreler ve diğer parametreler  bulunur.

Çeşitli veritabanlarına bağlanma yöntemlerini www.connectionstrings.com adresinden bulabilirsiniz.

Connection'a Erişim ve Varolan Connection'ı Editleme

Bir kez daha belirtmek gerekirse biz daha çok Connectionların yeni yaratımlarından ziyade mevcut Connectionların özelliklerini okuma/değiştirme işlemleri yapacağız.

Connection’ın kendisine Connections koleksiyonuna index veya isim vererek ulaşabiliyoruz.

Activeworkbook.Connections(1)
Activeworkbook.Connections("Query from PDWH_USR")

Connection’ın kendisini elde ettikten sonra bunla ilgili işlemleri yapmak için genelde Connection’ın tipine göre ilgili property’yi seçip daha çok bunun üzerinden işlem yapıyoruz, Connection’ın kendisiyle pek bi işimiz olmuyor. Yani bağlantı tipimiz ODBC ise Activeworkbook.Connections(1) yerine Activeworkbook.Connections(1).ODBCConnection üzerinde işlem yapıyoruz.

Connection’ı kendisiyle ilgili olarak tek ihtiyaç duyacağımız bilgi connection’ın tipidir. Yani ODBC ise farklı işler olsun OLEDB ise farklı şeyler olsun demek için tip bilgisine ihtiyaç duyarız. Bunları da yukarıda görmüştük; ODBC için tip xlConnectionTypeODBC olup bunun nümerik değeri 2'dir. Buna property Activeworkbook.Connections(1).Type şeklinde ulaşabiliriz

Bu tip bilgisine ulaştıktan sonra connection tipine göre ilgili property seçilir: Activeworkbook.Connections(1).ODBCConnection gibi. Bunun da sadece birkaç propertysine ihtiyaç duyacağız. En önemlisi, bize ConnectionStringi veren Connection propertysi’dir. Gördüğünüz gibi ConnectionString diye bir property yok onun yerine Connection var: Activeworkbook.Connections(1).ODBCConnection.Connection

Mesela connection tipi ODBC ise ConnectionString ... olsun demek için aşağıdaki kodu yazarız.

If Activeworkbook.Connections(i).Type = 2 Then
   Activeworkbook.Connections(i).ODBCConnection.Connection="....."
End If

Bu ConnectionString’i elde etmenin bir yolu daha var, bunu ilgili kısımda ayrıca göreceğiz ancak ön bilgi adına aşağıda veriyorum,  QueryTable'lar üzerinden:

ActiveSheet.ListObjects(1).QueryTable.Connection 'access, oracle v.s
'Veya text/web connection ise Doğrudan QT üzerinden(web ve txt con için)
ActiveSheet.QueryTables(1).Connection			

Refresh işlemleri

Workbook nesnesinin RefreshAll adında bir metodu vardır, ilgili dosyadaki tüm bağlantıları (ve pivot tabloları) refreshler. Bu bazı durumlarda işimizi gören pratik bir yöntemdir. Özellikle bu metod, kodumuzun son satırı yer alıyorsa. Mesela uzunca bir işten sonra ActiveWorkbook.RefreshAll derseniz, dosyadaki tüm bağlantılar sorunsuzca refreshlenir.

Ancak(aslında büyük bir ancaaaak), bu metod asenkron(asynchronous) bir metoddur. Yani, refresh işlemleri henüz bitmeden sonraki satırlar okunmaya devam edilir. Bu da her zaman olmasa da bazen sorunlara neden olur. Zira arkasından bir kaydetme işlem gibi bir işlem varsa  hatalara ve kitlenmelere neden olabilir.

O yüzden kodun son satırı olmadığı sürece refresh işlemlerini connection nesnesi üzerinden(bir döngü içinde tüm bağlantıları ele alacak şekilde) yapmayı tercih etmeliyiz, workbook nesnesi üzerinden değil. Böylece tüm bağlantılar refresh olana kadar beklemiş oluruz. Zira Connection nesnesinin Refresh metodu, BackgroundQuery özelliğine False atandığında synchronous çalışır, yani bir sonraki satıra geçmeden önce mevcut satır icrası beklenir.

Sub AlternatifRefreshAll()
    'Önceki kodlar
 
    For Each cn In ActiveWorkbook.Connections
        cn.ODBCConnection.BackgroundQuery = False 'kodun synchronous işlemesini sağlar
        cn.Refresh
    Next
 
    'Sonraki kodlar
End Sub	

Örneğin aşağıdaki tabloda mevcutta bulunan kayıt sayısını(listobjectin row sayısı) yazdırmak istiyoruz diyelim. Önce mevcudu yazdıracağız, hemen arkasından DAO ile bir kayıt ekleyeceğiz ve hemen arkasından tekrardan kayıt sayısını okuyacağız.

Bunun için kodumuzu şu şekilde hazırlarsak

Sub async_refresh()
Dim ws As Worksheet
Set ws = ActiveSheet

Debug.Print "Yeni kayıt öncesi:" & ws.ListObjects(1).ListRows.Count, Time
Call KayıtEkle 'Kayıt ekleme işini yapan prosedür
ActiveWorkbook.RefreshAll 'yaklaşık 15-20 sn çalışır
Debug.Print "Yeni kayıt sonrası:" & ws.ListObjects(1).ListRows.Count, Time
End Sub		

Bunun çıktısı şöyledir:
  Yeni kayıt öncesi:334 21:46:17
  Yeni kayıt sonrası:334 21:46:18

Gördüğünüz gibi, uzun süren refresh işini beklememiş ve önceki kayıt sayısı ile sonrakini aynı vermiş ve bunu 1 sn içinde yapmıştır.

Ancak güvenli yöntem ile çalıştırırsak,

Sub sync_refresh()
Dim con As WorkbookConnection
Dim ws As Worksheet
Set ws = ActiveSheet

Debug.Print "Yeni kayıt öncesi:" & ws.ListObjects(1).ListRows.Count, Time
Call KayıtEkle
For Each con In ThisWorkbook.Connections
  con.ODBCConnection.BackgroundQuery = False
  con.Refresh
Next con
Debug.Print "Yeni kayıt sonrası:" & ws.ListObjects(1).ListRows.Count, Time
End Sub

Sonuç şöyle çıkacaktır:
  Yeni kayıt öncesi:336 21:47:57
  Yeni kayıt sonrası:337 21:48:06

Görüldüğü üzere 9 sn'lik refresh işlemi beklenmiş ve kayıt sayısının 1 arttığı gösterilmiştir.

"KayıtEkle" prosedürümüz de şöyledir.

Sub KayıtEkle()
Dim db As Database
Dim rs As Recordset

Set db = DAO.OpenDatabase("buraya dosya adresini yazın")
Set rs = db.OpenRecordset("Anadata", dbOpenTable)
rs.AddNew
rs.Fields(0) = 2
rs.Fields(1) = 1
rs.Fields(2) = 2
rs.Fields(3) = WorksheetFunction.RandBetween(1000, 2000)
rs.Fields(4) = WorksheetFunction.RandBetween(1, 1000000)
rs.Update
rs.Close
db.Close

End Sub

ÖNEMLİ: RefreshAll yapılması durumunda ortaya çıkan sorunlar için birçok sitede önerilen DoEvents, Wait, Sleep yöntemlerinin hiçbiri işe yaramaz. Bu işin tek çözümü budur. İlgilenenler buradan da detay baklabiliriler.

ListObject ve QueryTable

Data erişim yolları

Excel’den dış dataya çeşitli yollardan ulaşabiliyoruz. Bunların türlerini Excel ana menüsündaki şu sayfadan ulaşabilirsiniz. Bizim için önemli olan hangi yöntemle yaratıldığında (2013 ve 2016’da yeni gelen Get&Transform, New Query yöntemleri dahil) nereler değişiyor, ne tipte bir bağlantı oluşuyor detayı. Çünkü oluşan bağlantı tipinin ODBCConnection mı yoksa OLEDBConnection mı olduğuna bağlı olarak kodlarımızda ayarlama yapmamız gerekcek.

Genel olarak bakıldığında;

  • MS Query her zaman ODBC yaratır
  • Data_Conn_Wizard:(2016'dan itibaren Legacy)
                ODBC DSN-->ODBC
                Other/Advanced-->OLEDB
  • Data_Import_Access:OLEDB
  • Txt import:TEXT
  • SQLServer:OLEDB
  • PowerQuery:OLEDB(Mashup)

NOT:ODBC’de CommandType her zaman SQL’dir, yani xlCmdSql. OLEDB’de ise SQL olabileceği gibi doğrudan bir tablo/query de olabilir.

ListObject ve QueryTable türleri

ListObject dediğimiz nesne aslında Table sayfasından gördüğümüz Table alanımızdır.  Bu nesnenin Name özelliğine bakınca Table menüsündeki ismin aynısını görürüz.(ListObject’in eski adı zaten TableList idi, sonradan ListObject olmuş.)

İki tür listobject olabilir:

  • Data bağlantılı: Böyleyse bunun QueryTable propertysi vardır, ve refreshelenebilir.
  • Data bağlantısız: FormatAsTable ile Table yapıp uygulanmıştır. Bunun bir data bağlantısı yoktur. Querytable özelliği de yoktur, dolayısıyla refreshlenemez.

Biz doğal olarak burada ilk tür Listobject ile ilgileneceğiz.

QueryTable’a gelince, o her halükarda refreshable data içerir. ListObject’deki gibi QueryTable’ın da iki türünden bahsedebiliriz.

  • ListObject ile ilişkili: Access, Oracle gibi DB’lerden kurulan bağlantılar.
  • ListObject ile ilişkisiz: Text ve Web bağlantıları tek başına bulunan(standalone) QueryTable’lar üretir.

Excelde bir bağlantı kurduğumuzda bu bir text veya web bağlantısı değilse ListObject ve QueryTable ikisi de otomatikman oluşur, ister VBA içinden ister Excel menülerden oluşturmuş olalım farketmez. Yani VBA’de standalone ListObject yaratsak bile ona eşlik eden QueryTable de yaratılmış olur.

ListObject’e eşlik eden QueryTable’lar Worksheet.QueryTables koleksiyonun bir parçası değildir. Bunlara sadece ListObject.QueryTable özelliği ile erişilebilir.

Access,Oracle veya SQL Server gibi DB bağlantılarında Listobject'in bir adı varken, QueryTable'ın adı olmaz.(QueryTable'ın Name propertysi sadece text ve web queyler için var)

Yani bir text bağlantısı(Standalone Querytable) ve bir Oracle bağlantsı içeren bir dosyada şu sonuçları elde ederiz.

Debug.Print ActiveWorkbook.Connections.Count '2 (ikisi de)
Debug.Print ActiveSheet.ListObjects.Count '1 (Oracle)
Debug.Print ActiveSheet.QueryTables.Count '1 (Text)

Ama dosyamızda aslında iki tane QueryTable var, fakat bunlardan biri Listobject'n query'si. Bunlarda ayrı ayrı ilgil Listobject'in QueryTable özelliği ile ulaşaılabilir ama tek seferde hepsini saydıracak bir Count özelliği yok, zaten buna  gerek de yok, Listobject sayısı kadar Listobjecte bağlı QueryTable sayısı vardır. 

Yine mesela aşağıdaki kod bi Oracle bağlantısı olan dosyada hiçbirşey yazmazken text bağlantısı olan bir dosyada QueryTable’nin adını yazar.

Sub LOQT()
Dim ws As Worksheet
Dim q As QueryTable
 
Set ws = ActiveSheet
For Each q In ws.QueryTables 'bu bize standalonelarınkini verir
    Debug.Print q.Name
Next q
End Sub

Yeni yaratımlar

Yukarıda kısaca değindiğimiz data erişim yollarından biri kullanılarak yeni bir bağlantı yaratabiliyoruz. Bu yeni yaratımlara çeşitli örnekleri aşağıda bulabileceğiniz gibi Makro Kaydetme aracı ile kendiniz de görebilirsiniz. Tüm yapmanız gerekn Record Macro tuşuna bastıktan sonra bağlantınızı kurmak ve sonrasında kodu incelemek. Tabi makro kaydedeci gereksiz uzunlukta bir kod üretmektedir, zira tüm parametrelere değer atamak gibi bir özelliği var bu aracın. Siz burada gereksiz parametre atamalarını çıkarın, diğerlerine otomatikman default değerleri atanacaktır. Array şeklindeki CommandText'i de tek bir string olarak yazın.

Örneğin Access'ten MS Query ile bir bağlantı kurduğumuzda aşağıdaki gibi bir kod oluşmakta

    Application.CutCopyMode = False
    With ActiveSheet.ListObjects.Add(SourceType:=0, Source:=Array(Array( _
        "ODBC;DSN=MS Access Database;DBQ=C:\inetpub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar\vbadb.accdb;DefaultDir=C:\inetp" _
        ), Array( _
        "ub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar;DriverId=25;FIL=MS Access;MaxBufferSize=2048;PageTimeout=5;" _
        )), Destination:=Range("$A$1")).QueryTable
        .CommandType = 0
        .CommandText = Array( _
        "SELECT Data.Bölge, Data.`Şube Adı`, Data.`Ürün Adı`, Data.`Aylık Gerç`" & Chr(13) & "" & Chr(10) & "FROM Data Data" _
        )
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .PreserveColumnInfo = True
        .ListObject.DisplayName = "Table_Query_from_MS_Access_Database"
        .Refresh BackgroundQuery:=False
    End With

Biz bunu maneul olarak yazmak istediğimizde aşağıdaki gibi çok daha sade yazarız.

Sub yeni_access_LO_odbc()
Dim constr As String
Dim qt As QueryTable

constr = "ODBC;DSN=MS Access Database;DBQ=C:\inetpub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar\vbadb.accdb;DefaultDir=C:\inetpub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar;DriverId=25;FIL=MS Access;MaxBufferSize=2048;PageTimeout=5;"
' OLEDB versiyonu--> constr = "OLEDB;Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\......."

Set qt = ActiveSheet.ListObjects.Add(SourceType:=0, Source:=constr, Destination:=ActiveCell).QueryTable
 
With qt
    .CommandType = xlCmdSql 'recordmacro sırasında 0 yazar, aynen brakabilecğeiniz gibi manuel yazarken intellisense çıkar ve bunu seçersiniz
    .CommandText = "select * from data" 'Record macroda otomatik oluşan Array yerine tek bir string içine yazarsınız
    .ListObject.Name = "Acces bağlantı"
     'Diğer öğeleri pas geçebiliriz, böylece default değerleri atanır
    .Refresh BackgroundQuery:=False
End With
    
End Sub	

Bu arada eğer dataya erişim yöntemi olarak Data>Get&Transform>From Database diyip Access seçerseniz, üretilen kod biraz daha farklı olmakta, zira burada Power Query devreye sokulmaktadır.

 ActiveWorkbook.Queries.Add Name:="Data (2)", Formula:= _
        "let" & Chr(13) & "" & Chr(10) & "    Source = Access.Database(File.Contents(""C:\inetpub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar\vbadb.accdb""), [CreateNavigationProperties=true])," & Chr(13) & "" & Chr(10) & "    _Data = Source{[Schema="""",Item=""Data""]}[Data]" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & "    _Data"
    ActiveWorkbook.Worksheets.Add
    With ActiveSheet.ListObjects.Add(SourceType:=0, Source:= _
        "OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=""Data (2)"";Extended Properties=""""" _
        , Destination:=Range("$A$1")).QueryTable
        .CommandType = xlCmdSql
        .CommandText = Array("SELECT * FROM [Data (2)]")
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .PreserveColumnInfo = True
        .ListObject.DisplayName = "Data__2"
        .Refresh BackgroundQuery:=False
    End With				

Biz şimdilik MS Query yöntemiyle ilerleyeceğiz. Zira birçok kullanıcıda şuan için PowerQuery entegrasyonunun olmadığını varsayabiliriz. Gerçi olsa da farketmez, kodlar büyük ölçüde aynı, sadece connection strginlerinde küçük farklar sözkonusu.

Standalone bağlantı

Öncelikle standalone(kendi başına) bağlantılara bakalım.

Bu örneğe bakarken hali hazırda üzerinde bağlantı olan bir dosya ile çalışmanızı tavsiye ederim. Bu dosya sizin kendinize ait bir dosya olabileceği gibi en üstte benim linkini verdiğim örnek dosya da olabilir.

Sub standalone_LO_QT()
'F8 ile tk tek çalıştırın
Dim ws As Worksheet
Dim qts As QueryTables
Dim qt1 As QueryTable, qt2 As QueryTable, yeniqt As QueryTable
Dim los As ListObjects
Dim lo1 As ListObject, lo2 As ListObject, yenilo As ListObject

Set ws = ActiveSheet
Set dest = ActiveCell

'1)*******QueryTable işlemleri************Bunlar standalone QueryTable'ler içindir.(Text dosyası gibi)
'1a)Mevcutlara erişim:Mevcutta standalone Querytable bulunan bir sayfada çalışın
Set qts = ws.QueryTables
Set qt1 = qts.Item(1) 'veya item kullanılmayadan doğrudan qts(1) yazılabilir
Set qt2 = qts("ExternalData_4") 'ismiyle de ulaşaibliriz

'1b)Yeni yaratım
Set yeniqt = qts.Add(connection:="TEXT;" & adres & "hatalog.txt", Destination:=dest)
yeniqt.Refresh

'!!!!!!!!!!!!!!!!!!!!!!----şimdi başka bir boş hücre seçin-----------!!!!!!!!!!!!!!!!
Set dest = ActiveCell

'2)*******standalone ListObject işlemleri*********(Access, Sqlserver, oracle v.s)
'2a)Mevcutlara erişim:Mevcutta listobject bulunan bir sayfada çalışın(standalone olup olmadığı farketmez)
Set los = ws.ListObjects
Set lo1 = los.Item(1) 'veya item kullanılmayadan doğrudan los(1) yazılabilir
Set lo2 = los("Table_Query_from_MS_Access_Database") 'ismiyle de ulaşaibliriz

'2b)yeni eklemek için querytable lazım zira refreshlenen şey aslında querytabledır. o yüzden bu madde standalon listobject içermez
strSource = "ODBC;DSN=MS Access Database;DBQ=" & adres & "vbadb.accdb;DefaultDir=" & _
                adres & ";DriverId=25;FIL=MS Access;MaxBufferSize=2048;PageTimeout=5;"

Set yenilo = los.Add( _
    SourceType:=xlSrcExternal, _
    Source:=strSource, _
    LinkSource:=True, _
    XlListObjectHasHeaders:=xlGuess, _
    Destination:=dest)
    
With yenilo.QueryTable
    .CommandText = "SELECT Data.Bölge, Data.`Şube Adı`, Data.`Ürün Adı`, Data.`Aylık Gerç`" & Chr(13) & "" & Chr(10) & "FROM Data"
    .CommandType = xlCmdSql
    .Refresh BackgroundQuery:=False
End With
'Ancak yaratım olduktan sonra standalone listobject refreshlenebilir
yenilo.Refresh

End Sub	

ListObject ile ilişkili bağlantılar

Aşağıkida örnekte ListObject ilişkili bir QueryTable'la ilgili çeşitli özelliklere bakıyoruz.

Sub qt_ilişkili_lo()
Dim lo As ListObject
Dim loqt As QueryTable

Set lo = ActiveSheet.ListObjects.Item(1)
Set loqt = lo.QueryTable

Debug.Print loqt.Connection 'connection string verir
Debug.Print loqt.CommandText 'SQLi verir
'Debug.Print loqt.FieldNames 'bişey dğeil, true/false, isimler gürnümsünmü
Debug.Print loqt.ListObject.Name 'Table_Query_from_MS_Access_Database7
'Debug.Print loqt.Name 'hata verir
Debug.Print loqt.QueryType '1 yazar. 1:xlODBCQuery 2: xlDAORecordset 7: xlADORecordset 5: xlOLEDBQuery 6: xlTextImport
Debug.Print loqt.WorkbookConnection.Name 'Query from PDWH_USR11
Debug.Print lo.DisplayName 'Query from MS Access Database1
Debug.Print lo.ListColumns.Count
Debug.Print lo.ListColumns(5) 'son eklediğim manuel alanı da saydı
Debug.Print lo.Name 'Table_Query_from_MS_Access_Database7
'Debug.Print lo.QueryTable.Name 'hata verir
Debug.Print lo.Range.Address 'son yeni eklenen kolon dahil verir
Debug.Print lo.SourceType '3 3:xlSrcQuery, 0:xlSrcExternal 1:xlSrcRange
loqt.Refresh 'lo.refresh de olur

End Sub

Text dosyası bağlantısı

Sub txt_yeni_con()
'sadece QueryTable, ListObject olmaz
    kaynak = "TEXT;Q:\.......\kredilog.txt"
    Set qt = ActiveSheet.QueryTables.Add(Connection:=kaynak, Destination:=ActiveCell)
    qt.Name = "kredi sorgusu"
    qt.Refresh
End Sub

Çeşitli üyeler

Unlink: Listobject'in veri kaynağıyla bağını koparır ama Table özelliği devam eder.

Unlist: Listobject'in hem veri kaynağıyla bağı koparır hem de Table özelliği bozulur, yani Table formatından normalde Range’e döndürür. Gerçi zebra desen olarak yine kalır ama fonksiyonalite olarak artık Table gibi davranamaz. Yani herhangi bir yere yeni satır eklenince zebra deseni de bozmuş olursunuz. (Table seçiliyken Design menüsündeki "Convert to Range" ile aynı işi yapar)

Ben daha çok Unlinki tercih etmenizi tavsiye ederim. Ne de olsa Table candır.

ListColumns: Bu özellik de yine ListObject'e ait olup, çoğu durumda çektiğimiz veri kaynağındaki kolon sayısını elde etmede kullanılır. Ancak bazı durumlarda biz Excel üzerinde de yeni kolonlar ekleyebilir ve bu kolonda çeşitli hesaplamalar yapabiliriz. Örneğin ListObjectin altında yatan QueryTable’ımıza baz teşkil eden SQL metni (veya tablo/query) 5 kolondan oluşuyor diyelim. Sonuç döndükten sonra biz bir de formül yazacağımız 6.kolon açarsak, ListObject’in kolon sayısı(ListColumns.Count) 5 değil 6 olacaktır.

ListRows: İlgili ListObject'te kayt sayısıyla ilgili işlemler yapmayı sğalayan collectiondır. Bunun da Count property'si ve index alma özelliği kullanılabilir. Toplam kayıt sayıt sayısını veren B ve son kaydı silen lo.ListRows(lo.ListRows.Count).Delete gibi.

Diğer üyelerini de Macro Recorder ile Table menüsündeki ayarlarla oynayarak kendinizi görebilirsiniz ama en önemlileri bunlardı diyebilirim.

Refresh

Bir Table'ı refreshleme 2 türlü olabilir.

1)ListObject veya QueryTable üzerinden

Refresh işlemleri için QueryTable’nin de ListObject’in de Refresh metodu kullanılabilir. Bu bağlamda Activesheet.ListObjects(1).QueryTable.Refresh ile  Activesheet.ListObjects(1).Refresh tamamen özdeştir.

'i)Text ve Web dışı kaynaklarda
ActiveSheet.ListObjects(1).QueryTable.Refresh BackgroundQuery:=False
'veya
ActiveSheet.ListObjects(1).Refresh BackgroundQuery:=False
 
'ii)Text veya web kaynaklarda
ActiveSheet.QueryTables(1).Refresh BackgroundQuery:=False

2)Doğrudan connection üzerinden gidilebilir

Bu yöntemi yukarıda connections konusunda görmüştük.

ActiveWorkbook.Connections(1).ODBCConnection.BackgroundQuery = False
ActiveWorkbook.Connections(1).Refresh

NOT: Her iki grupta da senkron bir refresh işlemi için BackgroundQuery özelliklerine False değeri atanmıştır.

Şifre Güvenliği

ListObject ve QueryTable kullanmanın bir sakıncası veritabanı bağlantı şifrelerinizin Excel içinde gömülü olarak bulunması olacaktır. Bunun ele geçmemesi için alınabilecek birkaç önlem olmakla birlikte bilgili (ve kötü niyetli) bir kullanıcı tarafından ele geçirilebilir. Bu önlemlere aşağıda yer verilmiştir.

  • Bir sayfa Activesheet.Visible= xlVeryHidden yapılıp şifre buradan okunabilir.
  • Ribbon ilgili dosya aktifken geçici olarak kapatılabilir. Böylece Data veya Table menüsünden Properties butonuna tıklayamazlar.
    Private Sub Workbook_Activate()
       If Environ("UserName") = 12345 Then Exit Sub
    	Application.ExecuteExcel4Macro "SHOW.TOOLBAR(""Ribbon"",False)"
    	Application.DisplayFormulaBar = False
    	Application.DisplayStatusBar = 	Not Application.DisplayStatusBar
    	ActiveWindow.DisplayWorkbookTabs = False
    End Sub
    '------------------------
    Private Sub Workbook_Deactivate()
        If Environ("UserName") = 12345 Then Exit Sub
    	Application.ExecuteExcel4Macro "SHOW.TOOLBAR(""Ribbon"",True)"
    	Application.DisplayFormulaBar = True
    	Application.DisplayStatusBar = True
    	ActiveWindow.DisplayWorkbookTabs = True
    End Sub
    '-------------------------
    Private Sub Workbook_Open()
        If Environ("UserName") = 12345 Then Exit Sub
         MsgBox "Ribbon bu dosya aktif olduğu sürece devre dışı olacaktır. Başka bir excel dosyasına geçerseniz tekrar aktif olacaktır."
    End Sub
  • Unlist veya Unlink kullanımı: Sözkonusu dosya günlük oluşan bir rapor ise şifreniz Format dosyanızda durur, dosya hergün kaydolurken ilgili günün tarihi ile kaydolur ve bu dosyada Unlist/Unlink yaparak şifrenin görünmesini engellemiş olursunuz. Ancak Format dosyasında şifreniz hala kalacaktır.

    Kullanıcıların şifreyi içeren Format dosyasını açmasını engellemek için de ilgili dosyanın Workbook_Open markosuna sizden başkasının açmasını engelleyecek bir kod koyabilirsiniz. Üstelik bu tür giriş denemelerini Logger gibi bir fonksiyonla da kayıt altına alabilirsiniz. (Tabi kötü niyetli kullanıcı makroları disable yaparak bu engeli de aşabilir.)
    If readonlykontrol = True Then
        If Environ("UserName") <> 12345 Then 'bu sizin siciliniz olsun
            If InStr(ActiveWorkbook.Name, "Format") > 0 Then
                Logger WorksheetFunction.Rept(" ", 50 - Len(raporad)) & raporad, "İzinsizGiriş", 0, Environ("UserName") + " nolu sicil Q'dan giriş yapmaya çalıştı"
                MsgBox "Hey dostum, ortalığı karıştırmayı bırak. Format dosyama sakın bir daha dokunayım deme"
                ActiveWorkbook.Close savechanges:=False
                Exit Sub
            End If
        End If
    End If
  • ListObject ve QueryTable’yi VBA ile oluşturmak: Son çözüm, Workbook_Open içinde ilgili bağlantıların yaratıldığı kodları yazmaktır. VBA projesine de Protection koydunuzmu işlem tamamdır. Bu da kırılamaz bir şifre değildir tabi ancak bu hinlikleri bilen biri tarafından kırılabilir.
  • Çözüm değil ancak alternatif olarak yukarıdaki ADO ve DAO yöntemlerini kullanmak da işinize yarayabilir. Ancak SQL çok çok çoooook uzunsa bunu direkt VBA ortamında hazırlamak sıkıntılı olabilir, onun yerine SQL kısmını bir txt/sql uzantılı dosyasından okutmakta fayda var.

Hayalet Protokol: Application.OnTime ile Scheduling işlemleri

Aşağıdaki kodlar benim günde yaklaşık 40 raporun kendiliğinden çalışmasını, refresh olmasını ve yeni günün tarihiyle kaydedilmesini, gerekiyorsa ilgili alıcılara maille iletimini sağlar. (NOT:Ben yerimde yokken departmaan benden mailler geldiğinde arkadşaların "Volkan sen yokken senin hayaletin ortalıkta dolaşıp mailler atıyordu" demesinden kaynaklı olarak bu sistemin adını Hayalet Protokol olarak belirledim.)

Gerek konu gerek kodlar ilk başta karışık gelebilir. Anlaşılırlığı artırmak adına kodlardan hemen önce bir akış diyagramı da ekledim. Udemy'deki eğitimime kaydolursanız orada görsel olarak da akışı takip edebilirsiniz. Düzenli rapor üreten biriyseniz bu konuyu mutlaka anlamaya çalışın. Size ve bölümünüze sunacağı fayayı tahmin bile edemezsiniz. En az 3-4 adamlık tasarruftan bahsediyorum. Şahsen tüm sitede benim en önem verdiğim konu burasıdır.

Ön bilgiler:

  • İki bilgisayarla çalışıyorum. Biri, sadece birkaç makronun çalıştığı PC'm, biri de schedule edilmiş işlerin ağırlıklı olarak çalıştığı laptop.
  • Kodların bazısı her iki makinada da çalışıyor, bazısı sadece PC'de, bazısı da sadece laptopta
  • PC'de çalışanlar genelde mesai saati dışında çalışıp bitiyor. Gün içinde çalışanlar da hep laptopta çalışıyor.
  • Diyelim ki sicilim 12345. PC ismim B12345, laptop ismim L12345
  • Raporların bir kısmı Veri Ambarı(kısaca DWH) tablolarına SQL atıyor, bazısı DWH'tan türeyen datamartlara, bazıları da online sistemlere. Online olanlar için herhangi bir "tablo dolmuş mu" kontrolü yapmıyorken, DWH ve ondan türeyen datamartlar için dünün datası dolmuş mu diye kontrol yapıyorum
  • İlk kod bloğunda kapatOnMakroPC diye bir modül var. Bunun hikayesi şu. Her akşam laptopta(makroPC) Exceli otomatik kapattırıyorum, ki güncel Personal.xlsb ve Schedule.xlsb dosyaları laptopa yedeklenebilsin. Böyelce kendi PC'mde shcedule ayarlarında bir değişiklik yaptığımda veya Personal.xlsb dosyama yeni bir prosedür eklediğimde laptopa da aynen geçmesini sağlıyorum ki işlerde aksama olmasın. Bunun için PC'de bu iki dosyayı ne zaman kaydetsem, bunların "Workbook_AfterSave" eventi devreye giriyor ve dosyaların güncel halini ortak alana da kaydediyor. Laptopta da Windows Task Scheduler ile her gece yarısı önce yedeklemeyi yaptırıyorum, sonra Excel'i tekrar açtırıyorum
  • Hem PC'de hem laptop'ta Personal.xlsb ve Schedule.xlsb dosyam XLSTART klasöründe yer alıyor, böylece Excel açılır açılmaz onlar da gizli olarak açılıyorlar
  • Keza, her iki bilgisayarda da bilgisayar açılır açılmaz Excel'in de açılmasını sağlayan bir kurgu var. Bunu windows ayarlarından siz de yapabilirsniz.
  • Bundan sonra süreç şu şekilde devam ediyor.
    • Schedule dosyasının Workbook_Open eventi devreye giriyor, ve bu event de StartTimer prosedürünü çalıştırıyor.
    • Bu prosedür, Dwh(Veri ambarı) dolmuş mu yani güncel data yüklenmiş mi kontrolünü yapıyor.
    • Dwh dolmadıysa 10 dk sonrasına kendisini tekrar schedule ediyor, dwh dolduysa "dwh raporları" isimli prosedürü çalıştııryor
    • Dwhçılar içindeki tüm prosedürler sırayla çalıştırılıyor.
    • Varsa diğer datamart kontrolleri de aynı süreçte yapılmaktadır.

Akış şeması

Bu şemayı Udemy kursum için hazırlamıştım. Videoda animasyonlu olarak teker teker uygun sırada açıldıkları için oradan bakmanızı da tavsiye ederim.

Aşağıdaki DWH(HR) diye geçen şey, bilgisayarıma kurduğum Oracle veritabanındaki HR şeması. Temsili olarak DWH, yani veri ambarı tabloları bu şemada olacak, Volkan şemasında da DM yani datamart olacak. Bunlardaki tablolar dolmuş mu diye kontrol edeceğiz.

Schedule dosyasını buradan indirebilirsiniz. Açılır açılma çalışmasını engellemek için Workbook_Open'ın başlangıcına geçici olarak Exit Sub satırı ekledim.

Şimdi bir de örnek klasörlere bakalım.

Aşağıdaki vereceğim tüm prosedürler Schedule.xlsb dosyamda bulunmaktadır. Şimdi, ilk olarak Workbook_Open prosedürüne bakalım.

Private Sub Workbook_Open()
    ilksaat = Date + TimeSerial(23, 30, 0)
    
    'Debug.Assert False
    
    'PC'yi mesai başlangıç saatinden sonra açmışsak kontrol raporları hemen başlasın, diğerlerine gerek yok
    If TimeValue(Now) > ilksaat Then
        Application.OnTime Now + TimeSerial(0, 0, 10), Procedure:="dwh_kontrol"
        Application.OnTime Now + TimeSerial(0, 0, 20), Procedure:="dm_kontrol"
    Else 'gece 00:00'da programlandığı saatte açıldıysa
        Call StartTimer
    End If
End Sub

Şimdi de StartTimer prosedürünü inceleyelim. Bu, schedule işlemlerini ele aldığım ana prosedürdür.

Sub StartTimer()

'anapc veya makropc dışında bir bilgisayarda çalışırsa çıksın
If Environ("computername") <> volkanpc And Environ("computername") <> makropc Then Exit Sub

'hem anapc hem makropcde şunlar çalışsın
Application.OnTime Now + TimeSerial(24, 0, 0), Procedure:="StartTimer"

If Environ("computername") = volkanpc Then
    Application.OnTime TimeValue("00:05:00"), Procedure:="eskilerisil"
    Application.OnTime ilksaat, Procedure:="dwh_kontrol"
    Application.OnTime ilksaat + TimeSerial(0, 1, 0), Procedure:="dm_kontrol"
    Application.OnTime TimeValue("11:00:00"), Procedure:="AccessRaporu"
    Application.OnTime TimeValue("11:30:00"), Procedure:="SQLServerRaporu"
Else 'makropc
    '.....
    Application.OnTime TimeValue("23:55:00"), Procedure:="kapatOnMakroPC" 'gece diğer pclere yedekleme yapılsın diye dosyayı kapatmamız gerekiyor
End If

End Sub

Şimdi de kontroller modülündeki prosedürlere bakalım. Burada da kalabalık olmaması adına tüm kontrol prosedürlerini koymadım, onun yerine iki farklı yöntemi göstermesi adına iki prosedürü verdim. İlkinde , BT ekiplerinden gelen "Tablo doldu" tarzındaki mailin varlığını kontrol ediyorum. İkincisinde ise farklı dinamiğe sahip olduğundan "Tablo doldu" maili gelmeyen bir datamart için bir tabloya SQL atıp kayıt dönüp dönmediğine bakıyorum.

'1.yöntem
Sub dwh_kontrol()
Const raporad As String = "dwh_kontrol"
Const adresKontrol As String = "yükleme kontrol\dwh\dwhyüklemekontrol - "
Dim outApp As New Outlook.Application
Dim ns As Outlook.Namespace
Dim myroot As Outlook.Folder, klasör As Outlook.Folder
Dim eItems As Outlook.Items
Dim eItem As Outlook.MailItem
Dim fso As New FileSystemObject

'Debug.Assert False
Logger Now, Environ("computername"), raporad, "Kontrol", 0, "Girildi"
On Error GoTo hata

If Mevcutmu(adres & adresKontrol & Date - 1 & " Sonuçları.txt") Then
    Logger Now, Environ("computername"), raporad, "Kontrol", 0, "ZatenOK"
    Call dwh_raporları
    Exit Sub
End If

AlertUpdatingEvent False, False 'normalde commentsiz olmalı

Set ns = outApp.GetNamespace("MAPI")
'hem benim hem serkanın pcsinde aynı klasör olduğunu varsayıyorum
Set myroot = ns.Folders("volkan.yurtseven@hotmail.com")
Set klasör = myroot.Folders("Kontrol Mailleri")

Set eItems = klasör.Items

Kriter1 = "[Subject]=""DWH yüklemesi tamamlanmıştır"""
Kriter2 = "[ReceivedTime]>'" & Date & "'" 'Tarihin saat kısmından dolayı > olmalı
Set kriterliler = eItems.Restrict(Kriter1).Restrict(Kriter2)
 
If kriterliler.Count < 1 Then 'maili bulamadıysak 10 dk sonra yeniden schedule ederiz
   Logger Now, Environ("computername"), raporad, "Kontrol", 0, "Reschedule"
   Application.OnTime Now + TimeSerial(0, 10, 0), Procedure:="dwh_kontrol"
Else 'mail varsa sürece devam
    fso.CreateTextFile adres & adresKontrol & Date - 1 & " Sonuçları.txt"
    Call MailBilgi("DWH Kontrolü", anaAlicilar)
    Logger Now, Environ("computername"), raporad, "Kontrol", 0, "OK"
    Call dwh_raporları
    GoTo çıkış
End If

çıkış:
Set myroot = Nothing: Set klasör = Nothing
Set ns = Nothing: Set outApp = Nothing
Set eItems = Nothing: Set eItem = Nothing: Set fso = Nothing
AlertUpdatingEvent True, True
Exit Sub

hata:
AlertUpdatingEvent True, True
Set myroot = Nothing: Set klasör = Nothing
Set ns = Nothing: Set outApp = Nothing
Set eItems = Nothing: Set eItem = Nothing: Set fso = Nothing
Logger Now, Environ("computername"), raporad, "Kontrol", Err.Number, Err.Description

End Sub
'----------------------------------------------------------------
'2.yöntem
Sub dm_kontrol()
Const raporad As String = "dm_kontrol"
Const adresKontrol As String = "yükleme kontrol\dm\dmyüklemekontrol - "

'Debug.Assert False
Logger Now, Environ("computername"), raporad, "Kontrol", 0, "Girildi"

AlertUpdatingEvent False, False 'normalde commentsiz olmalı
On Error GoTo hata

If Mevcutmu(adres & adresKontrol & Date - 1 & " Sonuçları.xlsb") Then
    Logger Now, Environ("computername"), raporad, "Kontrol", 0, "ZatenOK"
    Call dm_raporları
    Exit Sub
End If

Workbooks.Open adres & adresKontrol & "Format.xlsb" 'workbook_open kodu çalışacak

'veritabanı yüklenmediyse 10 dk sonraya reschedule
If Not Mevcutmu(adres & adresKontrol & Date - 1 & " Sonuçları.xlsb") Then
    ActiveWorkbook.Close savechanges:=False
    Logger Now, Environ("computername"), raporad, "Kontrol", 0, "Reschedule"
    Application.OnTime Now + TimeSerial(0, 10, 0), Procedure:="dm_kontrol"
    Exit Sub
Else 'başarılıysa devam edelim
    ActiveWorkbook.Close
    Kill "C:\geçici\geçici2.xlsb"
    Call MailBilgi("DM Kontrolü", anaAlicilar)
    Logger Now, Environ("computername"), raporad, "Kontrol", 0, "OK"
    Call dm_raporları
    GoTo çıkış
End If

çıkış:
AlertUpdatingEvent True, True
Exit Sub

hata:
AlertUpdatingEvent True, True
End Sub

Şimdi de dwh_raporları ve dm_raporları prosedürlerine bakalım. Burada raporlar çalışma sırasına sokuluyor.

Sub dwh_raporları()
    'Debug.Assert False
    Application.OnTime Now + TimeSerial(0, 0, 10), Procedure:="dwhrapor1"
    Application.OnTime Now + TimeSerial(0, 0, 20), Procedure:="dwhrapor2"
    Application.OnTime Now + TimeSerial(0, 0, 30), Procedure:="dwhrapor3"
End Sub

Sub dm_raporları()
    'Debug.Assert False
    Application.OnTime Now + TimeValue("00:00:10"), Procedure:="dmrapor1"
    Application.OnTime Now + TimeSerial(0, 0, 20), Procedure:="dmrapor2"
    Application.OnTime Now + TimeSerial(0, 0, 30), Procedure:="mixrapor1"
End Sub

Şimdi de online olan, ve dolayısıyla "dolmuş mu?" kontrolüne gerek olmayan prosedürlere bakalım.

Sub AccessRaporu()
    rprLog = "AccessBlg"
    rprMail = "Accesten Bölge Rakamları"
    yol = "Accesten Bölge Rakamları\Accesten Bölge Rakamları - "
    kontrol1 = vbNullString
    alıcı = diğerAlıcılar
    
    RutinKod rprLog, rprMail, yol, kontrol1, alıcı
End Sub

'-----------------------
Sub SQLServerRaporu()
    rprLog = "dwh1"
    rprMail = "dwh 1 raporu"
    yol = "Sqlserverdan site konuları\Sqlserverdan site konuları - "
    kontrol1 = vbNullString
    alıcı = diğerAlıcılar
    
    RutinKod rprLog, rprMail, yol, kontrol1, alıcı
End Sub

Şimdi dwh raporlarından birine ait prosedüre bakalım; şuan hala schedule.xlsb içindeyiz.

Sub dwhrapor1()
    'Debug.Assert False
    rprLog = "dwh1"
    rprMail = "dwh 1 raporu"
    yol = "Oracle dwh - hr şemasından country\Oracle dwh - hr şemasından country - "
    kontrol1 = "yükleme kontrol\dwh\dwhyüklemekontrol - " & Date - 1 & " Sonuçları.txt"
    alıcı = dwhRapor1Alıcıları
    
    RutinKod rprLog, rprMail, yol, kontrol1, alıcı
End Sub

Bazı raporlar ise hem dwh hem dm tablolarının yüklenmesini gerekebilir.

Sub mixrapor1()
    rprLog = "mix"
    rprMail = "Mix raporu"
    yol = "Mix raporlar\Mix rapor1 - "
    kontrol1 = "yükleme kontrol\dm\dmyüklemekontrol - " & Date - 1 & " Sonuçları.xlsb"
    kontrol2 = "yükleme kontrol\dwh\dwhyüklemekontrol - " & Date - 1 & " Sonuçları.txt"
    alıcı = diğerAlıcılar
    
    RutinKod rprLog, rprMail, yol, kontrol1, alıcı, kontrol2
End Sub

Şimdi de bu son iki kodun sonundaki RutinKod prosedürüne bakalım. Gördüğünüz üzere bu prosedür, oldukça çok parametre alıyor. Bunları anlayabileceğinizi düşünüyorum. Son parametre opsiyonel olup, mixrapor1'deki gibi, iki rapor kaynağından(dwh ve dm) da beslenen raporlar için kullanılmaktadır.

Sub RutinKod(ByVal raporLogger As String, ByVal raporMail As String, ByVal folderfile As String, _
             ByVal kontrol1 As String, ByVal alıcılar As String, _
             Optional ByVal kontrol2 As Variant)
                 
    'Debug.Assert False
    'normalde kontrol dosyalarını kontrole gerek yok. ihtiyaden kontrol yapıyoruz
    If Not Mevcutmu(adres + kontrol1) Then Exit Sub
    If Not IsMissing(kontrol2) Then
        If Not Mevcutmu(adres + kontrol2) Then Exit Sub
    End If
    
    'Daha önce oluştuysa çıkalım
    If Mevcutmu(adres & folderfile & Date - 1 & " Sonuçları.xlsb") Then Exit Sub
    
    On Error GoTo hata
    AlertUpdatingEvent False, False
    
    Workbooks.Open adres & folderfile & "Format.xlsb"  'çalışmaya başlayacak
    ActiveWorkbook.Close
    Kill "C:\geçici\geçici2.xlsb"
    Call MailBilgi(raporMail, alıcılar)
    Logger Now, Environ("computername"), raporLogger, "OK", 0, "rapor başarıyla çalıştı"
    AlertUpdatingEvent True, True
    Exit Sub
    
hata:
AlertUpdatingEvent True, True
Logger Now, Environ("computername"), raporLogger, "Hata", 0, Err.Description
End Sub

Şimdi schedule.xlsb'den çıkıp, veritabanı sorgulaması yapılacak rapor dosyasındaki koda bakalım.

Private Sub Workbook_Open()
Const raporad = "dwh-country"
Application.Run "schedule.xlsm!wbopen", raporad
End Sub

Gördüğünüz üzere kod çok kısa ve bizi tekrar schedule.xlsb'ye yönlendiriyor. Bunu böyle yaptık, çünkü yaklaşık 40 rapor içinde her birine aynı kodu yazmanın anlamı yok. Onun yerine bi kere yazıyoruz, ve her defasında bu kodu, uygun parametreyle(rapor adı) çağırıyoruz. Hadi şimdi bir de bu son koda bakalım.

Sub wbopen(ByVal raporad As String, Optional ptrefresh As Boolean = False)
    Dim lo As ListObject
    Dim con As WorkbookConnection
    Dim ws As Worksheet
    Dim pt As PivotTable
    On Error GoTo hata
    
    'Debug.Assert False
    'yetkili kişiler dışında direkt açan kişilere uyarı çıksın
    If Environ("username") <> "Volkan" And Environ("username") <> "Serkan" Then
        If ActiveWorkbook.ReadOnly = False Then
            'Date - 1'de bazen manuel değişiklikler yapmak gerekebiliyor
            If InStr(ActiveWorkbook.Name, "Format") > 0 Or InStr(ActiveWorkbook.Name, Date - 1) > 0 Then
                  Logger Now, Environ("computername"), raporad, "YetkisizGiriş", 0, ""
                  MsgBox "Dosyaya doğrudan değil, Kokpit formundan ulaşınız"
                  ActiveWorkbook.Close savechanges:=False
                  Exit Sub
            Else 'date - 2 ve öncesini açarsa sorun yok, ama burda koddan çıksın
                Exit Sub
            End If
        Else 'readonly açılmazsa yani kokpitten açılmışsa sorun yok, ama burda koddan çıksın
            Exit Sub
        End If
    End If
    
    'volkan ve serkansa devam ediyoruz. İçinde refreshlenebilir bir Table yoksa çıksın
    'excel versiyonunuza göre Queries soruglaması desteklenmeyebilir, o zaman bunu kaldırırsınız
    If ActiveWorkbook.Queries.Count = 0 And ActiveWorkbook.Connections.Count = 0 Then Exit Sub
    
    Logger Now, Environ("computername"), raporad, "WBOpen", 0, "WB giriş yapıldı"
    isim = Left(ActiveWorkbook.Name, Len(ActiveWorkbook.Name) - 14) 'Neden 14, "dd.mm.yyy - Sonuçları"nın uzunluğu
    
    'tüm connectionlar refresh
    For Each con In ActiveWorkbook.Connections
        If con.Type = xlConnectionTypeODBC Then
            con.ODBCConnection.BackgroundQuery = False
        Else
            con.OLEDBConnection.BackgroundQuery = False
        End If
        con.Refresh
    Next con
    
    For Each ws In ActiveWorkbook.Sheets
        ws.Activate
        
        If ws.ListObjects.Count > 0 Then 'refresh olacak sayfalarda
            'refresh sonucu data gelmiyorsa çıkalım
            If Range("a1").End(xlDown).Row = 2 Then GoTo iptal '??????dosya açık kalmıyor mu????
        
            'refresh sonucu data geliyorsa unlink yapalım
            For Each lo In ws.ListObjects
                lo.Unlink
            Next lo
        End If
        
        'pivot refresh
        If ptrefresh = True Then
            For Each pt In ws.PivotTables
                pt.RefreshTable
            Next pt
        End If
    Next ws

    hedef = ActiveWorkbook.Path & "\" & isim & " - " & Date - 1 & " Sonuçları.xlsb"
    saveas_işlemi_inworkbook isim, hedef
    Logger Now, Environ("computername"), raporad, "WBOpen", 0, "OK"

iptal:
'???wb close??
Exit Sub

hata:
    Logger Now, Environ("computername"), raporad, "WBOpen", Err.Number, Err.Description
    ActiveWorkbook.Close savechanges:=False
End Sub

Bu kod da çalışınca sıra, schedule kuyruğundaki diğer kodlara geçer ve işlemler aynen devam eder.

 Rapor log dosyamızın içine de bakalım.

Schedule.xlsb dosyamızdaki Thisworkbook modülünde bir kodumuz daha var, ona da bakalım. Açıklamasını en başta yapmıştık.

Private Sub Workbook_AfterSave(ByVal Success As Boolean)
    yol = "C:\inetpub\wwwroot\aspnettest\excelefendiana\Ornek_dosyalar\Makrolar\makroyedek\"
    FileSystem.SetAttr yol & Me.Name, vbNormal
    fso.CopyFile Me.FullName, yol
    FileSystem.SetAttr yol & Me.Name, vbReadOnly
End Sub

Son olarak Schedule.xlsb dosyamdaki diğer kodlar ise şöyle;

Sub AlertUpdatingEvent(a As Boolean, u As Boolean, Optional e As Boolean = True)
    With Application
      .DisplayAlerts = a
      .ScreenUpdating = u
      .EnableEvents = e
    End With
End Sub
Function Mevcutmu(adres As String) As Boolean
    On Error GoTo hata
    If GetAttr(adres) >= 0 Then Mevcutmu = True
    Exit Function
     
hata:
    Mevcutmu = False
End Function
Sub kapatOnMakroPC()
    Application.Quit
End Sub
Sub Logger(ByVal tarih As String, ByVal pc As String, ByVal rapor As String, ByVal logtipi As String, ByVal hatano As Integer, _
    ByVal açıklama As String)

    Dim dosya As String
    Dim dosyano As Integer
    
    dosyano = FreeFile()
    dosya = adres & "RaporLog.txt"
    
    Open dosya For Append As #dosyano
    Print #dosyano, tarih, pc, rapor, logtipi, hatano, açıklama
    Close #dosyano
    
End Sub
Sub MailBilgi(ByVal rapor As String, ByVal alıcılar As String)
    Dim outApp As New Outlook.Application
    Dim outMail As MailItem
    
    Set outMail = outApp.CreateItem(olMailItem)
    With outMail
        .Subject = rapor & " raporu yüklemesi"
        .Body = "Güncel " & rapor & " raporu oluşmuştur"
        .To = alıcılar
        .Send
    End With
End Sub
Sub saveas_işlemi_inworkbook(ByVal dosyaismi As String, ByVal hedef As String)
    ActiveWorkbook.SaveAs "C:\geçici\geçici.xlsb" 'dosya tipine göre kontrol de yapılabilir
    kaynak = ActiveWorkbook.FullName
    ActiveWorkbook.SaveAs "C:\geçici\geçici2.xlsb" 'dosya tipine göre kontrol de yapılabilir
    kaynak2 = ActiveWorkbook.FullName
    
    FileCopy kaynak, hedef
    Kill kaynak
    'kaynak 2 nerde kullanım?
End Sub

Sub eskilerisil()
'Ödev olacak
End Sub

Özet

Tüm bu süreç kendini besleyen otomatik bir süreçtir. Başarılı bir şekilde kurulması durumunda müthiş bir verimlilik sağlanacaktır.

Yukarıda belirttiğim üzere, konu biraz karmaşık olup, daha verimli bir şekilde algılanabilmesi için Udemy eğitimininden yararlanmanızı tavsiye ederim.

Scheduled program akışını kesintiye uğratan unsurlar

Bazen schedule edilmiş dosyalarınızı güniçinde de açmanız ve kullanmanız gerekebilir. O yüzden belli saatlerde açıldığında faklı bir davranış göstermesini isteyebilirsiniz. Keza dosyayı açan sizseniz başka, diğer kişilerse başka şekilde davranmasını isteyebilirsiniz.

Bu davranış şekillerinden biri de kullanıcıya bir mesaj göstermek olabilir. Eğer programın bir yerinde mesaj verirseniz, bu mesaj kutusundaki bir düğmeye basılana kadar ekranda kalır, bu da takip eden schedule kodlarınızın çalışmasını engellemiş olur. O yüzden MsgBox'lı uyarılarınızı Schedule zamanı içinde size kesinlikle çıkarmamalı, sadece diğer kişilere çıkarmalıdır. Gerekiyorsa kendinize bir maille bilgilendirme yaptırabilir veya Statusbara mesaj yazdırabilir veya logger prosedürüyle kayıt altına alabilirsiniz.

Benim kullandığım yöntem şudur. Kullanıcılara UserForm kullanarak Kokpit diye bir arayüz oluşturdum ve bunu onlara verdiğim Add-in'deki menüye yerleştirdim. Kullanıcılar ilgili dosyayı kokpitten açtıklarında, dosyanın readonly açılmasını sağlıyorum. Normalde herkes hep dünün raporunu açmak ister , o yüzden kokopitten de sadece dünün raporlarını açabiliyorlar. Daha eski raporları açmak için ortak alana gidip klasörden manuel açmaları gerekiyor. Şimdi öyle bir kod yazmam gerekiyor ki, dünkü raporu ortak alandan açamasınlar, zira bir rapor gün içinde iki defa farklı zamanlarda refresh ediliyor; aynı zamanda ana format dosyamın da açılmaması lazım, zira onu açarlarsa schedule makrom takılmaktadır. O yüzden dünün dosyasını veya Format dosyasını klasörden manuel açmaya çalışırlarsa uyarı verdiriyorum. Şimdi aşağıdaki kodu inceleyelim.

Private Sub Workbook_Open()
On Error GoTo hata

If Environ("UserName") <> 12345 Then 'Dosya benim dışında biri tarafından açıldıysa
    If Right(Me.Name, 11) = "Format.xlsm" Or (Right(Me.Name, 25) = Date - 1 & " Sonuçları.xlsm" And Not Me.ReadOnly) Then
        MsgBox "Dosyayı ortak alandan değil, Kokpitten açın."
        Me.Close savechanges:=False
    End If
End If

If Environ("computername") <> "L12345" Then Exit Sub 'dosyayı bazen PC'mde açıp SQL'inde veya genel sayfa düzeninde güncelleme yapmam gerekiyor. Açılır açılmaz da refresh olmasın diye laptop dışında açılırsa yani PC'mde açılırsa kalan kodlar çalışmasın istiyorum
'Diğer kodlar

End Sub

Bir diğer önemli husus da, programınızda son derece ince ayarlanmış bir hata yönetimi mekanizması olması gerekir. Detaylar için hata yakalama bölmünü iyice özümsemiş olmanızı tavise ederim. Schedule edilmiş programlarda hataları hata mesajı olark vermek yerine bir logger prosedürüyle bir dosyaya yazdırmayı tercih edebilirsiniz.

Yöntemlerin Karşılaştırılması

  • QueryTable’lar oldukça hızlıdır ve özellikle VBA bilmeyenler için bile dataya ulaşmada kolaylık sağlar. ADO için mutlaka VBA bilmek gerekir. Ancak ADO’nun da QueryTable’ya göre avantajları vardır.
  • ADO ile aynı anda birden çok DB(Ör:SQL Server ve Access) sorgulanabilirken QueryTable ile tek seferde sadece tek DB sorgulanabilir.
  • ADO ve DAO ile gelen data formatsız bir data olacaktır. Bunu formatlı hale getirmek için ek kod yazmak gerekir. Halbuki bir WorkbookConnection kurulduğunda(text/web değilse) Table formatı otomatik gelmektedir.
  • Ayrıca manuel refreshlemek gereken durumlarda ListObject yine bir kolaylık sağlamakta iken ADO/DAO için kod sayfasına girip kodu manuel çalıştırmak gerekmektedir. Belki bunun için sayfaya bir buton konularak kodun refresh mantığıyla çalıştırılması sağlanabilir.

Çeşitli Örnekler

Bir grup dosyadaki şifreleri (ve/veya userları) toplu olarak değiştirme

Bu kod özellike benim gibi günde 30-40 civarı dosyayı schedule etmiş kişiler için çok faydalı olacaktır. Oracle/DB2 gibi veritabanlarına bağlanıp veri çekiyorsanız çok büyük ihtimalle ayda bir şifrenizi değiştirmeniz gerekmektedir. Excel içine gömdüğünüz şifre bilgilerinin de aynı şekilde değişmesi gerekir. Sadece şifre değil, bazen schedule işlemini başka birinine devretmeniz de gerekebilir, bu durumda user bilgilerinin de değişmesi gerekir. İşte böyle bir durumda bu 30-40 dosyayı tek tek açıp, kimisinde 5-6 connection da bulunabilir, tek tek user/şifre bilgisi değiştirmek tüm gününüzü alabilir, zira tüm sorguların çalışması gerekecektir. İşte bu aşağıdaki kod, sadece birkaç dakika içinde tüm dosyalarda gerekli değişklikleri yapacaktır.(Dosyaları kaydedip kapatma işlemini de prosedürün sonuna siz ekleyin)

Burda toplamda üç ayrı prosedür bulunuyor. Bunlardan biri, diğer iki prosedür tarafından çağrılan bir Function. Zaten iki kez kullanılması gerektiği için bunu Function yaptım. (Bu Function'da çoklu değer döndürmek için Collection kullandım). Yaptığımız değişiklikler olmuş mu diye görmek için baktığımız üçüncü prosedüre gerek duymazsanız, Function içindeki kodu ana prosedüre yedirebilrsiniz(isterseniz tabi).

Kodun içinden de gördüğünüz üzere, oracle bağlantısı olanlar için ayrı, DB2 bağlantıları için ayrı olmak üzere ana prosedürü toplamda 2 kez çalıştırıyorum. Siz, kaç farklı veritabanına bağlanıyorsanız o kadar çalıştırmanız gerekecek. (Database'lerin illa farklı makinalarda olması gerekmiyor, Oracle üzerinde bulunan 3 farklı database'e de bağlanmanız durumunda yine 3 kez çalıştırmanız gerekir.)

Dim files As Collection
'-----------------------
Sub toplu_sifre_degisme()
'bir grup dosyadaki şifreleri (ve/veya userları) toplu olarak değiştirmek için kullanılır
On Error Resume Next
 
Dim wB As Workbook
Dim cvp As VbMsgBoxResult
Dim protectlimi As Boolean
 
Application.Calculation = xlCalculationManual 'Dosyalarda yoğun formülasyon varsa bunlar devreye girmesin diye
hey = MsgBox("Tüm makrolar çalıştıktan sonra değiştiiryosun dimi, çünkü bazı raporlarda DateModifed kontrolü var?", vbYesNo)
If hey = vbNo Then
    Exit Sub
End If
 
cvp = MsgBox("Tüm dosyalar kapalı ve collectionın son hali güncel, değil mi?", vbYesNo)
If cvp = vbNo Then
    Exit Sub
End If
 
eski = InputBox("Eski şifreyi girin")
If eski = "" Then Exit Sub
yeni = InputBox("Yeni şifreyi girin")
If yeni = "" Then Exit Sub
 
'çalışmasınlar diye event disable yapalım
Application.EnableEvents = False
 
'collection fonksiyonu çağıralım
Set files = dosyacoll
 
'şimdi dosyaları açıp şifreleri değiştirelim
For Each File In files
    Workbooks.Open Filename:=File
    Set wB = ActiveWorkbook
    If wB.ProtectStructure = True Then 'protection varsa kaldıralım, aksi halde değişiklik yapamayız
        protectlimi = True
        wB.Unprotect (1234) 'haftalık skorkart, tmv
    End If

    For Each cn In wB.Connections
        If cn.Type = xlConnectionTypeODBC Then
            If InStr(cn.ODBCConnection.connection, eski) > 0 Then
                cn.ODBCConnection.connection = Replace(cn.ODBCConnection.connection, eski, yeni)
            End If
        End If
    Next cn
    
    'protection varsa tekrar koyalım
    If protectlimi = True Then wB.Protect (1234)
    protectlimi = False 'tekrar false yapıyorum ki, mevcut değeri taşımasın
Next File
 
'son olarak da doğru değişmiş mi diye bakalım
Call toplu_connecition_string_gorme
Application.EnableEvents = True
Set files = Nothing

Application.Calculation = xlCalculationAutomatic
 
End Sub
'--------------------------------------
Function dosyacoll() As Collection
Const gunlukyol As String = "..."
Dim files As Collection
Dim DBtür As Byte
 
DBtür = Application.InputBox("DB türünü girin. Oracle için 1, DB2 için 2", Type:=1)
 
'collectionı oluşturalım
Set files = New Collection
With files
    If DBtür = 1 Then 'oracle
        .Add (gunlukyol + "…...xlsb")
        'diğer dosyalar da aynı şekilde collectiona eklenir
    ElseIf DBtür = 2 Then 'DB2
        .Add (gunlukyol + "…...xlsb")
        'diğer dosyalar da aynı şekilde collectiona eklenir
    Else
        MsgBox "yanlış DB türü girdiniz"
        Application.EnableEvents = True
        Exit Function
    End If
End With
 
db = DBtür
Set dosyacoll = files
 
End Function
'--------------------------------------
Sub toplu_connecition_string_gorme()
'pwdyi bul sonraki 10 karakteri al
On Error Resume Next
 
Dim wB As Workbook
Dim cn As WorkbookConnection
Dim dizi() As String
Dim fso As New FileSystemObject
 
Application.EnableEvents = False
'files dolu mu diye kontrol edelim, doluysa ilk prosedürün devamıdır, değilse bağımsız açılmıştır
If Not files Is Nothing Then
    GoTo atla
Else
    Set files = dosyacoll
    For Each c In files
        Workbooks.Open Filename:=c
    Next c
End If
 
atla:
N = 0 'maksimum connection sayısını bulmaya çalışıyoruz
For Each f In files
    dosyaad = fso.GetFileName(f)
    If Workbooks(dosyaad).Connections.Count  > N Then
        N = Workbooks(dosyaad).Connections.Count
    End If
Next f
 
ReDim dizi(1 To files.Count, 1 To N)
 
 
i = 1
j = 1
dosyalar = ""
done = False
For Each f In files
    dosyaad = fso.GetFileName(f)
    dizi(i, j) = dosyaad
    For Each cn In Workbooks(dosyaad).Connections
        j = j + 1
        dizi(i, j) = cn.ODBCConnection.Connection 'hepsinin odbc olduğunu bildiğim için
    Next cn
    j = 1
    i = i + 1
Next f
 
 
Workbooks.Add
 
For i = 1 To files.Count
    For j = 1 To N
        If InStr(1, dizi(i, j), "UID") = 0 Then
            Cells(i, j).Value2 = dizi(i, j)
        Else
            Cells(i, j).Value2 = Mid(dizi(i, j), InStr(1, dizi(i, j), "UID"), 30)
        End If
    Next j
Next i
 
Application.EnableEvents = True
Exit Sub
 
exudo:
Application.EnableEvents = True
MsgBox "Bir hata oluştu, örneğin protectli bir dosyada şifre değiştirmeye çalışıyor olabilirsiniz"

End Sub	
(NOT:Her ne kadar şifre güvenliği kısmında şifrenizi connection string içinde yazmamanızı önerdiysem de, olur da böyle birşey yaptıysanız diye bu örneği koydum. Aslında şifreyi tek bir yerden okumanız durumunda sadece orada değiştirmeniz yeterli olacaktır ve yukarıdaki zahmete de girmemiş olursunuz. Dediğim gibi bir nedenden bu yolu benimsediyseniz diye bu örneği koydum)

Açık olan dosyalardaki Connectionların Şifre/user ve SQL bilgilerini değiştirme

Diyelim ki içinde bir veya daha fazla connection olan bir veya daha fazla dosya açık durumda. Bir önceki örnekte dosyalar kapalıydı, bu örnekte bu sefer açık durumdalar. Bir diğer fark da bu sefer SQL değişikliği de yapmak isteyebilirsiniz. Mesela açık olan tüm dosyalarda 1 gün öncesinin datasını çeken SQL’ler(“sysdate-1” satırını içeren) olduğunu düşünün. Bunların hepsini 2 gün öncesinin datasını gösteren SQL’e(sysdate-2) çevirmek isteyebilirsiniz. Bunu manuel yaparsanız yine şifre değişimde olduğu gibi SQL değişiminde de tüm bağlantıların çalışmasını beklemek zorundasınızdır. İşte bu aşağıdaki kod ile beklemeden tek seferde hızlıca gerekli kod değişikliklerini yapabilirsiniz.

Kodun yeterince açık olduğunu düşündüğüm için ilave bir açıklama yazmıyorum. Anlaşılmayan birşey olması durumunda aşağıdaki Yorum bölümüne sorunuzu yazabilirsiniz.

Sub ConString_veya_SQL_degistir()
'açık olan bir dosyada SQL veya connectionstring(genellikle şifre ve user için) değiştirir
On Error GoTo çıkış
 
Dim wB As Workbook
Dim cn As WorkbookConnection
Dim eski As String
Dim yeni As String
Dim contype As Byte
Dim neyi As Byte
 
Do
    neyi = InputBox("SQL için 1, connection string için 2 girin")
Loop Until neyi < 3
 
eski = InputBox("Eski değeri girin")
If eski = "" Then Exit Sub
yeni = InputBox("Yeni değeri girin")
If yeni = "" Then Exit Sub
 
 
N = 0
dosyalar = ""
For Each wB In Application.Workbooks
    If Windows(wB.Name).Visible = True Then 'Personal.xlsb gibi gizli dosyalarda çalışmasına gerek yok
        If wB.Connections.Count > 0 Then 'içinde bağlantı olan dosyalarda yapsın
            For Each cn In ActiveWorkbook.Connections
            'sadece ODBC ve OLEDB'lerde yapsın, diğerlerinde bi işlem yapmasın
                If cn.Type = xlConnectionTypeODBC Or cn.Type = xlConnectionTypeOLEDB Then
                    If neyi = 1 Then 'SQL-comnandtext
                        If cn.Type = xlConnectionTypeODBC Then
                            cn.ODBCConnection.CommandText = Replace(cn.ODBCConnection.CommandText, eski, yeni)
                        Else
                            cn.OLEDBConnection.CommandText = Replace(cn.OLEDBConnection.CommandText, eski, yeni)
                        End If
                    Else 'con string
                        If cn.Type = xlConnectionTypeODBC Then
                            cn.ODBCConnection.connection = Replace(cn.ODBCConnection.connection, eski, yeni)
                        Else
                            cn.OLEDBConnection.connection = Replace(cn.OLEDBConnection.connection, eski, yeni)
                        End If
                    End If
                End If
            Next cn
            N = N + 1
            dosyalar = wB.Name & ";" & dosyalar
        End If
    End If
Next wB
 
MsgBox N & " adet dosyada şifre değişti. "
 
Workbooks.Add
Range("a1") = dosyalar
Exit Sub
 
çıkış:
MsgBox "Bir hata oluştu"
End Sub

Tüm workbookların refresh olmasını sağlama

Diyelim ki, o anda açık olan tüm dosyaların refresh olmasını istiyorsunuz. Normalde Data menüsündeki RefreshAll sadece açık olan doyadaki bağlantıları refreshler. Biz ise açık olan tüm dosyalarda bunu yapmak istiyoruz. Tabi gizli olan Personal.xlsb tarzı makro dosyalarımız hariç. Bunun için gerekli kod aşağıdaki gibidir.

Sub tum_wblarda_tum_connectionlar_refresh()
On Error GoTo çık
 
For Each wB In Application.Workbooks
    If Windows(wB.Name).Visible = True Then
        If wB.Connections.Count > 0 Then
            Debug.Print wB.Name
            For Each cn In ActiveWorkbook.Connections
                Debug.Print cn.Name
                bBackground = cn.ODBCConnection.BackgroundQuery
                cn.ODBCConnection.BackgroundQuery = False
                cn.Refresh
                cn.ODBCConnection.BackgroundQuery = bBackground
            Next cn
        End If
    End If
Next wB
Exit Sub
 
çık:
MsgBox "hata"
End Sub

TEST SORULARI

Son Sorumuz şuymuş:Bir metindeki tüm noktaları yoketmek istiyorsunuz. Hangi fonksiyonu kullanırdınız?
Soru:

A şıkkı:

B şıkkı:

C şıkkı:

D şıkkı:

Doğru Cevap Etiketler

İlişkili konuyu seç

95245

Label
* Sorulara verilen yanlış cevaplardaki esprili yorumlarım için hoşgörünüze sığınıyorum.
* Test ve Ödevlerdeki bazı detaylar burada anlatılmamış olabilir. Bunları kendiniz araştırıp bulmalısınız.
* Birden çok konuya ait içeriği olan ödevler var. Algoritmik açıdan bakıldığında o an en uygun konuya adreslenmiştir.
Dikkat! Bir soruya cevap verdikten sonra geri dönemezsiniz.
0
0
0
0

SoruID:... Şu an için bu konu için soru bulunmamaktadır. İletişim menüsünden örnek sorularınızı bana iletebilirsiniz.




ÖDEVLER

0
0
ÖdevNo:... Şu an için bu konu için ödev bulunmamaktadır. İletişim menüsünden örnek ödev sorularını bana iletebilirsin.
Çözüme bakın(Başka türlü de çözülebilir tabi, bu benim çözümüm.)




=YORUMLAR ve SORULAR=


DEVİR UYARISI

Herkese merhaba. Hosting maliyetlerinin aşırı artması yüzünden sitemi yakın zamanda(en geç Mayıs 2023) kapatmaya karar vermiştim. Ancak, siteyi yakından takip eden bir arkadaş siteyi devralmak istemiştir. Siteyi, Mayıs ayında kendisine devir etmeye karar verdim. Üyelik bilgilerini bana güvenerek girdiğiniz için, hepsini silmiş bulunuyorum, yani mail adreslerinizi kimseyle paylaşmamış olacağım. Bilginizi rica ederim.