RSS
poniedziałek, 29 kwietnia 2013
Oracle - indeksy na kluczach obcych (foreign keys)

Indeksowanie kluczy obcych uchodzi za dobra praktykę i tak naprawdę w 99% przypadków powinno się to robić. Mimo to baza danych Oracle nie tworzy takich indeksów automatycznie w momencie tworzenia kluczy obcych.

Indeksy nie są tak istotne w sytuacjach, gdy:

 

  1. nigdy nie usuwamy rekordów z tabeli nadrzędnej (gdy wykorzystujemy delete cascade, usuwanie rekordów z tabeli nadrzędnej jest jeszcze kosztowniejsze gdyż wykonywany jest pełny skan tabeli podrzędnej tak, aby wyszukać wszystkie pasujące rekordy),
  2. nigdy nie zmieniamy wartości klucza głównego,
  3. nie łączymy rezultatów zapytań w oparciu o relację Parent - Child.


//wszyscy pracownicy z wydzialu o numerze :X
select * from dept, emp
     where emp.deptno = dept.deptno and dept.deptno = :X;

O ile punkt 3 potencjalnie wpływa „jedynie” na wydajność zapytań, o tyle usuwanie i / bądź modyfikacja wartości kluczy głównych w tabeli nadrzędnej (punkty 1 i 2) może prowadzić do pełnej blokady tabeli podrzędnej ponieważ w takich wypadkach Oracle zakłada na tabeli blokadę „table level lock”. W moim wypadku to właśnie doprowadziło do poważnych problemów dwa tygodnie temu.

Poniższe zapytanie, oparte na znalezionym w książce Expert Oracle Database Architecture wynajduje wszystkie, niepoindeksowane klucze obce. W zależności od posiadanych uprawnień możemy je wykonać na różnych widokach systemowych (na przykład ALL_CONS_COLUMNS A, USER_CONS_COLUMNS A, DBA_CONS_COLUMNS A) wystarczy odpowiednio „przesunąć” komentarze.

Skrypt generuje polecenia create index jak również, na wszelki wypadek, polecenia drop index. Nazwy indeksów są tworzone automatycznie na podstawie nazwy klucza obcego:

SUBSTR (CONSTRAINT_NAME, 1, 28) || '_I

Przed utworzeniem indeksów dobrze jest sprawdzić czy nazwy tworzonych obiektów będą unikalne i czy np. nie zawierają dziwnych znaków, np. u mnie ktoś użył znaku # w nazwie klucza do reprezentacji numeru.

Skrypt sprawdza, czy istnieje dokładnie pasujący indeks i jeśli go nie znajdzie, wyświetla ostrzeżenie *CHECK*. Jeśli na danej tabeli założony jest indeks złożony (niepasujący dokładnie do kolumn klucza), zapytanie wyświetla dodatkową informacje, aby „ręcznie” sprawdzić istniejący indeks złożony. Nie musi być on identyczny tak długo jak wiodąca jego cześć zgodna jest z kolejnością kolumn klucza obcego.

 

Poniższy przykład pożyczony ze strony AskTom[2] ilustruję sytuację:

create table emp ( empno int primary key, ...  deptno references DEPT );

create index on dept(deptno,empno);  -- OK
create index on dept(empno,deptno);  -- wystapi lock
create index on dept(deptno);  -- OK


Jeśli nie chcecie kopiować tekstu, skrypt możecie pobrać tutaj.

 

