News for hackers. Stuff that matters.
Re:Things I wished more developers knew about databases by pbowyer on Wednesday April 22, 2020 @11:14AM (#22946410)
I like SQL when I'm not writing reporting queries. GROUP BYs bite me (With MySQL 8 I end up reaching for the ANY_VALUE() function), and I end up with more subqueries than I feel I should need.

When working with time-indexed data I feel I'm forcing the database to do something it doesn't want to. E.g. if I want to answer the query "How many sales are there per day this month?" and I want an entry for every day in the month, even when there were zero sales. Or another query asking "Which days didn't have sales?" and listing the days. I haven't found a way to do this in-database. I end up answering in code based on the data I fetch.

Definitely feel it's me not SQL, but not found the answer.

Re:Things I wished more developers knew about databases by EvanAnderson on Wednesday April 22, 2020 @11:34AM (#22946656)
Your example is a fun one and not too hard to do.

Thinking about the intersection of sets is important for grokking what you're trying to do in that example (and with SQL in general). For me, at least, SQL suddenly made a lot of sense when the idea that I was working with intersections and subsets of sets.

In your example you have (1) a set of all dates in a range, and (2) a set of sales totals for days that had sales. Set 1 could be a "numbers table" or generated with something like "generate_series()" in PostgreSQL. Set 2 is made by summarizing the data in a sales table by date using "GROUP BY" and "SUM".

Then you're just looking at JOIN'ing those sets and COAELSCE'ing the NULL returned from days when there are no sales into 0.

Re:Things I wished more developers knew about databases by pbowyer on Wednesday April 22, 2020 @11:42AM (#22946752)
> Set 1 could be a "numbers table" or generated with something like "generate_series()"

I've done it this way when doing it per-minute or per-hour - i.e. for 60 minutes or 24 hours; fixed, constant ranges. I picked this example because (to me at least) it's more difficult :)

Calendar months have different lengths, so something would have to tell it which month to use (and account for leap years). But say you wanted the last 60, 90 or 120 days? I suppose you could first create a temporary table to be the "numbers table", custom made for the range you want to fetch - is there an alternative?

Re:Things I wished more developers knew about databases by Amezarak on Wednesday April 22, 2020 @11:54AM (#22946905)
I have done this many times. My preferred solution was to create a recursive CTE beginning with the desired start date and ending with the desired end date. I often based these on MIN and MAX subqueries, but obviously you could also pick arbitrary dates.

You can use days, months, weeks, years, or whatever you wanted as the "increment" in the CTE using the DATEADD function. Then (for nulls) you simply LEFT JOIN your CTE with the desired date part (aggregated) of your table. This gives you your first answer, and a simply filter will get you your second answer.

It's also common for people to just create all these date tables beforehand as actual, materialized tables. In my opinion, this is less elegant (what happens in 2101?! Somebody had better remember to add to the table!) but it naturally works just as well and probably saves some perf.

Re:Things I wished more developers knew about databases by EvanAnderson on Wednesday April 22, 2020 @11:50AM (#22946849)
In many databases you would create a function to fill the role of that "numbers table" and, effectively, map that function onto rows. That function can express whatever "richness" you need (i.e. civil calendar month, phase of the moon, etc... >smile<) that might not be easily expressed as a series.
Re:Things I wished more developers knew about databases by will_pseudonym on Wednesday April 22, 2020 @11:53AM (#22946892)
There are date functions in most SQL packages which can create the limiting factors you're looking for, just as in procedural languages. Once you have the list of dates you're interested in, the problem becomes simple.
Re:Things I wished more developers knew about databases by will_pseudonym on Wednesday April 22, 2020 @11:48AM (#22946818)
I learned SQL working in Access. There wasn't anything like generate_series(), and I remember having to do things like above by having a start and end date, cross joined with a table of nonnegative integers I made, and creating a field which added an integer to the start date, subject to being no greater than the end date.
Re:Things I wished more developers knew about databases by JamesSwift on Wednesday April 22, 2020 @11:31AM (#22946618)
The answer is likely that your storage schema is incorrect. You have things stored in OLTP (i.e. app database) but trying to read it as OLAP (i.e. reporting database). Once you reimagine the data in the OLAP style then these kinds of queries are simplistic.

EDIT: specifically for your example, in an OLAP style you would generate a Times table and then foreign key the sales table to it based on the date. Then you can easily query against that Times table as the filter/bucket for your queries.

Re:Things I wished more developers knew about databases by pbowyer on Wednesday April 22, 2020 @11:45AM (#22946784)
Yes, agreed. It was designed for OLTP not OLAP, and I have to get my mindset into that.

For days as my example has, would the Time table be generated for say 1970-2050? Given months are of different lengths and there are leap years, I'm assuming this is needed, rather than storing a single year.

Re:Things I wished more developers knew about databases by JamesSwift on Wednesday April 22, 2020 @12:35PM (#22947393)
Yes, the time table is a pregenerated set of all possible moments in time for whatever resolution you care about. So, for example, if you cared about day resolution it might be

  date_key | year_num | month_num | day_num | quarter_num | week_num | month_name | month_name_short
  ...
  20200101   2020       1           1         1             1          january      jan
  20200102   2020       1           2         1             1          january      jan
  ...
  20200401   2020       4           1         2             14         april        apr
  ...
You would FK on the `date_key` and add as many columns as you need to support querying against the dimension. I also like to add a proper `datetime` representation of the date so I can easily do a date range query.
Re:Things I wished more developers knew about databases by jacques_chester on Wednesday April 22, 2020 @02:02PM (#22948375)
I found Kimball's book to be interesting and helpful.

I think the Kimball approach gets bogged down in temporal evolution (Slowly Changing Dimension type 1? 2? 3? 4? 5?), but that's more about the underlying absence of meaningful bitemporalism than the dimensional schema approach per se.

Re:Things I wished more developers knew about databases by mulmen on Wednesday April 22, 2020 @06:53PM (#22951082)
For anything other than the absolute simplest case you should have two databases. One for OLTP (the application(s)) and one for OLAP (the reporting).

If your reporting queries are hard or complex it's because you did a bad job architecting the reporting tables. Reporting queries should almost always be the simplest form of query if you designed your warehouse properly.

Days with sales is easy with a simple SUM or COUNT and GROUP BY.

Days without sales is easy if you think about this dimensionally.

  SELECT dim_days.day_of_month,
    COUNT(fact_orders.order_id)
  /*
    Select from dim_days first because you want every day.
  */
  FROM dim_days
  /*
    Outer join to the fact table to pull in the data you have and add that to your dates.
  */
  LEFT JOIN fact_orders
    ON dim_days.calendar_day = fact_orders.order_day
  /*
    Filter results by the desired range.
  */
  WHERE dim_days.month = 4
    AND dim_days.year = 2020
  GROUP BY dim_days.day_of_month;
As I continue to say:

"SQL is easy. Data is hard."

Re:Things I wished more developers knew about databases by alexilliamson on Wednesday April 22, 2020 @11:57AM (#22946940)
You can (IMO) pretty succinctly enumerate all the days in a month with a recursive CTE. The example in the following link does it for weekdays, but it's the same idea: https://www.sqlservertutorial.net/sql-server-basics/sql-serv...
Re:Things I wished more developers knew about databases by sk5t on Wednesday April 22, 2020 @11:26AM (#22946553)
A more functional mindset can definitely help here. Think of your "sales per day" model in terms of starting with a sequence of days--startingDay up to startingDay + n--as the input to a function that maps to an aggregate of that day's activity.

Aggregate functions in SQL are IMHO quite awesome once you develop a comfort level to stop worrying about them per se. I wouldn't like to try to get Excel to tell me--or write the code to do manually--something like "show me the standard deviation in units sold by day of week over the last ten summers."

Re:Things I wished more developers knew about databases by pbowyer on Wednesday April 22, 2020 @11:46AM (#22946799)
I find thinking about analytic queries like these as map/filter/reduce easier than thinking in terms of the SQL!
Re:Things I wished more developers knew about databases by taffer on Wednesday April 22, 2020 @12:31PM (#22947358)
SQL and functional programming constructs are actually quite similar:

SELECT -> map

FROM -> stream

JOIN -> flatMap

WHERE -> filter

GROUP BY -> collect

ORDER BY -> sorted

https://blog.jooq.org/2015/08/13/common-sql-clauses-and-thei...

Re:Things I wished more developers knew about databases by karmakaze on Wednesday April 22, 2020 @09:21PM (#22951905)
I don't see how JOIN is like flatMap. It's a subset of a cross-product. It's more like do-notation:

  x <- ..
  y <- ..
  return (x, y)
Re:Things I wished more developers knew about databases by taffer on Thursday April 23, 2020 @04:15AM (#22953825)
> I don't see how JOIN is like flatMap

This is explained in the blog post I linked, including an example.

Re:Things I wished more developers knew about databases by karmakaze on Thursday April 23, 2020 @03:38PM (#22960229)
It's really flatMap/map from the link:

  List<Integer> s1 = Stream.of(1, 2);
  Supplier<Stream<String>> s2 = ()->Stream.of("A", "B");

  s1.flatMap(v1 -> s2.get()
                   .map(v2 -> tuple(v1, v2)))
    .forEach(System.out::println);
That's just a nested loop.