Τόσα χρόνια στον χώρο της πληροφορικής έχω μάθει να προσπαθώ να καταλάβω τι γίνεται πίσω από την σκηνή με αυτό που ασχολούμαι. Ο SQL Server είναι ένα από αυτά, και στο οποίο έχω αφιερώσει αρκετές τρίχες της κεφαλής μου. Σήμερα θα σας πάρω λίγο από τον πολύτιμο χρόνο σας για να σας μεταφέρω μια γνώση που θα σας φανεί αρκετά χρήσιμη.
Όλοι λίγο ή πολύ έχετε γράψει ένα sql query. Άλλες φορές αυτό λειτούργησε άψογα άλλες φορές όχι. Το μυστικό για να γράψεις ένα καλό sql query είναι να έχεις κατανοήσει πως αυτό λογικά εκτελείται, ιδιαίτερα στην μηχανή που χρησιμοποιείς, στην δικιά μας περίπτωση ο SQL Server.
Ένα sql query περιέχει κάποιες εκφράσεις μέσα του, εδώ θα ασχοληθούμε με τι βασικές, Έτσι ένα sql query είναι της μορφής αυτής. Στους αριθμούς μέσα στις παρενθέσεις φαίνεται η σειρά, βήμα, φάση εκτέλεσης.
Κάθε βήμα δημιουργεί έναν virtual table ο οποίος είναι το input για το επόμενο βήμα. Αυτά το virtual tables δεν είναι διαθέσιμα σε κανένα πλήν του SQL Server, εκτός του τελευταίου που είναι και αυτό που παίρνουμε σαν απάντηση. Εάν στο sql query μας δεν έχουμε κάποια έκφραση απλά αυτή αγνοείτε και πάει στο επόμενο βήμα.
(8) SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE | ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
Ας δούμε τα πράγματα λίγο αναλυτικά με ένα παράδειγμα που θα δημιουργήσουμε με το παρακάτω script
SET NOCOUNT ON;
USE tempdb;
GO
IF OBJECT_ID('dbo.Orders') IS NOT NULL
DROP TABLE dbo.Orders;
GO
IF OBJECT_ID('dbo.Customers') IS NOT NULL
DROP TABLE dbo.Customers;
GO
CREATE TABLE dbo.Customers
(
customerid CHAR(5) NOT NULL PRIMARY KEY,
city VARCHAR(10) NOT NULL
);
INSERT INTO dbo.Customers(customerid, city) VALUES('ANTON', 'Athens');
INSERT INTO dbo.Customers(customerid, city) VALUES('NASOS', 'Athens');
INSERT INTO dbo.Customers(customerid, city) VALUES('FANIS', 'Athens');
INSERT INTO dbo.Customers(customerid, city) VALUES('CHRIS', 'Salonica');
CREATE TABLE dbo.Orders
(
orderid INT NOT NULL PRIMARY KEY,
customerid CHAR(5) NULL REFERENCES Customers(customerid)
);
INSERT INTO dbo.Orders(orderid, customerid) VALUES(1, 'NASOS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(2, 'NASOS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(3, 'FANIS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(4, 'FANIS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(5, 'FANIS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(6, 'CHRIS');
INSERT INTO dbo.Orders(orderid, customerid) VALUES(7, NULL);
Μετά από την εκτέλεση του θα έχουμε δύο πίνακες τους Customers, Orders οι οποίοι είναι related μεταξύ τους και τα δεδομένα τους θα είναι τα εξής
Customers Table Data
customerid | city |
ANTON | Athens |
CHRIS | Salonica |
FANIS | Athens |
NASOS | Athens |
Orders Table Data
Orderid | customerid |
1 | NASOS |
2 | NASOS |
3 | FANIS |
4 | FANIS |
5 | FANIS |
6 | CHRIS |
7 | NULL |
Ας πάρουμε σαν σενάριο ότι θέλουμε να βούμε τους πελάτες της Αθήνας που έχουν κάτω από τρεις παραγγελίες.
Έτσι το sql query μας θα είναι σαν αυτό.
SELECT C.customerid, COUNT(O.orderid) AS numorders
FROM dbo.Customers AS C
LEFT OUTER JOIN dbo.Orders AS O
ON C.customerid = O.customerid
WHERE C.city = 'Athens'
GROUP BY C.customerid
HAVING COUNT(O.orderid) < 3
ORDER BY numorders;
Το αποτέλεσμα της εκτέλεσης του είναι το παρακάτω
Customerid | numorders |
ANTON | 0 |
NASOS | 2 |
Ποιά είναι όμως η λογική του εκτέλεση; Ας δούμε λοιπόν τα βήματα της εκτέλεσης.
Βήμα 1ο - Cross Join
FROM dbo.Customers AS C ... JOIN dbo.Orders AS O
Εδώ φτιάχνει το καρτεσιανό γινόμενο των δύο πινάκων και το βάζει στο 1ο virtual table (VT1). Το περιεχόμενο του πίνακα αυτού είναι 28 γραμμές ( 4x7).
Customerid | City | Orderid | customerid |
ANTON | Athens | 1 | NASOS |
ANTON | Athens | 2 | NASOS |
ANTON | Athens | 3 | FANIS |
ANTON | Athens | 4 | FANIS |
ANTON | Athens | 5 | FANIS |
ANTON | Athens | 6 | CHRIS |
ANTON | Athens | 7 | NULL |
CHRIS | Salonica | 1 | NASOS |
CHRIS | Salonica | 2 | NASOS |
CHRIS | Salonica | 3 | FANIS |
CHRIS | Salonica | 4 | FANIS |
CHRIS | Salonica | 5 | FANIS |
CHRIS | Salonica | 6 | CHRIS |
CHRIS | Salonica | 7 | NULL |
FANIS | Athens | 1 | NASOS |
FANIS | Athens | 2 | NASOS |
FANIS | Athens | 3 | FANIS |
FANIS | Athens | 4 | FANIS |
FANIS | Athens | 5 | FANIS |
FANIS | Athens | 6 | CHRIS |
FANIS | Athens | 7 | NULL |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
NASOS | Athens | 3 | FANIS |
NASOS | Athens | 4 | FANIS |
NASOS | Athens | 5 | FANIS |
NASOS | Athens | 6 | CHRIS |
NASOS | Athens | 7 | NULL |
Βήμα 2ο - Apply Join condition ON Filter
ON C.customerid = O.customerid
Στο βήμα αυτό εφαρμόζεται το ON που υπάρχει στο Join και μόνο τα rows εκείνα τα οποία ικανοποιούν το βήμα πηγαίνουν στον VT2 που θα είναι το αποτέλεσμα του βήματος αυτού.
Έτσι αν πάρω τον VT1 και εφαρμόσω στο παράδειγμα μας το ON θα έχω το εξής αποτέλεσμα
Customerid | City | Orderid | customerid | ΟΝ Filter |
ANTON | Athens | 1 | NASOS | FALSE |
ANTON | Athens | 2 | NASOS | FALSE |
ANTON | Athens | 3 | FANIS | FALSE |
ANTON | Athens | 4 | FANIS | FALSE |
ANTON | Athens | 5 | FANIS | FALSE |
ANTON | Athens | 6 | CHRIS | FALSE |
ANTON | Athens | 7 | NULL | UNKNOWN |
CHRIS | Salonica | 1 | NASOS | FALSE |
CHRIS | Salonica | 2 | NASOS | FALSE |
CHRIS | Salonica | 3 | FANIS | FALSE |
CHRIS | Salonica | 4 | FANIS | FALSE |
CHRIS | Salonica | 5 | FANIS | FALSE |
CHRIS | Salonica | 6 | CHRIS | TRUE |
CHRIS | Salonica | 7 | NULL | UNKNOWN |
FANIS | Athens | 1 | NASOS | FALSE |
FANIS | Athens | 2 | NASOS | FALSE |
FANIS | Athens | 3 | FANIS | TRUE |
FANIS | Athens | 4 | FANIS | TRUE |
FANIS | Athens | 5 | FANIS | TRUE |
FANIS | Athens | 6 | CHRIS | FALSE |
FANIS | Athens | 7 | NULL | UNKNOWN |
NASOS | Athens | 1 | NASOS | TRUE |
NASOS | Athens | 2 | NASOS | TRUE |
NASOS | Athens | 3 | FANIS | FALSE |
NASOS | Athens | 4 | FANIS | FALSE |
NASOS | Athens | 5 | FANIS | FALSE |
NASOS | Athens | 6 | CHRIS | FALSE |
NASOS | Athens | 7 | NULL | UNKNOWN |
Δηλαδή ο VT2 θα είναι τελικά ο παρακάτω
Customerid | City | Orderid | customerid |
CHRIS | Salonica | 6 | CHRIS |
FANIS | Athens | 3 | FANIS |
FANIS | Athens | 4 | FANIS |
FANIS | Athens | 5 | FANIS |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
Βήμα 3ο - Apply OUTER Join
FROM dbo.Customers AS C LEFT OUTER JOIN dbo.Orders AS O
Στην περίπτωση μας μόνο ένας είναι ο πελάτης από την πίνακα τον πελατών που δεν υπάρχει στον VT2 o ΑΝΤΟΝ οπότε μπαίνει και αυτό στον VT3 που είναι το αποτέλεσμα του βήματος αυτού
Customerid | City | Orderid | customerid |
CHRIS | Salonica | 6 | CHRIS |
FANIS | Athens | 3 | FANIS |
FANIS | Athens | 4 | FANIS |
FANIS | Athens | 5 | FANIS |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
ΑΝΤΟΝ | Athens | NULL | NULL |
Βήμα 4ο - Apply WHERE filter
WHERE C.city = 'Athens'
Το αποτέλεσμα είναι ο VT4
Customerid | City | Orderid | customerid |
FANIS | Athens | 3 | FANIS |
FANIS | Athens | 4 | FANIS |
FANIS | Athens | 5 | FANIS |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
ΑΝΤΟΝ | Athens | NULL | NULL |
Βήμα 5ο - Apply Grouping
GROUP BY C.customerid
Το αποτέλεσμα είναι ο VT5
Customerid | City | Orderid | customerid |
FANIS | Athens | 3 | FANIS |
FANIS | Athens | 4 | FANIS |
FANIS | Athens | 5 | FANIS |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
ΑΝΤΟΝ | Athens | NULL | NULL |
Βήμα 6ο - Apply Cube or Rollup
Δεν έχουμε κάτι τέτοιο στο query μας οπότε πάει στο επόμενο βήμα.
Βήμα 7ο - Apply HAVING Filter
HAVING COUNT(O.orderid) < 3
Το αποτέλεσμα είναι ο VT7
Customerid | City | Orderid | customerid |
NASOS | Athens | 1 | NASOS |
NASOS | Athens | 2 | NASOS |
ΑΝΤΟΝ | Athens | NULL | NULL |
Βήμα 8ο - Apply SELECT List
SELECT C.customerid, COUNT(O.orderid) AS numorders
Το αποτέλεσμα είναι ο VT8
Customerid | numorders |
NASOS | 2 |
ANTON | 0 |
Βήμα 9ο - Apply DISTINCT
Δεν έχουμε κάτι τέτοιο στο query μας οπότε πάει στο επόμενο βήμα.
Βήμα 10ο - Apply ORDER BY
SELECT C.customerid, COUNT(O.orderid) AS numorders
Το αποτέλεσμα είναι ο VT10
Customerid | numorders |
ANTON | 0 |
NASOS | 2 |
Βήμα 11ο - Apply TOP
Δεν έχουμε κάτι τέτοιο στο query μας οπότε καταλήγουμε στο τελικό μας αποτέλεσμα.
ΥΓ. Επίτηδες παρέλειψα κάποια βήματα για να μην σας κουράσω. Αν υπάρχει απαίτηση από εσάς μέσω των σχολίων σας θα προχωρήσω βαθύτερα και σε αυτά