--
-- http://notatkiprogramisty.blox.pl
--
-- Query looks for Foreign Keys without indexes
-- Adapted from SQL statement that was found in the Expert Oracle Database Architecture.
--
-- 1. Before running create statements check if automatically created index names are unique
-- 2. Modify schema owners XXXX, YYYY as appropriate
--
WITH TAB
     AS (  SELECT DECODE (B.TABLE_NAME, NULL, '*Check*', 'OK') STATUS1,
                  A.OWNER,
                  A.TABLE_NAME CHILD_TABLE,
                  B.TABLE_NAME PARENT_TABLE,
                  A.CONSTRAINT_NAME,
                  A.COLUMNS,
                  B.COLUMNS INDEX_COLUMNS
             FROM (  SELECT A.OWNER,
                            SUBSTR (A.TABLE_NAME, 1, 30) TABLE_NAME,
                            SUBSTR (A.CONSTRAINT_NAME, 1, 30) CONSTRAINT_NAME,
                               MAX (DECODE (POSITION, 1, SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 2, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 3, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 4, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 5, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 6, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 7, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 8, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 9, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 10, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 11, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 12, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 13, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 14, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 15, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (POSITION, 16, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                               COLUMNS
                       FROM ALL_CONS_COLUMNS A,    --USER_CONS_COLUMNS A, --DBA_CONS_COLUMNS A,
                            ALL_CONSTRAINTS B      --USER_CONSTRAINTS B --DBA_CONSTRAINTS B
                      WHERE     A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
                            AND A.OWNER = B.OWNER
                            AND B.CONSTRAINT_TYPE = 'R'
                   GROUP BY A.OWNER,
                            SUBSTR (A.TABLE_NAME, 1, 30),
                            SUBSTR (A.CONSTRAINT_NAME, 1, 30)) A,
                  (  SELECT TABLE_OWNER,
                            SUBSTR (TABLE_NAME, 1, 30) TABLE_NAME,
                            SUBSTR (INDEX_NAME, 1, 30) INDEX_NAME,
                               MAX (DECODE (COLUMN_POSITION, 1, SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 2, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 3, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 4, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 5, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 6, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 7, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 8, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 9, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 10, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 11, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 12, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 13, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 14, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 15, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                            || MAX (DECODE (COLUMN_POSITION, 16, ', ' || SUBSTR (COLUMN_NAME, 1, 30), NULL))
                               COLUMNS
                       FROM ALL_IND_COLUMNS -- USER_IND_COLUMNS -- DBA_IND_COLUMNS
                   GROUP BY TABLE_OWNER,
                            SUBSTR (TABLE_NAME, 1, 30),
                            SUBSTR (INDEX_NAME, 1, 30)) B
            WHERE     A.TABLE_NAME = B.TABLE_NAME(+)
                  AND A.OWNER = B.TABLE_OWNER(+)   --SCHEMA NAME
                  AND B.COLUMNS(+) LIKE A.COLUMNS || '%'
                  AND A.OWNER IN ('YYYY', 'XXXX')
         ORDER BY A.OWNER, A.TABLE_NAME)
  SELECT TAB.*,
         CASE
            WHEN REGEXP_COUNT (COLUMNS, ',') + 1 > 1
            THEN
               '*Check Existing Composite Indexes*'
            ELSE
               'OK'
         END STATUS2,
         'CREATE INDEX ' || SUBSTR (CONSTRAINT_NAME, 1, 28) || '_I ON ' || CHILD_TABLE || '(' || COLUMNS || ');' CREATE_STM,
         'DROP INDEX ' || SUBSTR (CONSTRAINT_NAME, 1, 28) || '_I' || ';' DROP_STM
    FROM TAB
   WHERE STATUS1 != 'OK'
ORDER BY STATUS2, CHILD_TABLE;

 

UWAGA!

Jeśli w systemie brakuje wielu indeksów (u mnie brakowało ich niemal 300) należy liczyć się z tym, że ich utworzenie, może negatywnie wpłynąć na wydajność systemu. Ja sam po operacji musiałem dwa indeksy usunąć do momentu poprawienia jednego z zapytań.

 

Dodatkowe informacje / linki:

  1. http://docs.oracle.com/cd/E11882_01/server.112/e25789/consist.htm#autoId24
  2. http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:292016138754

 

środa, 24 kwietnia 2013
Programowanie iPhone - praca z baza danych SQLite

 

W większości użytkowych aplikacji gdzieś ‘na zapleczu’ wykorzystywana jest baza danych. W przypadku aplikacji mobilnych na telefony iPhone z reguły jest to SQLite, nawet CoreData, opracowane przez Apple, oparte jest na tym właśnie silniku.

Najogólniej rzecz biorąc każdy program wykonujący operacje na bazie danych działa według podobnego schematu:


1. Łączy się z baza danych.
2. Wykonuje jedną, bądź więcej operacji CRUD*.
3. Rozłącza połączenie.

 

W tym momencie należałoby się zastanowić, kiedy najlepiej zamknąć nasze połączenie, pamiętają, że każda operacja jego otwierania/zamykania obciąża nieco system a co się z tym wiąże i baterię naszego urządzenia. Czy najlepiej zrobić to zaraz po operacji, czy może, wiedząc, że za chwile będziemy dokonywać następnej, zaczekać?


Sam początkowo wykorzystywałem pierwsze z wyżej opisanych podejść tj. otwierałem połączenie, wykonywałem, zwykle tylko jedna z operacji CRUD, po czym połączenie zamykałem. W zasadzie nie stanowiło to większego problemu gdyż operacje bazodanowe nie były zbyt częste. Jeden z ostatnich projektów zmusił mnie jednak do zmiany podejścia :) przy wykonywaniu setek operacji INSERT każda milisekunda zaczęła być ważna.


Opinie w sieci są podzielone, ale większość osób stoi jednak na stanowisku, że nie ma potrzeby ciągłego otwierania i zamykania połączenia z bazą. Okazuje się również, że autorzy SQLite w upewnienie się, aby naszą bazę nie łatwo było uszkodzić włożyli naprawdę dużo pracy (http://sqlite.org/testing.html, http://sqlite.org/atomiccommit.html).


Teraz we własnych aplikacjach stosuję następujące podejście. Otwieram połączenie z bazą podczas startu aplikacji, bądź, gdy zostaje ona przeniesiona na pierwszy plan (applicationDidBecameActive), a zamykam w momencie, gdy działanie aplikacji zostaje przerwane (applicationWillResignActive / applicationDidEnterBackground). Jak dotąd podejście to sprawdza się znakomicie.

 

 

* CRUD - create, read, update, delete

 

PS. Powyższe spostrzeżenia można zastosować także, gdy nie pracujemy bezpośrednio z SQLite, a na przykład wykorzystując FMDB, jeden z najpopularniejszych wrapperów Objective-C do SQLite. Połączenie może pozostać otwarte tak długo jak długo nie zmieniamy struktury bazy.

 

Dodatkowe informacje / linki:

FMDB

SQLite

Programowanie iOS - App States and Multitasking




środa, 17 kwietnia 2013
iPhone - Eco Drive 1.9 - Nowa wersja aplikacji dostępna!

Dziś z AppStore pobrać można najnowszą wersję aplikacji Eco Drive 1.9 pozwalająca na gromadzenie rozmaitych danych o podróżach odbywanych samochodem.

 

Aplikacja umożliwia nie tylko śledzenie zużycia paliwa czy przebytego dystansu, ale może też przypomnieć o niezbędnym przeglądzie auta, czy oszacować koszty przyszłej podróży na podstawie zebranych danych historycznych.

 

W wersji tej dodano również możliwość dokładnej rejestracji pokonywanej trasy wraz z parametrami poruszania się pojazdu oraz z możliwością eksportu danych w formacie rozpoznawalnych przez aplikacje Excel / Numbers.

 

Dodano także możliwość rejestracji punktów zainteresowania POI i eksportu ich w formacie zgodnycm z nawigacjami Garmin.

Możliwości aplikacji:

  • rejestr danych podróży,
  • rejestracja cyklu jazdy tj.: cykl miejski, pozamiejski, mieszany,
  • route tracking - zbieranie informacji geograficznych, kierunku poruszania się pojazdu, prędkości, wysokości npm. Itp., 
  • integracja z Twitter’em,
  • szacowanie kosztów oraz inne statystyki podróży (np.: dystans / 1L / gal, zużycie paliwa / 100km/mil, koszt 1 km/m),
  • kalkulator przybliżonych kosztów podróży,
  • konwerter jednostek,
  • wsparcie w zarządzaniu przeglądami samochodu,
  • przechowywanie danych pojazdu (np.: marka, model, numer rejestracyjny, VIN, dane o ubezpieczeniu – numer polisy, numery kontaktowe),
  • eksport/import danych w formacie kompatybilnym z Numbers™/Excel™,
  • pełny backup/odzyskanie danych podróży zapisanych w aplikacji (nie dotyczy zebranych parametrów podczas poruszania się pojazdu – te można jedynie wyeksportować),
  • archiwizowanie wyliczanych przybliżonych kosztów podróży,
  • możliwość porównania zapisanych przybliżonych kosztów z danymi faktycznymi,
  • prosty kalkulatory wyliczający przybliżony czas trwania podróży.

Aplikację można pobrać z AppStore:

Eco Drive

Pamiętaj, aby przed aktualizacją aplikacji wykonać backup danych wcześniej zarejestrowanych podróży.

 

W jednym z kolejnych postów napisze jak importować punkty POI do Garmin-a, oraz krok po kroku opiszę jak stworzyć aplikację typu „Route Tracker” na iPhone.

 

Do rozdania 5 kodów promocyjnych umożliwiających bezpłatne pobranie programu z AppStore. Aby otrzymać kod wyślijcie email na adres: notatkiprogramisty[@]gmail.com.
poniedziałek, 15 kwietnia 2013
iPhone - Eco Drive 1.9

 

W środę w AppStorze pojawi się nowa wersja jednej z naszych aplikacji - Eco Drive.

 

Dla niewtajemniczonych, aplikacja Eco Drive pozwala na gromadzenie rożnych danych o podróżach samochodem. Umożliwia śledzenie zużycia paliwa, przebytego dystansu, może tez przypomnieć o niezbędnym przeglądzie auta.


Najnowszą widoczną w nowej wersji zmianą jest nowy ekran i nowy tryb pracy pozwalający na dokładne śledzenie naszej podróży. Rejestruje on współrzędne geograficzne zgodnie z wymaganą dokładnością, prędkość, dystans, kierunek jazdy,  jak również pozwala na rejestrowanie tzw. punktów zainteresowania POI (ang. point of interest).


  Eco Drive 1.9 - Route Tracking    Eco Drive 1.9 - Route Tracking    Eco Drive 1.9 - Route Tracking

 

Zarówno zarejestrowane dane szczegółowe, jak i punkty POI mogą następnie zostać wyeksportowane poprzez email w formacie CSV, który może być następnie odczytany przez aplikacje Excel/Numbers.


Dodatkowo punkty POI można zapisać i wyeksportować w formacje zgodnym z systemami nawigacji Garmin, a następnie zaimportować je do naszego urządzenia.



Aplikację można pobrać z AppStore:

Eco Drive

W jednym z kolejnych postów napisze jak importować punkty POI do Garmin-a, oraz krok po kroku opiszę jak stworzyć aplikację typu „Route Tracker” na iPhone.


czwartek, 11 kwietnia 2013
Programowanie iPhone - obsługa / wykrywanie 1, 2, 3 tapnięć

 

Tworząc aplikacje na iPhone-a w pewnym momencie niewątpliwie staniemy przed koniecznością obsługi tapnięć (ang. taps). Poniżej dwa sposoby, w jaki możemy do tego podejść.

Pierwszy kod jest bardzo prosty i umożliwia kontrolę wymaganego opóźnienia pomiędzy kolejnymi tąpnięciami. W przypadku 2 i 3 tapnięć poprzednie zostają anulowane (cancelPreviousPerformRequestsWithTarget).


-(void)oneTap {
   NSLog(@"Single tap...");
}

-(void)twoTaps {
   NSLog(@"Double tap...");
}

-(void)threeTaps {
   NSLog(@"Triple tap...");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   //wykrywanie tapniecia (gdziekolwiek)
   UITouch *touch = [touches anyObject];
   
   switch ([touch tapCount]) {
      case 1:
         [self performSelector:@selector(oneTap) withObject:nil afterDelay:.5];
         break;
         
      case 2:
         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(oneTap) object:nil];
         [self performSelector:@selector(twoTaps) withObject:nil afterDelay:.5];
         break;
          
      case 3:
         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(twoTaps) object:nil];
         [self performSelector:@selector(threeTaps) withObject:nil afterDelay:.5];
         break;
           
      default:
         break;
   }
}

 

Poniżej wyniki z przykładowego wykonania aplikacji:

2013-04-07 14:57:26.462 Tapik[743:c07] Single tap...
2013-04-07 14:57:27.968 Tapik[743:c07] Single tap...
2013-04-07 14:57:29.343 Tapik[743:c07] Triple tap...
2013-04-07 14:57:31.193 Tapik[743:c07] Double tap...
2013-04-07 14:57:32.585 Tapik[743:c07] Double tap...
2013-04-07 14:57:33.889 Tapik[743:c07] Triple tap...
2013-04-07 14:57:35.161 Tapik[743:c07] Triple tap...
2013-04-07 14:57:36.441 Tapik[743:c07] Triple tap...


Jeśli potrzebujemy nieco większych możliwości, a nie jedynie prostego wykrywania tąpnięć najlepszym rozwiązaniem jest wykorzystanie „rozpoznawacza” gestów (to moje wolne tłumaczenie z ang. gesture recognizer :) jeśli ktoś zna lepsze proszę pisać.

Poniżej kod, w zasadzie odpowiadający funkcjonalnie poprzedniemu przykładowi, oparty o klasę UITapGestureRecognizer.


//inicjalizacja...
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)];
UITapGestureRecognizer* tripleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTripleTapGesture:)];

