字幕表 動画を再生する 英語字幕をプリント DAVID MALAN: We've seen how with languages like Python can we implement business logic on a server and, even if we want, generate web pages. And we've seen, on the browser side, the so-called client side, how you can render information or data to users. And with CSS can we style it, and with JavaScript can we even make it interactive. But when building a business or any application that is interacting with users, where is all that data being stored? Well, if you're running your own small business, you might simply be using a spreadsheet. Of course, spreadsheets, whether they're implemented as Microsoft Excel, or Google Spreadsheets, or Apple Numbers, generally stores data in rows and columns. And if you're like me, you probably use that first row to represent the names of the columns and the types of the data that you're storing. And as you continue to add more and more data to that spreadsheet, you probably, like me, continue to add row after row after row. And meanwhile, if you've got so much data or so many different types of data that it doesn't really belong in one sheet, you might actually create a second sheet or a tab along the bottom of the screen, for instance, in which you can store additional data as well. So indeed, Microsoft Excel allows you to store data relationally, so to speak. You might have one sheet with customer info, another sheet with invoices, another sheet with products, or any number of other types of data. And you can relate those to another by somehow having some commonality among them, some identifier, some name, some customer ID or the like. And so data that tends to be stored in rows and columns can have these relationships. And you can do this certainly in the cloud these days as well with Google Docs, simply a web-based version of the same. But when designing your spreadsheets, or more generally, your data's schema, the design thereof, there's a number of questions you need to consider. What data do you want to store? And where do you want to store it? And what type of data is it, in fact? Because whether using Excel, or Numbers, or Google Spreadsheets, odds are you sometimes configure the columns to be displayed or formatted in some ways. Numbers might be displayed with or without commas. Numbers might be displayed with dollar signs or some other symbol. You might separate one field from another using special symbols or punctuation. So these are all configuration options in a spreadsheet that might be among the first decisions you make. In fact, let's consider a sample spreadsheet wherein I might want to represent users in some web application. All of us have, of course, registered for websites or applications these days. And what are some of the questions you're asked? Well, you might be asked for your user name by some application. You might be asked for your actual name. What else might you be asked for? Perhaps your email address. And perhaps if you're buying something, or registering for something, or someone that needs to know a bit more about you, perhaps they'll even ask you for your address, maybe your phone number, or perhaps even your age. So you can certainly imagine there being even other fields that you need. But let's consider now what types of data each of these fields is. Now, what should a user name be? Well, by definition, this should be a unique value that belongs to you and only you on a system. For instance, if I'm registering for a website and that website is storing some data in, for instance, a spreadsheet, I might ideally try to claim Malan if it's available. My name meanwhile, of course, will be just David Malan, which someone else can certainly have as well. Hopefully my email address is only mine. And that, too, will be stored in a column of its own. And then my address, for instance, here on campus, 33 Oxford Street, Cambridge, Massachusetts, 02138. A phone number by which I can be reached, 617-495-5000. And then my age shall be-- well, we'll leave that blank just for now. So notice how each of these pieces of data is just a little bit different. Some of them seem to be numeric. Some of them seem to be alphabetic. Some of them even have some punctuation therein. Now, you might or might not in a spreadsheet care to display this data a bit differently. Indeed, the only number that I'm sweeping under the rug here is, in fact, my age. But hopefully I don't need one or more commas in that value. So there isn't really any need for special formatting here. But it turns out that when you're actually building a website or software-based application, you're probably not storing your data ultimately in just spreadsheets. You might graduate eventually from being a small shop to needing more than just Microsoft Excel, or Numbers, or even something cloud-based like Google Spreadsheets. Why? Well, you have more rows than those programs can generally handle. In fact, on your own Mac or PC, odds are, when opening up big files, whether a spreadsheet or any other document, sometimes you might actually feel that. The computer might start to slow, and you might start to see a spinning beach ball or hourglass because you start to hit the limits of what a local computer can do with just client-side software, like a spreadsheet tool. And so eventually you might actually need to use a proper database. And a database is really just a piece of software that can absolutely run on your Mac or PC. But very commonly it runs on a server or somewhere else in the cloud to which your own software connects. For instance, if you're building a website or an application in a language like Python, you can, in Python, write code that talks or communicates with that database to pull data down and to send data back up. But spreadsheets are wonderfully straightforward. It's just so intuitive how you store the data in rows and columns and just more and more rows as you have more and more data. And so what's nice about some databases is that they mimic exactly this design. There exists in the world what are called relational databases. And indeed, this is among the most common ways to store data relationally. The jargon is a bit different in the world of databases. You no longer call these things spreadsheets. We call them databases. And we don't call the individual tabs sheets. We call them tables. But we continue to call the structure of the data therein rows for each additional piece of data and columns for the different types of data that we have. But in a relational database, such as Oracle, Microsoft Access, SQL Server, My SQL, Postgres, or something smaller and lighter weight called SQLlite, the burden is on you, the designer of the database, or the programmer, or the business person to actually decide on what types of data you are going to store. Because the database, in order to handle more and more data than a typical spreadsheet can support, needs a bit of help from you, needs to know what type of data you're storing so that it can search it more efficiently and sort it more effectively and make it easier for you ultimately to add and remove data from that particular database. So in other words, you can do all of the same operations. But generally, in a database, you have so much more data, you're going to need to have the database help you help yourself. So what does that actually mean here? Well, let's consider some of these fields here. Were we to migrate my spreadsheet with many, many, many rows of users, for instance, to a proper database relationally, I'm going to have to make a few more decisions as well. And it turns out that I need to choose various types for these columns. And generally, in a database, you only have a limited menu of options. So let's take a look at what those might be. Here in many relational databases are just some of the data types that are available to you; an integer if you want to represent something like 1, 2, 3, or perhaps even a negative, a real number, otherwise known as a floating point value, for instance, in Python that actually has a decimal point and perhaps some numbers thereafter, numeric, which is more of a catch-all and might handle things like dates and times that are numbers in some sense but have some more formal structure to them, and then the more general text, when you just have words or phrases, characters or whole paragraphs or more that you might want to store as well in some column. And then lastly, you might have the cutely named BLOB, or binary large object, when you actually want to store zeros and ones, that is to say binary data, like actual files in your database. However, you needn't do this typically. You can actually store files, of course, on a file system on your own hard drive somewhere on the server, but that option exists for you. Now, this happens to be the list of data types supported by the simplest of relational databases, something called SQLite. And as its name implies, it actually supports a very specific language via which you or anyone can request data from the database and store data in the database, or update it, or delete it. And that language is called SQL, structured query language. SQL is a language via which you can do exactly that, retrieve data from a database, put data into a database, and so much more. It's an alternative to the pointing and clicking with which you're probably familiar with something like Microsoft Excel, or Google spreadsheet, or Apple Numbers, where it's really you, the human, doing all the work via a graphical user interface. Now, to be fair, in all of this spreadsheet programs are there functions or macros that you might be able to write so that you can program in those environments but with some limitations. And indeed with SQL and with SQL databases, more generally, that is databases that understand this language, can you achieve far higher performance, can you store far more data, and nonetheless get at it quickly. So it's sort of the next evolution of what you might otherwise do only within your own computer spreadsheet. But many other databases, Oracle, SQL Server, Microsoft Access, MySQL, Postgres and more, support more than these data types that allow you to help the database help you even more. Because the more the database knows about your data, the smarter decisions it can make at a lower level below these abstractions and storing that data so that, when you have a question, it can answer it quickly. And so how might you refine what we mean by integer? Well, in some databases, you might want to tell it that this is just a small integer in this column one after the other, row by row. Or maybe it's just an integer somewhere a little bigger than that, or perhaps a big int, which means it can be even larger. Now, these actually map to very well-defined values. If you think back to how we considered data itself is stored in a computer, it's ultimately with zeros and ones or bits. And indeed, an integer, so to speak, generally takes up just 32 bits or four bytes. But, of course, that few bits actually translates, if you do a bit of the math, to four billion possible values. So if you were to specify that a column in your database is of type integer, that means you could type in the number from zero all the way on up to four billion, or if you want negative numbers, from negative two billion roughly to positive two billion as well. But beyond that three billion or four, you're out of luck if you're only using 32 bits. Now, that might seem huge. And that is a good problem to have if I have four billion users, or sales, or anything in my database. But for certain large companies these days, having four billion or more records of some sort, transactions or logs, not at all uncommon. And for those purposes does there exist big int, a bigger data type that uses 64 bits or eight bytes via which you can count much, much, much higher, so high that I'm not even sure how to pronounce that number. But indeed, it should be large enough for almost all of our purposes. So ultimately, it's not enough to say that the field in a database is an integer but to specify just how big maximally that integer can be. Now, fortunately, I think for my age we could probably get away with small int. But indeed, with a small int, you typically have 16 bits. So even then could your user be as old as 65,535 years old. Now, what about real numbers? These are floating point values with decimal points. And here, too, you have fewer choices, typically, but along the same lines. A real number would typically take 32 bits, which allows you only some amount of precision, some number of digits after that decimal point. If you want to go even further and be ever more precise can you use twice as much space, 64 bits or eight bytes, and use double precision instead. Well, it would seem that we sort of start with an OK value and an even better value here. But what's the trade-off, as there always is? Well, it's fine if want to be able to count higher, whether with integers or real numbers. But you are literally going to pay a price. Because if you want to count higher than four billion in the context of integers, you're going to have to spend twice as much space from four bytes to eight in order to store those values. And if you're never actually going to use values larger than four billion, you're just spending twice as much space as you need to. And if you don't really need to store floating point values to terribly many digits of precision, you're just wasting space. Now, for small databases, this might be inconsequential. But for the Googles, and Microsofts, and others of the world to deal in gigabytes and terabytes of data, all of these bits and bytes will surely add up. As for numeric, more of a catch-all numerically, so to speak, whereby you have Booleans, zeros or ones, or false and true values, dates which comes in a prescribed format, typically year, year, year, year, dash, month, month, dash, day, day. Indeed, even though across the world there are different formats and different communities for representing dates, in SQL do you specify them exactly in that format only. Date time adds to that a space followed by hour, hour, colon, minute, minute, colon, second, second, whereby if you want to store both date and a time, you should store it in exactly that format in your database, even though, thanks to code, whether Python or SQL itself, you want to display that date in a geographically localized and conventional way, you nonetheless store it in this way in your database. Now, sometimes you need to fight against the imprecision that's inherent as a risk with real numbers. And so you can use the so-called numeric data type to specify a so-called scale and precision. That is two numbers that specify really how many digits you ultimately want to support. So if you want to store things like dollar amounts, maybe to cents or hundreds of cents, two or four decimal places, can you specify exactly that, and you will get exactly that much precision. As for time and timestamp, well, time is quite simply hour, hour, colon, minute, minute, colon, second, second. But timestamp's a little more interesting. It, too, can be used to represent time. But as with many languages, Python and JavaScript among them, it does so typically by remembering or counting some number of milliseconds or seconds from a particular point in time. In fact, some years ago, it was conventional to simply start counting time from January 1st, 1970, the so-called epic as opposed to say 0 BC or AD. Unfortunately, it turns out that you generally represent these timestamps with four bytes or 32 bits, which gives you access to four billion or so possible values. And it turns out that in the year 2038, we will have run out of bits with which to represent time. So if you recall the so-called Y2K problem when we ran into this issue around the year 2000, so have we rather painted ourselves into a corner in many systems by not allocating enough space to get us past that year. Well, what's the solution invariably going to be? Well, use more space and, thus, cost. But that is inherent in many systems. And it's one of these things that us humans will have to address. How about text? Well, text can, too, come in multiple forms. Whether it's English or any other language, you might have different lengths thereof. And so within the world of text values and databases, need you specify typically how many characters you want to store in that column. And this is distinct from something like a spreadsheet program where you can probably keep typing and typing or pasting as many characters as you'd like. A database, in so far as its purpose in life is to give you better performance and more scalability than a spreadsheet allows, wants you to tell it a bit more. It's going to ask you to decide whether you want to store a fixed number of chars. So char followed by a number here represented at its end means how many characters exactly shall you store in every cell in this column. Now, why might that be applicable? Well, in the US, for instance, for states, we have a standardization of two characters for every state in the country. And so you might say char (2), thereby allowing you to store CT for Connecticut, and FL for Florida, and MA for Massachusetts and so on, because you know that every value is going to be that finite length, two. But sometimes you don't know the max length. And indeed, in my own table of users, my own name might have D-A-V-I-D and a space and M-A-L-A-N, so 11 for me. But your name might be shorter or longer. Or some user that hasn't even registered for our website yet might have an even longer name than that. So varchar, or variable number of chars, exists, too, where you can specify not a fixed number of characters to use but a maximum upper bound, perhaps something like 30, perhaps something like 300, or some number of characters that you decide that's going to be the upper limit of any human I actually see in my sight who might have a name as many as that characters. But varchar is smart and the databases that use it, because it will use that many characters maximally. But it's not going to waste space. In fact, if you're using 30 characters for every name in your table, well, it's only going to store as many characters are necessary, perhaps plus a bit of overhead or accounting to keep track of that value. But that way you save space but still can handle large text or large strings. Lastly, if you have particularly large text, whole paragraphs, or essays, or documents that someone might paste in, or the contents of an entire web page that you want to search, well, you can have a text field more generally, which tends to support tens of thousands or more characters in total. But it does so in a way that's not quite as efficient. Rather than keep it right in the column itself, so to speak, it puts it somewhere else on the server, the result of which is that you can fit more room over here, so to speak. But it takes more time and effort to go search or find that data, so, again, a trade-off of space and time. But here, too, as with integers and real, you seem to have multiple choices, one of which at first glance is better. After all, why use char ever if you could also just say varchar with that same amount? For instance, for those two character codes for states, why not just say varchar and not just char (2)? Well, it turns out that if you promise the database that every cell in your column will use exactly the same number of bytes, it turns out that you'll have very straight or justified edges, in some sense, conceptually for that column. For instance, if every cell in a column takes up exactly the same amount of space, then you might know, if you start numbering the bytes that are represented by those cells, this might be byte zero up here and then two characters away will be address two, and then four, and then six, and then eight. And if you think back to how algorithms like, say, binary search are implemented, as soon as you have the ability to know arithmetically where the next value, or the last value, or, best yet, the middle value is, numerically can you jump right to it in so-called constant time, thereby enabling something like binary search and ultimately logarithmic time. So this is a powerful thing. If you commit to the database that all of your values will take up the exact amount of space, the database can search that value faster for you. Varchar, unfortunately, is rather akin to having only a left justified column, whereby you might have data that looks very straight on one edge with all of the characters left aligned. But because the length of those characters vary, this cell might be this wide, and this one this wide, and this one this wide, and this one this wide. And as such, because you have a very ragged edge, so to speak, you can't just do simple math and add two and add two to get to the next value in that column. So in the worst case, if a column is only a varchar, the best the database can do by default is just linear search, or big O of n, which, recall, was notably slower than something like log N or logarithmic time. So the more help you can provide to the database the better off you are. But of course you don't want to air too far on the other side and say, oh, well, I'm going to have char 300 for every cell in my Names column, because then you're using 300 characters by definition for just David Malan who needs just, say, 11. So lastly is there this thing BLOB, binary large object, which can only store binary data, which can be used for images or files. But those, again, tend to be best stored somewhere else on the system. So with these possibilities in mind, how might we go about designing the schema for this data? We already have the names of my columns. And I've already gone ahead and put in one sample row. So let's consider in the context of all of those SQL types which ones apply here. Well, for user name, something like Malan, it's probably not numeric because that's not something I'd be so inclined to type. But it probably is text. But within text we have the choice of char or varchar or larger blocks of text, more generally. So what should this be? Well, this one depends. A user name tends to be fairly short. Because after all, we humans probably don't want to type very long strings just to log into some site. Historically, maximal values of eight characters was common. But that tends to be fairly constrained. And you can no longer express very much of your name if you tend to have a long name yourself. So here we might say something like char, because we know it will be small. And we'd like to be able to search on this field efficiently, especially to log someone in fast, but probably not quite as short as eight, so maybe 16 or 20. Or if it's hard to guess there and you want more dynamism, maybe you would say varchar of something like 255. Why that value? Well, recall that with eight bits can you count 256 possible values. But if you start counting at zero, you can only go as high as 255. And historically have many databases had limits of, say, 255 for a varchar, though that's no longer the case. But you'll still see this very commonly. So what's the best answer? I don't know. It depends on your data or someone else's. For here, I'll go ahead and leave it as varchar (255) just to give myself flexibility. But a very good argument could be made for char of some smaller value or even varchar of some smaller value, too. As for name as well, I'm not really sure what this one should be. I don't know how many characters your own name has. Something like 11 is probably too few, even though it satisfies my name just fine. 30 feels a bit [? type. ?] And frankly, I bet if we Google longest name in world, something tells me there's someone out there with quite a few characters, hopefully no more than, say, 255. But there, too, we might want to do some due diligence. With email, too, this seems to be easy. This, too, is just characters, even though you could certainly have numbers. I don't know how long the maximum email address will be. But frankly, it's probably going to be variable in length, so I'm going to go with a default. Why 255? Again, it's probably way more than we need. But varchar is smart. It's not going to waste that many bytes. It's just a reasonable upper bound in the absence of better intuition. At least we're being consistent. Now, address, that might be decently long, especially if it's going on envelope on, say, multiple lines. But here, too, this is probably just text, and so I'll go ahead here and say this, too, 255. Phone number. Finally, a number by name. Well, what should a phone numbers field be? Well, we had integer, or real, or, more specifically, small int or integer, or big int. But the funny thing here is even though a phone number is indeed called by us humans a number, it's really just a symbolic representation of a unique identifier for someone's phone. And so these hyphens might be common in one community or culture. Maybe you'd have parentheses or even pluses for country codes. So frankly, very quickly does even a phone number not become so much a number but a textual string. So here I have some discretion. And maybe I could be presumptuous and assume I'm only going to have customers or users for now, say, in the US for whatever business constraints. And so I might say, you know what? This is a candidate to actually do something like char, say, 10, three-digit area code, a three-digit exchange, and then four digits thereafter. But that doesn't leave room for those hyphens, so I could make a char 12. Or frankly, if they're just going to be there all the time, why don't I leave them as char 10 and just get rid of those values in my data? Or alternatively, I could support parentheses or pluses as well. It really depends on how I want to store the data. But I like the idea of a textual type, something like char or maybe varchar as opposed to an integer. Because at least if I've called certain communities or out from some businesses, sometimes you have to type unusual numbers. At least in other countries, for instance, if we generalize beyond this data set here do you type zero to actually connect to someone local. And the problem with zero is that, mathematically, it's meaningless to start a value with zero. And unfortunately, a computer takes that to heart. And if you were to store phone as an integer but a number were in some community to start with a zero, your database would probably just get rid of it. In fact, try that yourself in Microsoft Excel, or Apple Numbers, or Google Spreadsheets. And depending on how you formatted the column, you can type as many zeros as you want followed by other digits. And odds are, when you hit Enter, those zeros are gone. As for age, here we perhaps finally have a compelling candidate for a number. Small int should probably get the job done. Integer would work as well, or big int, but increasingly wasteful. But you know what? Even here it's not that obvious. I probably shouldn't even use an integer type here at all. Why? Well, I might indeed ask a human for his or her age upon registering. The catch is that age might change the next day or the day after, or the day after, because, of course, time is advancing. And unless I also stored the date and ideally time at which the user registered, I can't really even do any math to figure out, oh, you registered a year ago. Let me assume your age is that plus one. So what would have been better from the get-go than age? Probably just something like date of birth, DOB. And, of course, in SQL do we have the ability to store dates, even date times. So here we probably have a better candidate for exactly one of those numeric types, so to speak. Now, we've only just begun to scratch the surface of available data types and data we might want to store. In fact, our spreadsheet or database could contain even more types. But now let's just suppose that we're happy with our types. And the goal now at hand is to start searching our data and storing that data in an actual database. I've been using here Google Spreadsheets just to lay out my schema sort of as a worksheet, if you will. But now let's graduate to actual SQL syntax and suppose that this same data is stored not in Google Spreadsheets or any other but in some database elsewhere, a piece of software running somewhere else on my own computer or somewhere in the cloud that knows how to organize this data, still in rows and columns but in such a way that I can use this new language, SQL or SQL, in order to access my data. So what are the fundamental operations that SQL supports, or a relational database, more generally? It turns out that throughout computer science is there a pattern of operations that many different systems support. In the world of databases, you have fairly crassly what's called CRUD, the ability to create data, read data, update, and delete. But more specifically, in the context of SQL, this new language, our last, you have the ability to create data, select data, a.k.a. READ data, update or insert data, a.k.a. UPDATE, or delete or drop data, a.k.a. DELETE. So whereas in SQL you have these very specific terms, they are just representative of a class of operations that you might see throughout computer science. So how do we go about using SQL? Well, short of buying and installing something like Oracle or downloading something free like MySQL, we can simply use something that's indeed, by definition, lighter weight called SQLite. SQLite is an implementation of SQL, this database language, via which you can install it on your own Mac or PC, and it uses not special software or servers but rather just a file on your own Mac or PC that stores all of those tables and, in turn, rows and columns. It thereafter creates an abstraction of sorts as though you have a full-fledged server running on your own machine with which you can communicate using SQL. Now, any number of programs can be used to actually talk to a SQLite database. You might use a purely textual interface, so-called command line interface, using only your keyboard. Or you might use something graphical, a GUI, a graphical user interface. In fact, one of the easiest programs to use is this one here, DB Browser, freely available for Macs, for PCS running Windows, or Linux, or any number of operating systems as well. In advance, I've gone ahead and opened up a database in a file whose name ends in .sqllite or also commonly .db. This then is, again, just a file, a binary file filled with zeros and ones that collectively represent patterns of rows and columns and the tables that contain them. And if I open this program, I see all of the tables or, if you will, sheets from our old spreadsheet world. This happens to be a database all about music. And indeed, I have a table about musical albums and artists, customers and employees, and genres, and invoices, invoice lines that represent actual songs bought, the types of media involved, and playlists and playlists tracks via which users can actually customize those songs, and then lastly, the tracks or the songs themselves. In other words, someone else on the internet has gone to the trouble of aggregating all of this data about music and their authors and organized it into an SQL database. Now, how? Well, if you look to the right here, you'll see a number of esoteric commands all involving CREATE TABLE. And indeed, CREATE is one of the four fundamental operations that SQL supports. And without going into the weeds of how these tables were created, they were created in such a way that the author had to decide on every table's columns' names as well as their types. And that part is interesting. For instance, let me go into the Albums table here and expand this one now. Here I have Album ID, and Title, and, curiously, Artist ID. But you'll notice in the Album table we have no mention of artist's name. In fact, if we follow the white rabbit here, we see in the Artist table that, oh, in Artist do we have an artist ID and name. And indeed, if we keep digging, we'll see that in each of these tables there's not quite as much information as I might like. In fact, IDs, whatever those are seem to be more prevalent. And that is because, in the world of databases, and perhaps even some of your own spreadsheets, it's conventional and daresay best practice to normalize your data. You indeed should try to avoid having redundancies within tables and even across tables. Now, what does that mean? Well, consider our own spreadsheet of users that we're trying to get into a database. I had a column there called Address. And I proposed that my own address was 33 Oxford Street in Cambridge, Massachusetts 02138. Now, it turns out that there's quite a few other people at my workplace, and so they might have that exact same address as well. And in fact, that address doesn't really lend itself to very nice printing on envelopes because all I did was separate things with a comma, not actually hit Enter. And in fact, because everything was all in one column, it would seem a little difficult to search by, say, zip code. Find for me all of the users from Cambridge, Massachusetts in 02138. Well, you could do it sort of searching free form all of the values in some column. But it wouldn't be particularly clean or efficient because you'd have to look at and then ignore all of those other words and numbers, 33 Oxford Street, Cambridge mass, if you only care about that zip. So before we forge ahead, let's see if we can't infer why this database is seemingly, at first glance, more complicated than you might like. Well, if I go back into my spreadsheet here, what really should I have done with Address? I probably should have quantized that value into streets and then city, then state and zip code, not having just one column but several. In fact, here, what I've gone ahead and done is separate out Address into Street and City and State and Zip. And for each of those have I specified a very precise type. I've gone ahead and proposed that Street is still varchar (255), as is city, because I don't really know an upper bound, so we'll at least be consistent. For state, I've hopefully been smart in at least assuming users are in the US. I've said char (2) for just that two-character code. And for Zip, too, I'm preemptively trying to avoid a mistake with even Cambridge whose zip codes start with a zero. Where I had to specify again that that's just an integer, I might actually lose mathematically that first digit. But by storing it as a char with five characters and no hyphen or four others can I ensure that that 02138 remains exactly that. But here, too, we have a bit of inefficiency. Imagine if more and more users from my own building register for this particular application. You might have again and again these exact same values if all of my colleagues are in the same building. And here, as with programming, as soon as you start to see a lot of redundancy, the same data again and again, there's surely an opportunity for better design. This is correct. My colleagues might also live here if we added their names and their distinct emails. But they don't necessarily need to have the same exact text stored again and again. So what might you do in this case even in the world of spreadsheets? Well, on the sheet, I might just rename this actually more explicitly to users. And you know what? Let me go ahead and create another sheet in my spreadsheet world and call this, say, Cities. And my cities might actually have a city name and perhaps a state and a zip. But in this leftmost column, I could be smart and start to assign each of these cities some unique ID or identifier. And so here might I have just a unique identifier, typically an integer. City might again be varchar (255). And State might, again, be char (2). Zip Code, meanwhile, can stay as char (5). But now what I can do is presume this, that if Cambridge, Massachusetts, 02138 is in this sheet or, if you will, table let's arbitrarily but reasonably give it a unique identifier as one. And if I were to add more cities here, like Allston, Massachusetts and its zip code, I could sign it a unique ID of two. Because now that I have this data, here's where you get that relational aspect. I can relate this sheet or table to my other as follows. Back in this table now can I go ahead and delete much of this redundancy here and actually go ahead and say city is not really a city but it's a city ID. And here now it can be a proper integer, because the city in which all of my colleagues live is the exact same one as mine. Now, here there's still an opportunity for improvement, to be fair. Because if all of us are in the same building, maybe that should be factored out as well. And if I really wanted to go down this rabbit hole, I could add another sheet or table called, say, Buildings and factor out also that 33 Oxford Street, give it a unique ID, and only store numbers. So in short, the more redundancy and the more waste that you end up having in your data, the more opportunities there are to, so to speak, normalize it. To factor out those commonalities and create relations between some pieces of data and others and the convention in computing is to do, quite simply, numbers. Why? Well, it turns out it's a lot more efficient to just relate some data to others by relying on small integers. Four bytes is not all that much. And in fact, inside of a computer CPU are small registers, tiny pieces of memory that can be used on the most basic of operations; additions, subtractions, and comparisons for equality. And so with small values like integers can you very quickly reassemble or relate some data to others. And so here we have a general principle of database design to normalize it by factoring things out. And so if we go back into our musical database, you can perhaps infer why the author of this data did that preemptively. They have their albums having album IDs, a number like 1, 2, and 3, a title, which is the actual title of that album. And then to associate those albums with artists, they've used not the artist's name but an ID. In this way can an album have the same artist as another without storing that artist's name twice. Moreover, if the artist happens to change his or her name, as is not uncommon in the musical world, you can change that name in just one place and not have to scour your tables for multiple copies. And so if we continue this logic, we'll see in more and more tables that we have this principle of naming the data but then assigning it an ID. And if you want to relate some of that data to another, you simply store the ID, not the actual values. Of course, this is decreasingly useful as we go, because now some of my data is in this table, and that, and this other table, and here. And so while very academically clean and refined, it doesn't anymore seem useful to the data scientist in front of his or her computer that just wants to answer questions about data. And yet it's all over the place, whereas before, with Excel, you could just pull up a window. But that's where SQL itself comes in. SQL does not just prescribe how to type your data but rather how to query it as well. And in this particular program here, DB Browser, I can actually go over to this tab here for Execute SQL. And I can begin to execute those actual commands, SELECT, and CREATE, and UPDATE, DELETE, and others and actually see the results. What SQL allows you to do is express yourself programmatically using a fairly small language, albeit new, that allows you to create temporary tables, just the results, just the result sets, so to speak, that you want, only those rows that you care about. So for instance, if I want to see all of the albums in this database, well, in the world of spreadsheets, I would just double click in and peruse. But in the world of SQL, I'm actually going to type a command. I'm going to go ahead here and say SELECT star, for give me everything, from the table called Album, and then semicolon to finish my thought. I'm going to go ahead and click the graphical Play button here to execute this command. And you'll see suddenly that here are all of the albums apparently in this table, 347 of them in total in this particular database. And notice that all of the titles are in one column, the Album ID is to the left, and the Artist ID, of course, to the right. Well, if you're now curious who is the artist behind the album called For Those About to Rock, We Salute You, well, I can just make a mental note that the artist ID is one. And you know what? With SQL, it turns out you can use predicates. You can qualify the data you want. You don't have to say, give me all. You can say, give me this. So how do I do that? Well, I can actually say SELECT star from Artist, the other table, where-- and here comes my predicate-- artist ID equals one, a semicolon again to finish that thought, hit Play, and voila. It turns out it's AC/DC, the artist behind that particular album. Of course, this felt a bit manual. And this seems no better than a spreadsheet wherein now more of the work seems to be on me. But SQL's more expressive than this. Not only can you create, and select, an update, and delete data, you can also join data from one table and another. So in fact, let me go ahead and do this a little more explicitly. I want to go ahead and select everything from that original album table, but I'd like to first join it with the artist table. How? Well, I want to join those two tables, this one and this one, kind of like this, conceptually, so to speak. On what fields? Well, in Album, I recall there's an artist ID. I want that to be equal to artist.artistid. In other words, if you imagine this hand to be albums and this hand to be artist and the tips of my fingers each represent those artist IDs, we essentially want the SQL to line up my fingers so that I have on the left my albums and I have on the right the artist. And every one of my rows now has both. Let me finish my thought with a semicolon here and hit Play. And voila, now we see even more information but all together. We see that album number one, For Those About to Rock, We Salute You, has an artist ID of one and clearly an artist ID of one but a name. Well, what's happened? Well, both of these tables have kind of been concatenated together but joined intelligently such that the artist IDs in both tables line up on the left and the right. Of course, at this point, I really don't care about all these numbers. And I definitely don't need the temporary duplication of data, so I don't have to just keep saying star, which is the so-called wild card, which means give me everything. I can actually instead just say, give me the title of albums and the names of the artists by specifying with commas the names of the columns that I actually want. And if I now click Play, I get much simpler results, just the titles of albums and just the names of those artists. Now, how else can we express ourselves with SQL? Well, there are other keywords besides WHERE and besides JOIN. You can also group values by something and specify that they must all have something as well. For instance, let me go back to my data here and consider which artists have multiple albums. Well, if we consider the results that we had earlier do we have AC/DC as the artist behind For Those About to Rock, We Salute You, but also behind Let There Be Rock. Moreover, this band Accept has multiple albums as well. And if we scrolled further, we'd probably see others. So if we'd like to see those bands, those artists that have multiple albums, how can we do this? Well, what if I somehow collapsed all mentions of AC/DC into just one row, and next to that row I put an actual count? Well, I could refine this query as follows. I can say, yes, join these tables together, but then collapse them, if you will, based on grouping some common value. Let's go ahead now and group the data by name so that any artist that appears multiple times will effectively be collapsed into one. But I'd like to remember how many rows got collapsed into just one. And so rather than select the albums themselves this time, I'm going to select the album's name and then the count there of, thereby specifying show me the name and show me the count of that name before we grouped by. If I go ahead now and finish my thought and click Execute, I'll see that, indeed, AC/DC had two names and Aaron Goldberg had one. And if we keep scrolling, we'd see all of the bands' names that had one or more albums and the count for each of those. If I want to filter out now maybe those bands that only had one hit album in this database, I can instead say, you know what, go ahead and group by the group's name, but then show me only those bands having more than one album. Well, here, too, can I simply filter my results saying literally quite that, having some number count name greater than one, semicolon. Hitting Play now and you see the results immediately eliminate all of those bands that had just one album. And now if I scroll through, we'll see all those bands that had two or really more. And so with SQL can you begin to express yourself certainly more arcanely than you could with just a click of the mouse in a spreadsheet but ever so much more powerfully. But the key is to build these queries up piecemeal. Indeed, this now already looks quite complicated. But we began by just selecting all data and then refining, refining, refining, thereby working at this level and only getting into the weeds once we knew what we wanted. Notice now just how fast these operations have been. Indeed, at the bottom here do I see that 56 rows were returned. How fast? In one millisecond. And indeed, even though it might take me longer to describe in English what it is I want, in fact, the computer can find this data so quickly. But that's because we've done at least one thing already. This data is already organized with these proper types. And it also has a few other key characteristics as well. When storing your data in a SQL database, you're also asked to answer some questions. For every table you're asked to specify effectively what, if any, is this table's primary key. These are key words in SQL that you can apply to some column that says, hey, database, this column is my primary value that uniquely identifies every row in this table. In the context then of our user spreadsheet with which we began this discussion, that identifier for City was a primary key. I might very well have used a city's name as unique or perhaps even the zip code. But far more efficient, especially if you want to avoid ambiguities or duplication, is to just use that integer. And so here a primary key is almost always a numeric value, at least in the most optimized layouts of data. But it guarantees to the database that there will be no duplicates on this particular value. But more powerfully, you can define in one table a column to be a primary key, but then in another table that same value to be a so-called foreign key. In other words, throughout this example in the actual SQL database, I had Albums in one table and Artists in others. And that Artist table had an Artist ID column that was within that table known as a primary key. But when you saw that artist ID in the Albums table, it was contextually there a foreign key. Now, beyond semantics, this is an actual and valuable property. It ensures that the database knows how to link and how to link those two columns efficiently. Moreover, you have even fancier features available to you when you declare keys. You can also tell the database, you know what? If I ever delete this artist, go ahead and delete all of that artist's albums as well. And you can configure a database automatically to have this so-called cascade effect whereby data is updated and your data is consistent at the end of the day based on those relations, if you will. Now, in columns of data can you also specify that every value's got to be unique. It doesn't necessarily need to be your primary key, but it might still be unique. Like what? Well, in our Users table that we were creating on the fly, an email address might, by human convention, be unique, assuming I don't share it with someone else. But using an email address, multiple characters, many possible characters, tends not to be the most efficient way to search on data. So even in my Users table might I have added for best practice a numeric column as well, probably called ID, as my primary key. But I might still specify when moving that data from my Google spreadsheet into a SQL database that, you know what? Please ensure that this Email column's unique so that I or some other programmer doesn't accidentally insert duplicate data into this table. And moreover, the database then can search it more efficiently because it knows how many, if any, there are of any one value. There's one and only one maximally. Lastly, there's this keyword here, index. Well, the other feature you get so powerfully from proper databases is the ability to search and sort values efficiently. But the database doesn't know a priori how to do either on the data you care about. Because only if you tell the database what data you plan to search on and sort frequently can it help you in advance. And so if when creating a database table you tell the database server, go ahead and index this column, what it will do is use a database structure, a tree structure not unlike our so-called binary search trees, that pulls all the data up in an artist's rendition thereof, thereby ensuring that it doesn't take as many steps to find some email address or something else because you have indexed that particular column. It won't store it linearly top to bottom or left to right. It will store it in a two-dimensional tree structure of some sort, often known as a B-tree, that allows you to grab the data in hopefully logarithmic and not linear time. Well, turns out there are even more features you get from actual databases like SQLite. Well, you have the ability to specify when creating a table, please go ahead and auto increment this column. Well, what does that mean? Well, I very manually a moment ago assigned Cambridge the unique identifier of one. But why should I, the programmer, even have to worry or care about what the unique values of my inputs are? I just need that that key exists. I do not need to care about what that value is, just that it exists and it's unique. So you can tell the database on its own, please go ahead and, any time I add a new row to this table, increment that value automatically. You can also specify to a database, please ensure that no values in my database are null that is empty, thereby ensuring that a bug in your code or some missing user input doesn't accidentally put into your database a row that's only sparsely filled with real data. The database can help you with these things just as Python can as well, but it's a final layer of a defense before your data. And then functions as well. SQL itself is a programming language. It might not necessarily have as high ceiling as something like Python, as much functionality. But built into SQL are any number of functions. If you want to select the average revenue across any number of purchase orders, you can use the average function. And in MySQL Query can I select data but pass it into one of these functions and get back an answer without having to paste it into, say, a spreadsheet, let alone calculator. I can count rows just as I did. I wanted to count the number of albums that a given artist had, and COUNT was a function supported by SQL. You can get maxes and mins. You can get summations of value and so many more features built into this language. And while the tool you might use might not be DB Browser, perhaps it's just a textual interface or even something even more graphical, it ultimately is just executing on your behalf the SQL queries and handing them off to the database for execution. Now, with all of these features that you get with the database, it all sounds too good to be true. You can scale, you can eliminate redundancy, and you can still select all the data you want. But unfortunately, you have to start to think harder about the design of your system. Because databases are sometimes vulnerable to mistakes, if you will. Consider, for instance, something like Twitter that tends to keep track of how many times something's retweeted. Or consider an app like Instagram, which keeps track of how many likes or upvotes or views some post has gotten. On the most popular or viral of media, those counters, those integers might be getting incremented ever so quickly. If you and I both happen to view or like something at nearly the same time, well, that interface from us into the system might actually trigger some instruction on some server somewhere to tell Instagram's database to increment some value. But how does a database go about incrementing a value? Well, if the value of views or the value of counts is somehow stored in a database, a column of type integer, and you go ahead and execute a SQL SELECT in order to get that value, and, for instance, 100 people before me has liked some post, well, the value of that result comes back as 100. I then do some math in my code, perhaps Python. I increment the 100 to 101, and then I use a SQL UPDATE, as you can, to push the data back into the database. But suppose both you and I anywhere in the world both happen to like a post at the same or nearly the same time, as can certainly happen when posts are especially popular. Unfortunately, a computer can sometimes do multiple things at once or at least in such rapid succession that it appears to be at the same time, but a race of sorts can happen, a race condition, if you will, as follows. If both my button and your button is pressed at nearly the same time and that induces execution of code on Instagram server that selects for both of us the current count of views, suppose that both of the threads, so to speak, both of the SQL operations that select that data both come back with the value 100, each of the blocks of code serving our requests go ahead and increment that value to 101 and then, via SQL UPDATE, pushes that value back to the database. Unfortunately, because both you and I induced an evaluation of that math at nearly the same time, what the database might end up storing is not 102 but 101. In other words, if two people's input is triggering a race to update data, the database had better keep track of who and when asked for that update. Otherwise, you lose data. Now, in the case of tweets or likes, it's probably not all that problematic. Though, frankly, that is their business. But you can certainly imagine that with banks or financial institutions, where the numbers matter ever so more, you certainly don't want to accidentally lose track of some dollars. And so how do we go about solving this in the case of a SQL database? Well, it turns out that there is fairly fundamental primitives or solutions you can use. Consider a metaphor in the real world, such as, say, a familiar refrigerator. And suppose that you and your significant other happened to both like something to drink at the end of the day, like some milk. And so you go ahead when you get home, and the other person's not, and you open the fridge and you see, oh, darn it, we're out of milk. And so you close the fridge and you head downstairs and you walk to the nearest store. Meanwhile, that other person comes home and, wanting some milk, opens the fridge, and darn it if we aren't out of milk as well. And so that person now heads out, perhaps in a different car, in a different route, and heads to the store, some other store to get milk. Fast forward some amount of time and both of you come home, and darn it if you don't now have twice as much milk as you need, and it does go bad. And so you've both ended up buying milk when really only one of you needed to. And this is similar in spirit, but now you've got more data than you actually wanted, but it's not the right amount of data. So why did that happen? Well, both of you, like Instagram, inspected the state of some value and made a decision on it before the other person was done acting on that information. So in our very real world of milk, how could you go about avoiding that conflict, that race, to restock the fridge? Well, you could quite simply grab a pen and paper and leave a note, so to speak, on the fridge telling someone else, gone for milk, and hopefully they then would not do the same. Or perhaps more dramatically you could lock the refrigerator in some sense so that they can't even get into it and inspect that state. But ultimately, you need your act of checking the fridge and restocking it to be what we'll call atomic. And databases can and hopefully do provide atomicity, that property, the ability to do multiple things together or not at all but not be interrupted by someone else's work. And in fact, in the database world, these are generally known as actual locks whereby you say to the database, don't let anyone else write to this table or row until I am ready to release or unlock it. That, of course, though, tends to be a very heavy-handed solution. Say don't let anyone else touch this data. Better to do it on a more fine-grained control so that you don't slow your whole system down. And so SQL databases tend to support what are more finally known as transactions whereby you can execute one or more commands again and again and again back to back but make sure that all of them go through it once before, say, your commands that your user input induced actually is allowed to get executed. Now, honestly, even in the most high-tech of places like Instagram and Twitter, this is a hard problem. Because at some point, even waiting for my operations to complete before yours can go in can be a challenge and a bottleneck for everyone else. And so in the case of the most viral of posts, what can systems these days do? Well, you could just kind of wait and write that data back to the database that is updated eventually. And indeed, another property of databases is known as just that, eventual consistency, a property that says, don't lose any data, but only eventually make sure it's reflected on the server. Eventually get the value right, but do get it right. And so what Instagram and Twitter and others might do is just cache or buffer that data, waiting until things have quieted down 'til the post is no longer viral or most users have gone to sleep. Now, that alone might not be to the best of solutions, but it avoids having to get the highest powered and most expensive hardware. Of course, in other contexts, that might be the better solution. In the world of finance, sometimes it comes down to the actual length of cables or distance from some server to another to ensure that the data gets there so fast that you don't run into these sorts of challenges. So databases can solve this, but the developers and designers that use those databases need to know how to do it and that they should. Lastly, there's another challenge as well, unfortunately all too commonly done these days because folks just don't defend against it via common mechanisms. It turns out that a bad actor somewhere on the internet or your own network can potentially, if you're not careful, trick a database into executing commands that you didn't intend. For instance, suppose in the context of Python you have some code that looks a bit like this. Here is a program written in a mix of pseudocode and Python that's designed to allow a user to input the title of an album for which they want to search. And so here I use the Python function INPUT to prompt the user for just that. On the left-hand side do I clear a variable called Title, and then assigned from right to left, the user's response to that variable. Then suppose for the sake of discussion there is some function called EXECUTE whose purpose in life is to take input that itself represents a SQL command. That SQL command might be this, so like star from Artist where Title equals something. Now, what is that something? Well, if I have the user's input in a variable called Title, I can use the plus operator in Python, not to add but concatenate two strings together, coding them singly and completing that thought. The problem, though, with SQL is that user's not really to be trusted. And whether the user's input is coming from a keyboard on a Mac, or PC, or perhaps, more compellingly, from an app or website, you probably should not trust all your users. Because suppose that your user typed in not the album name for which they want to search, Let There Be Rock, but rather they type something like Let There Be Rock, semicolon, DELETE, thereby using SQL's apparent DELETE command in order to trick your database into executing not one but two commands, a SELECT and DELETE. And indeed, this is what's known as a SQL injection attack, the ability for an adversary, a bad actor out there, to somehow trick your database and your code into executing some command that you didn't intend. How is that possible? Well, some of these characters are dangerous, so to speak. A semicolon in SQL tends to separate one command from another. It finishes your thought. And if you yourself don't anticipate that some human, this bad actor, might type in themselves a semicolon when they really shouldn't be typing SQL at all, you might mistake that semicolon for the actual terminus between one command and another. And if you just blindly pass it into your server and let it execute as usual, you might execute not just that SELECT but that DELETE or anything else as well. And in this way can an adversary not only delete data from your database but maybe select more than you intended, or update or insert. It's ultimately up to you to defend against these threats. So, how? Well, it turns out that there are libraries, code written by other people that, frankly, via very easy-to-use functions, just make it easy to sanitize or scrub, so to speak, user's input. What do these libraries or these functions typically do? Honestly, they just escape, so to speak, these dangerous characters. Something like a semicolon or perhaps a single apostrophe that might, in SQL, have some special and dangerous potential meaning, they just escape them as by putting a backslash, a special character in front of them so that if the human were to type in Let There Be Rock, semicolon, DELETE, that would actually be interpreted safely by your database as a search for an album called Let There Be Rock, semicolon, DELETE, which of course most likely is not the name of an album. So that query would probably not return or select any results. But more importantly, it would not be tricked into executing two SQL commands. Rather, it would execute only the SELECT but with a nonsensical value. Lastly, consider what a database is. It's really a piece of software running on some computer somewhere, be it on my own Mac, or PC, or some server in the cloud. But if you have just one database, as I seem to keep presuming, you have this so-called single point of failure, again, just as we had in the world of cloud computing more generally. And so with something like data where you don't want to lose it and you certainly don't want all of your web servers or apps to go offline just because one server, your database server, has gone out, it's time to revisit the architecture or topology of systems more generally. Something tells me that it's not going to be sufficient to have just one database. You probably want two. But if you have two databases, now how do you decide where to put the data? Do you put it here, or over here, or maybe in both places? If you put it in both places, though, you're then using twice as much space, so already we've opened a can of worms. To solve one simple problem, don't be a single point of failure. But that's going to cost you some time, or some money, or certainly space. So what can you do if you're architecting a system that has now not just web servers but, say, databases? Well, odds are you're going to want to have not just the one, pictured here as a cylinder, as this convention, but you're probably going to want to have a second as well. But of course, if you have two of them, odds are it's not sufficient just to store half of your data on one and half of your data on the other, because, of course, you've not solved the single point of failure. You now just have two single points of failure because half of your data could be lost here or half of it here. So odds are you're going to want to start having backups of data. But you don't want to necessarily have to waste time restoring from backup, especially if you want to maintain as many as five nines of uptime. So odds are you're going to want to have these databases really be duplicates of one another. And whenever you write data to one database, you should probably write it to the other in parallel. So, yes, admittedly, you have just spent twice as much space and, frankly, twice as much money. But at some point those sorts of costs are inevitable. But there's other ways to scale here, too. You can, of course, hit a ceiling on vertical scaling even when it comes to databases. After all, if a database is just a program running on some server and there is only so much RAM or disk space or CPU in that server, eventually you're not going to be able to store as much data or as quickly as you want. So what can you do? Well, you could, for instance, shard your data and have not just two but maybe four or more servers and put all of the users whose names start from A to M on one half of your cluster of servers, so to speak, but maybe everyone else from M to Z based on, say, their last name can go on the others. To shard a database means to split the data in some predictable way that you can repeat again and again. But even there, too, even if only the As through Ms are going to the left, you want to make sure that you still have that backup or replica. And this arrow suggests that they really should be intercommunicating, not unlike load balancers we've seen. But there's another way you can scale your databases as well. You don't have to have databases doing both reading and writing. To read data from a database or any server means to take it from its memory and read it into yours. And to write means to do the opposite, to save it. Well, what you can do actually in the world of databases is also replicate your databases multiple times. And you might have connected to these two primary databases multiple other databases that are just copies in one direction and not both. And what you might then do is use these two primary databases not only to read but to write, abbreviated here RW. But these other databases down here, which are just copies of the ones to which they're connected, are just called read replicas. They exist solely for the purpose to read from them again and again. When might this make sense? Well, in some contexts, like social media, like Facebook, it's probably the case that there are more reads than there are writes. That is to say you probably know more people who post more content than you but you probably still read or look at theirs. And so if the data for your business follows that pattern whereby writes are maybe common but reads are way more common, you can do exactly this model and replicate again and again, honestly, as a tree structure for efficiency so that it doesn't all have to replicate one at a time. But then you can write software, be it in Python or something else, that writes data only to these one or two servers but reads from any number of them as well. But this, too, is a bit of a rabbit hole, because at some point you want to have this redundancy not in one location but others, east coast and west coast, one country and another. And at that point, you might actually run into the limitations of time. Because after all, it takes a non-zero number of milliseconds or seconds for data to travel long distance. Consider after all how long it might take data to go from Cambridge, Massachusetts to somewhere in Japan. That's far longer than it might take to just go down the road to MIT. So here, too, we can revisit all of the problems we saw in the world of cloud computing and servers more generally. They're back to revisit in the context of databases. But with databases, you care ever more that these things not go down, or if they do, that you have spares, backups, or replicas. Because now that we're storing our data in this centralized place, we have to think hard not only about how we're scaling computationally but how we're scaling in terms of our data as well. So consider where then we began. We started by laying out data in a spreadsheet, be it Microsoft Excel, or Apple Numbers, or Google Spreadsheets. From there we considered what types of data we might store there so that if we want to upgrade, so to speak, from a spreadsheet to database, we know what types we can specify. And in SQL, whether implemented in SQLite, Oracle, or MySQL, or something else, they tend to be standard types that tend to be common across platforms, albeit with some variations, so that we can think hard about these types and then ultimately help the database help us be performant. Because if I know that I'm going to be searching or selecting based on certain data, I can tell the database, for instance, to make it unique or at least index it. And then using SQL constructions like SELECT, and INSERT, and UPDATE, and DELETE, and yet others can I manipulate that data and get at it even faster, frankly, than the human me could with a mere spreadsheet. But with the design of any system, as with databases, we start to open new cans of worms and new problems as we start to explore more sophisticated challenges. But here, too, many, if not all, of these problems can be solved by simply reducing the problems to first principles and consider, what is the problem to be solved? How is our data represented? Where is it stored? And consider ultimately what business constraints or legal constraints we have when manipulating that data and consider what tools of the trade are available to us. This then is database design and, more generally, design unto itself, not just focusing on the correctness of implementing solutions to problems but the efficiency and the scalability as well thereof.
B1 中級 弁護士のためのCS50 2019 - データベース設計 (CS50 for Lawyers 2019 - Database Design) 1 0 林宜悉 に公開 2021 年 01 月 14 日 シェア シェア 保存 報告 動画の中の単語