(This page has no text content)
SECOND EDITION Learning SQL Alan Beaulieu Beijing • Cambridge • Farnham • Köln • Sebastopol • Taipei • Tokyo D l d t W B k C
Learning SQL, Second Edition by Alan Beaulieu Copyright © 2009 O’Reilly Media, Inc. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://safari.oreilly.com). For more information, contact our corporate/ institutional sales department: (800) 998-9938 or corporate@oreilly.com. Editor: Mary E. Treseler Production Editor: Loranah Dimant Copyeditor: Audrey Doyle Proofreader: Nancy Reinhardt Indexer: Ellen Troutman Zaig Cover Designer: Karen Montgomery Interior Designer: David Futato Illustrator: Robert Romano Printing History: August 2005: First Edition. April 2009: Second Edition. Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of O’Reilly Media, Inc. Learning SQL, the image of an Andean marsupial tree frog, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or for damages resulting from the use of the information con- tained herein. TM This book uses RepKover™, a durable and flexible lay-flat binding. ISBN: 978-0-596-52083-0 [M] 1239115419 D l d t W B k C
Table of Contents Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix 1. A Little Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Introduction to Databases 1 Nonrelational Database Systems 2 The Relational Model 4 Some Terminology 6 What Is SQL? 7 SQL Statement Classes 7 SQL: A Nonprocedural Language 9 SQL Examples 10 What Is MySQL? 12 What’s in Store 13 2. Creating and Populating a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Creating a MySQL Database 15 Using the mysql Command-Line Tool 17 MySQL Data Types 18 Character Data 18 Numeric Data 21 Temporal Data 23 Table Creation 25 Step 1: Design 25 Step 2: Refinement 26 Step 3: Building SQL Schema Statements 27 Populating and Modifying Tables 30 Inserting Data 31 Updating Data 35 Deleting Data 35 When Good Statements Go Bad 36 Nonunique Primary Key 36 Nonexistent Foreign Key 36 iii D l d t W B k C
Column Value Violations 37 Invalid Date Conversions 37 The Bank Schema 38 3. Query Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Query Mechanics 41 Query Clauses 43 The select Clause 43 Column Aliases 46 Removing Duplicates 47 The from Clause 48 Tables 49 Table Links 51 Defining Table Aliases 52 The where Clause 52 The group by and having Clauses 54 The order by Clause 55 Ascending Versus Descending Sort Order 57 Sorting via Expressions 58 Sorting via Numeric Placeholders 59 Test Your Knowledge 60 4. Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Condition Evaluation 63 Using Parentheses 64 Using the not Operator 65 Building a Condition 66 Condition Types 66 Equality Conditions 66 Range Conditions 68 Membership Conditions 71 Matching Conditions 73 Null: That Four-Letter Word 76 Test Your Knowledge 79 5. Querying Multiple Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 What Is a Join? 81 Cartesian Product 82 Inner Joins 83 The ANSI Join Syntax 86 Joining Three or More Tables 88 Using Subqueries As Tables 90 Using the Same Table Twice 92 iv | Table of Contents D l d t W B k C
Self-Joins 93 Equi-Joins Versus Non-Equi-Joins 94 Join Conditions Versus Filter Conditions 96 Test Your Knowledge 97 6. Working with Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Set Theory Primer 99 Set Theory in Practice 101 Set Operators 103 The union Operator 103 The intersect Operator 106 The except Operator 107 Set Operation Rules 108 Sorting Compound Query Results 108 Set Operation Precedence 109 Test Your Knowledge 111 7. Data Generation, Conversion, and Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Working with String Data 113 String Generation 114 String Manipulation 119 Working with Numeric Data 126 Performing Arithmetic Functions 126 Controlling Number Precision 128 Handling Signed Data 130 Working with Temporal Data 130 Dealing with Time Zones 131 Generating Temporal Data 132 Manipulating Temporal Data 137 Conversion Functions 141 Test Your Knowledge 142 8. Grouping and Aggregates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Grouping Concepts 143 Aggregate Functions 145 Implicit Versus Explicit Groups 146 Counting Distinct Values 147 Using Expressions 149 How Nulls Are Handled 149 Generating Groups 150 Single-Column Grouping 151 Multicolumn Grouping 151 Grouping via Expressions 152 Table of Contents | v D l d t W B k C
Generating Rollups 152 Group Filter Conditions 155 Test Your Knowledge 156 9. Subqueries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 What Is a Subquery? 157 Subquery Types 158 Noncorrelated Subqueries 159 Multiple-Row, Single-Column Subqueries 160 Multicolumn Subqueries 165 Correlated Subqueries 167 The exists Operator 169 Data Manipulation Using Correlated Subqueries 170 When to Use Subqueries 171 Subqueries As Data Sources 172 Subqueries in Filter Conditions 177 Subqueries As Expression Generators 177 Subquery Wrap-up 181 Test Your Knowledge 181 10. Joins Revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 Outer Joins 183 Left Versus Right Outer Joins 187 Three-Way Outer Joins 188 Self Outer Joins 190 Cross Joins 192 Natural Joins 198 Test Your Knowledge 200 11. Conditional Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 What Is Conditional Logic? 203 The Case Expression 204 Searched Case Expressions 205 Simple Case Expressions 206 Case Expression Examples 207 Result Set Transformations 208 Selective Aggregation 209 Checking for Existence 211 Division-by-Zero Errors 212 Conditional Updates 213 Handling Null Values 214 Test Your Knowledge 215 vi | Table of Contents D l d t W B k C
12. Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 Multiuser Databases 217 Locking 217 Lock Granularities 218 What Is a Transaction? 219 Starting a Transaction 220 Ending a Transaction 221 Transaction Savepoints 223 Test Your Knowledge 225 13. Indexes and Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 Indexes 227 Index Creation 228 Types of Indexes 231 How Indexes Are Used 234 The Downside of Indexes 237 Constraints 238 Constraint Creation 238 Constraints and Indexes 239 Cascading Constraints 240 Test Your Knowledge 242 14. Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 What Are Views? 245 Why Use Views? 248 Data Security 248 Data Aggregation 249 Hiding Complexity 250 Joining Partitioned Data 251 Updatable Views 251 Updating Simple Views 252 Updating Complex Views 253 Test Your Knowledge 255 15. Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Data About Data 257 Information_Schema 258 Working with Metadata 262 Schema Generation Scripts 263 Deployment Verification 265 Dynamic SQL Generation 266 Test Your Knowledge 270 Table of Contents | vii D l d t W B k C
A. ER Diagram for Example Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 B. MySQL Extensions to the SQL Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 C. Solutions to Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 viii | Table of Contents D l d t W B k C
Preface Programming languages come and go constantly, and very few languages in use today have roots going back more than a decade or so. Some examples are Cobol, which is still used quite heavily in mainframe environments, and C, which is still quite popular for operating system and server development and for embedded systems. In the data- base arena, we have SQL, whose roots go all the way back to the 1970s. SQL is the language for generating, manipulating, and retrieving data from a relational database. One of the reasons for the popularity of relational databases is that properly designed relational databases can handle huge amounts of data. When working with large data sets, SQL is akin to one of those snazzy digital cameras with the high-power zoom lens in that you can use SQL to look at large sets of data, or you can zoom in on individual rows (or anywhere in between). Other database management systems tend to break down under heavy loads because their focus is too narrow (the zoom lens is stuck on maximum), which is why attempts to dethrone relational databases and SQL have largely failed. Therefore, even though SQL is an old language, it is going to be around for a lot longer and has a bright future in store. Why Learn SQL? If you are going to work with a relational database, whether you are writing applica- tions, performing administrative tasks, or generating reports, you will need to know how to interact with the data in your database. Even if you are using a tool that generates SQL for you, such as a reporting tool, there may be times when you need to bypass the automatic generation feature and write your own SQL statements. Learning SQL has the added benefit of forcing you to confront and understand the data structures used to store information about your organization. As you become com- fortable with the tables in your database, you may find yourself proposing modifica- tions or additions to your database schema. ix D l d t W B k C
Why Use This Book to Do It? The SQL language is broken into several categories. Statements used to create database objects (tables, indexes, constraints, etc.) are collectively known as SQL schema state- ments. The statements used to create, manipulate, and retrieve the data stored in a database are known as the SQL data statements. If you are an administrator, you will be using both SQL schema and SQL data statements. If you are a programmer or report writer, you may only need to use (or be allowed to use) SQL data statements. While this book demonstrates many of the SQL schema statements, the main focus of this book is on programming features. With only a handful of commands, the SQL data statements look deceptively simple. In my opinion, many of the available SQL books help to foster this notion by only skimming the surface of what is possible with the language. However, if you are going to work with SQL, it behooves you to understand fully the capabilities of the language and how different features can be combined to produce powerful results. I feel that this is the only book that provides detailed coverage of the SQL language without the added benefit of doubling as a “door stop” (you know, those 1,250-page “complete referen- ces” that tend to gather dust on people’s cubicle shelves). While the examples in this book run on MySQL, Oracle Database, and SQL Server, I had to pick one of those products to host my sample database and to format the result sets returned by the example queries. Of the three, I chose MySQL because it is freely obtainable, easy to install, and simple to administer. For those readers using a different server, I ask that you download and install MySQL and load the sample database so that you can run the examples and experiment with the data. Structure of This Book This book is divided into 15 chapters and 3 appendixes: Chapter 1, A Little Background, explores the history of computerized databases, including the rise of the relational model and the SQL language. Chapter 2, Creating and Populating a Database, demonstrates how to create a MySQL database, create the tables used for the examples in this book, and populate the tables with data. Chapter 3, Query Primer, introduces the select statement and further demon- strates the most common clauses (select, from, where). Chapter 4, Filtering, demonstrates the different types of conditions that can be used in the where clause of a select, update, or delete statement. Chapter 5, Querying Multiple Tables, shows how queries can utilize multiple tables via table joins. x | Preface D l d t W B k C
Chapter 6, Working with Sets, is all about data sets and how they can interact within queries. Chapter 7, Data Generation, Conversion, and Manipulation, demonstrates several built-in functions used for manipulating or converting data. Chapter 8, Grouping and Aggregates, shows how data can be aggregated. Chapter 9, Subqueries, introduces the subquery (a personal favorite) and shows how and where they can be utilized. Chapter 10, Joins Revisited, further explores the various types of table joins. Chapter 11, Conditional Logic, explores how conditional logic (i.e., if-then-else) can be utilized in select, insert, update, and delete statements. Chapter 12, Transactions, introduces transactions and shows how to use them. Chapter 13, Indexes and Constraints, explores indexes and constraints. Chapter 14, Views, shows how to build an interface to shield users from data complexities. Chapter 15, Metadata, demonstrates the utility of the data dictionary. Appendix A, ER Diagram for Example Database, shows the database schema used for all examples in the book. Appendix B, MySQL Extensions to the SQL Language, demonstrates some of the interesting non-ANSI features of MySQL’s SQL implementation. Appendix C, Solutions to Exercises, shows solutions to the chapter exercises. Conventions Used in This Book The following typographical conventions are used in this book: Italic Used for filenames, directory names, and URLs. Also used for emphasis and to indicate the first use of a technical term. Constant width Used for code examples and to indicate SQL keywords within text. Constant width italic Used to indicate user-defined terms. UPPERCASE Used to indicate SQL keywords within example code. Constant width bold Indicates user input in examples showing an interaction. Also indicates empha- sized code elements to which you should pay particular attention. Preface | xi D l d t W B k C
Indicates a tip, suggestion, or general note. For example, I use notes to point you to useful new features in Oracle9i. Indicates a warning or caution. For example, I’ll tell you if a certain SQL clause might have unintended consequences if not used carefully. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) O’Reilly maintains a web page for this book, which lists errata, examples, and any additional information. You can access this page at: http://www.oreilly.com/catalog/9780596520830 To comment or ask technical questions about this book, send email to: bookquestions@oreilly.com For more information about O’Reilly books, conferences, Resource Centers, and the O’Reilly Network, see the website at: http://www.oreilly.com Using Code Examples This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example, “Learning SQL, Second Edition, by Alan Beaulieu. Copyright 2009 O’Reilly Media, Inc., 978-0-596-52083-0.” xii | Preface D l d t W B k C
If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. Safari® Books Online When you see a Safari® Books Online icon on the cover of your favorite technology book, that means the book is available online through the O’Reilly Network Safari Bookshelf. Safari offers a solution that’s better than e-books. It’s a virtual library that lets you easily search thousands of top tech books, cut and paste code samples, download chapters, and find quick answers when you need the most accurate, current information. Try it for free at http://my.safaribooksonline.com. Acknowledgments I would like to thank my editor, Mary Treseler, for helping to make this second edition a reality, and many thanks to Kevin Kline, Roy Owens, Richard Sonen, and Matthew Russell, who were kind enough to review the book for me over the Christmas/New Year holidays. I would also like to thank the many readers of my first edition who were kind enough to send questions, comments, and corrections. Lastly, I thank my wife, Nancy, and my daughters, Michelle and Nicole, for their encouragement and inspiration. Preface | xiii D l d t W B k C
CHAPTER 1 A Little Background Before we roll up our sleeves and get to work, it might be beneficial to introduce some basic database concepts and look at the history of computerized data storage and retrieval. Introduction to Databases A database is nothing more than a set of related information. A telephone book, for example, is a database of the names, phone numbers, and addresses of all people living in a particular region. While a telephone book is certainly a ubiquitous and frequently used database, it suffers from the following: • Finding a person’s telephone number can be time-consuming, especially if the telephone book contains a large number of entries. • A telephone book is indexed only by last/first names, so finding the names of the people living at a particular address, while possible in theory, is not a practical use for this database. • From the moment the telephone book is printed, the information becomes less and less accurate as people move into or out of a region, change their telephone num- bers, or move to another location within the same region. The same drawbacks attributed to telephone books can also apply to any manual data storage system, such as patient records stored in a filing cabinet. Because of the cum- bersome nature of paper databases, some of the first computer applications developed were database systems, which are computerized data storage and retrieval mechanisms. Because a database system stores data electronically rather than on paper, a database system is able to retrieve data more quickly, index data in multiple ways, and deliver up-to-the-minute information to its user community. Early database systems managed data stored on magnetic tapes. Because there were generally far more tapes than tape readers, technicians were tasked with loading and unloading tapes as specific data was requested. Because the computers of that era had very little memory, multiple requests for the same data generally required the data to 1 D l d t W B k C
be read from the tape multiple times. While these database systems were a significant improvement over paper databases, they are a far cry from what is possible with today’s technology. (Modern database systems can manage terabytes of data spread across many fast-access disk drives, holding tens of gigabytes of that data in high-speed mem- ory, but I’m getting a bit ahead of myself.) Nonrelational Database Systems This section contains some background information about pre- relational database systems. For those readers eager to dive into SQL, feel free to skip ahead a couple of pages to the next section. Over the first several decades of computerized database systems, data was stored and represented to users in various ways. In a hierarchical database system, for example, data is represented as one or more tree structures. Figure 1-1 shows how data relating to George Blake’s and Sue Smith’s bank accounts might be represented via tree structures. George Blake Checking Savings Debit of $100.00 on 2004-01-22 Debit of $250.00 on 2004-03-09 Credit of $25.00 on 2004-02-05 Sue Smith Checking MoneyMkt Debit of $1000.00 on 2004-03-25 Debit of $500.00 on 2004-03-27 Credit of $138.50 on 2004-04-02 Line of credit Credit of $77.86 on 2004-04-04 Customers Accounts Transactions Figure 1-1. Hierarchical view of account data George and Sue each have their own tree containing their accounts and the transactions on those accounts. The hierarchical database system provides tools for locating a par- ticular customer’s tree and then traversing the tree to find the desired accounts and/or 2 | Chapter 1: A Little Background D l d t W B k C
transactions. Each node in the tree may have either zero or one parent and zero, one, or many children. This configuration is known as a single-parent hierarchy. Another common approach, called the network database system, exposes sets of records and sets of links that define relationships between different records. Figure 1-2 shows how George’s and Sue’s same accounts might look in such a system. George Blake Checking Savings Debit of $100.00 on 2004-01-22 Debit of $250.00 on 2004-03-09 Credit of $25.00 on 2004-02-05 MoneyMkt Debit of $1000.00 on 2004-03-25 Debit of $500.00 on 2004-03-27 Credit of $138.50 on 2004-04-02 Line of credit Credit of $77.86 on 2004-04-04 Customers Sue Smith Accounts Checking Transactions Checking Savings MoneyMkt Line of credit Products Figure 1-2. Network view of account data In order to find the transactions posted to Sue’s money market account, you would need to perform the following steps: 1. Find the customer record for Sue Smith. 2. Follow the link from Sue Smith’s customer record to her list of accounts. 3. Traverse the chain of accounts until you find the money market account. 4. Follow the link from the money market record to its list of transactions. One interesting feature of network database systems is demonstrated by the set of product records on the far right of Figure 1-2. Notice that each product record (Check- ing, Savings, etc.) points to a list of account records that are of that product type. Account records, therefore, can be accessed from multiple places (both customer records and product records), allowing a network database to act as a multiparent hierarchy. Introduction to Databases | 3 D l d t W B k C
Both hierarchical and network database systems are alive and well today, although generally in the mainframe world. Additionally, hierarchical database systems have enjoyed a rebirth in the directory services realm, such as Microsoft’s Active Directory and the Red Hat Directory Server, as well as with Extensible Markup Language (XML). Beginning in the 1970s, however, a new way of representing data began to take root, one that was more rigorous yet easy to understand and implement. The Relational Model In 1970, Dr. E. F. Codd of IBM’s research laboratory published a paper titled “A Relational Model of Data for Large Shared Data Banks” that proposed that data be represented as sets of tables. Rather than using pointers to navigate between related entities, redundant data is used to link records in different tables. Figure 1-3 shows how George’s and Sue’s account information would appear in this context. 2004-01-22$100.00103DBT978 dateamountaccount_idtxn_type_cdtxn_id 2004-02-05$25.00103CDT979 2004-03-09$250.00104DBT980 2004-03-25$1000.00105DBT981 2004-04-02$138.50105CDT982 2004-04-04$77.86105CDT983 2004-03-27$500.00106DBT984 Transaction $75.001CHK103 balancecust_idproduct_cdaccount_id $250.001SAV104 $783.642CHK105 $500.002MM106 02LOC107 Account BlakeGeorge1 lnamefnamecust_id SmithSue2 Customer CheckingCHK nameproduct_cd SavingsSAV Money marketMM Line of creditLOC Product Figure 1-3. Relational view of account data There are four tables in Figure 1-3 representing the four entities discussed so far: customer, product, account, and transaction. Looking across the top of the customer 4 | Chapter 1: A Little Background D l d t W B k C
table in Figure 1-3, you can see three columns: cust_id (which contains the customer’s ID number), fname (which contains the customer’s first name), and lname (which con- tains the customer’s last name). Looking down the side of the customer table, you can see two rows, one containing George Blake’s data and the other containing Sue Smith’s data. The number of columns that a table may contain differs from server to server, but it is generally large enough not to be an issue (Microsoft SQL Server, for example, allows up to 1,024 columns per table). The number of rows that a table may contain is more a matter of physical limits (i.e., how much disk drive space is available) and maintain- ability (i.e., how large a table can get before it becomes difficult to work with) than of database server limitations. Each table in a relational database includes information that uniquely identifies a row in that table (known as the primary key), along with additional information needed to describe the entity completely. Looking again at the customer table, the cust_id column holds a different number for each customer; George Blake, for example, can be uniquely identified by customer ID #1. No other customer will ever be assigned that identifier, and no other information is needed to locate George Blake’s data in the customer table. Every database server provides a mechanism for generating unique sets of numbers to use as primary key values, so you won’t need to worry about keeping track of what numbers have been assigned. While I might have chosen to use the combination of the fname and lname columns as the primary key (a primary key consisting of two or more columns is known as a compound key), there could easily be two or more people with the same first and last names that have accounts at the bank. Therefore, I chose to include the cust_id column in the customer table specifically for use as a primary key column. In this example, choosing fname/lname as the primary key would be referred to as a natural key, whereas the choice of cust_id would be referred to as a surrogate key. The decision whether to employ natural or surrogate keys is a topic of widespread debate, but in this particular case the choice is clear, since a person’s last name may change (such as when a person adopts a spouse’s last name), and primary key columns should never be allowed to change once a value has been assigned. Some of the tables also include information used to navigate to another table; this is where the “redundant data” mentioned earlier comes in. For example, the account table includes a column called cust_id, which contains the unique identifier of the customer who opened the account, along with a column called product_cd, which contains the unique identifier of the product to which the account will conform. These columns are known as foreign keys, and they serve the same purpose as the lines that connect the entities in the hierarchical and network versions of the account information. If you are Introduction to Databases | 5 D l d t W B k C
Comments 0
Loading comments...
Reply to Comment
Edit Comment