singleTap.numberOfTapsRequired = 1;
[singleTap requireGestureRecognizerToFail:doubleTap];
[singleTap requireGestureRecognizerToFail:tripleTap];

doubleTap.numberOfTapsRequired = 2;
[doubleTap requireGestureRecognizerToFail:tripleTap];
   
tripleTap.numberOfTapsRequired = 3;

//dodanie do widoku...   
[self.view addGestureRecognizer:singleTap];
[self.view addGestureRecognizer:doubleTap];
[self.view addGestureRecognizer:tripleTap];

 

//metody obslugujace tapniecia...
-(void)handleSingleTapGesture:(UITapGestureRecognizer *)recognizer {
   NSLog(@"Single tap gesture...");
}

-(void)handleDoubleTapGesture:(UITapGestureRecognizer *)recognizer {
   NSLog(@"Double tap gesture...");
}

-(void)handleTripleTapGesture:(UITapGestureRecognizer *)recognizer {
   NSLog(@"Triple tap gesture...");
}

 

A to przykładowy wynik jego wykonania:

2013-04-07 14:59:24.658 Tapik[778:c07] Single tap gesture...
2013-04-07 14:59:25.892 Tapik[778:c07] Double tap gesture...
2013-04-07 14:59:27.289 Tapik[778:c07] Triple tap gesture...
2013-04-07 14:59:28.213 Tapik[778:c07] Triple tap gesture...
2013-04-07 14:59:29.238 Tapik[778:c07] Double tap gesture...
2013-04-07 14:59:30.024 Tapik[778:c07] Single tap gesture...
2013-04-07 14:59:30.959 Tapik[778:c07] Double tap gesture...

Możliwości UITapGestureRecognizer-a są naprawdę duże, dlatego też polecam zapoznanie się z dokumentacja udostępnioną przez Apple-a (linki otwierają się w nowym oknie). Bardzo prawdopodobne, że do tematu jeszcze powrócę.

 

Event Handling Guide for iOS

UIGestureRecognizer Class Reference

UITapGestureRecognizer Class Reference



czwartek, 04 kwietnia 2013
Programowanie gier - Raven udostępnił kody źródłowe Jedi Knight II: Jedi Outcast i Jedi Academy

W hołdzie dla dopiero co zamkniętego LucasArt'u Raven Software udostępnił publicznie kody źródłowy (modułów dla jednego gracza) dwóch gier z serii Star Wars: Jedi Knight II: Jedi Outcast oraz Jedi Academy.

 

"Raven is sad to hear about the closing of LucasArts today, we respected them and enjoyed working with them over the years.

 

We wish the best for all the talented people who were let go and hope they find good work in studios in the industry.

 

We loved and appreciated the experience of getting to make Jedi Knight 2: Jedi Outcast and Jedi Academy for LucasArts. As a gift to the persistently loyal fanbase for our Jedi games and in memory of LucasArts, we are releasing the source code for both games for people to enjoy and play with."

Źródłó: Kotaku - Australia

 

Nie są to gry nowe, Jedi Outcast trafił na rynek w 2002 roku a Jedi Academy rok pozniej w 2003, ale jeśli interesujesz się programowaniem, a w szczególności programowaniem gier warto na kod zerknąć.

 

Kod Jedi Knight II jest oparty na zmodyfikowanym silniku Quake III (ID TECH 3) i można go pobrać klikając w poniższe linki:

 

Jedi Outcast: http://sourceforge.net/projects/jedioutcast/

Jedi Academy: http://sourceforge.net/projects/jediacademy/

 

Star Wars - Jedi Academy i Jedi Knight 2: Jedi Outcast Star Wars - Jedi Academy i Jedi Knight 2: Jedi Outcast
wtorek, 02 kwietnia 2013
Oracle - odnajdywanie duplikatów, powtórzeń wierszy / danych w tabeli

Jeśli w tabeli próbujesz odnaleźć duplikaty danych (powtórzone wiersze, bądź powtórzone wartości w dowolnej kombinacji kolumn) możesz skorzystać z poniższego sposobu.

Przygotujmy najpierw dane testowe:


DROP TABLE find_duplicates;

CREATE TABLE find_duplicates
(
   col0   NUMBER,
   col1   VARCHAR2 (30),
   col2   VARCHAR2 (30),
   col3   VARCHAR2 (30)
);

INSERT INTO find_duplicates VALUES(1, 'Jan', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(2, 'Aga', 'Brzeczyszczykiewicz', 'Kowal');
INSERT INTO find_duplicates VALUES(2, 'Tomasz', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(3, 'Roman', 'Nowak', 'Kierowca');
INSERT INTO find_duplicates VALUES(1, 'Jan', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(4, 'Ola', 'Pietrasik', 'Kucharz');
INSERT INTO find_duplicates VALUES(1, 'Jan', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(6, 'Wojtek', 'Marciniak', 'Ksiegowy');
INSERT INTO find_duplicates VALUES(2, 'Jan', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(1, 'Jan', 'Kowalski', 'Kowal');
INSERT INTO find_duplicates VALUES(4, 'Ola', 'Pietrasik', 'Kucharz');


SELECT * FROM find_duplicates;

Oracle - odnajdywanie duplikatów, powtórzeń wierszy / danych w tabeli

Poniższe zapytanie, wykonywane na wszystkich kolumnach, odnajdzie wszystkie powtarzające się i policzy ilość powtórzeń.

  SELECT col0, col1, col2, col3, count(*) count
    FROM find_duplicates
   WHERE (col0, col1, col2, col3) IN (  SELECT col0, col1, col2, col3
                                 FROM find_duplicates
                             GROUP BY col0, col1, col2, col3
                               HAVING COUNT (*) > 1)
GROUP BY col0, col1, col2, col3
  HAVING COUNT (*) > 1
  ;

Wykonane na naszym przykładowym zbiorze danych zwróci nam dwa poniższe wiersze:

Oracle - odnajdywanie duplikatów, powtórzeń wierszy / danych w tabeli

Zapytanie nie musi być oczywiście wykonywane na wszystkich kolumnach. Poniższy przykład pokazuje zapytanie i rezultat jego wykonania na dwóch kolumnach col0 i col3.

  SELECT col0, col3, count(*) count
    FROM find_duplicates
   WHERE (col0, col3) IN (  SELECT col0, col3
                                 FROM find_duplicates
                             GROUP BY col0, col3
                               HAVING COUNT (*) > 1)
GROUP BY col0, col3
  HAVING COUNT (*) > 1
  ;  

Oracle - odnajdywanie duplikatów, powtórzeń wierszy / danych w tabeli

 

09:09, m0rt1m3r
Link Dodaj komentarz »
Tagi




PowerBuilder Tetris
D - Tetris



Programowanie iOS

C# ToolBox

SQL / TSQL / PLSQL ToolBox

Linux / Unix ToolBox





Zaprzyjaznione Strony

Sprite Bandits

Cake Time