Tom Ellison CrossTab Question

  • Thread starter Thread starter Charles D Clayton Jr
  • Start date Start date
C

Charles D Clayton Jr

Dear Tom, I was looking through the archives for an answer to a
problem when I came across your interesting solution on crosstabs
(5/12/2002). Unfortunately, I cannot seem to make it work. I keep
getting confused with all the values and pivot values (SQL is hard for
me to decipher). It is possible for you to revisit your post and then
use an actual table instead of generic names? Hopefully, I would then
be able to understand it.

Blessings,

Charles D Clayton Jr

PS This was your alternative to a multi-value crosstab
 
You might need to provide some details since Tom's reply was almost 1 1/2
years ago. Here is a solution for create multi-value crosstab queries posted
in another thread:
You can create multiple values by combining a crosstab and cartesian query.
The first thing you must do is decide what you mean by Price since the price
may vary during the month. I would suggest that you calculate the Quantity
and the Quantity*UnitPrice in your query. Then in your report, divide
Quantity/(Quantity*UnitPrice) to get the average price for the month.

I created a sample using the Northwind database.
-Create a new table tblXtabCOlumns with a single field [FldName]
-add two records to this table
"Quantity"
"QtyXPrice"
-create a crosstab with the table [Products], [Orders], [Order Details], and
[tblXtabColumns]
-join the first three tables as expected but don't join tblXtabColumns to
any other table
-your Column Heading expression is
Expr1:[FldName] & Month([OrderDate])
-your Value expression is
DaVal:IIf([FldName]="Quantity",[Quantity],[Quantity]*[Order
Details]![UnitPrice])
Sum
-I set OrderDate for the first three months of 1998
The full SQL is:

TRANSFORM Sum(IIf([FldName]="Quantity",[Quantity],[Quantity]*[Order
Details]![UnitPrice])) AS DaVal
SELECT Products.ProductName
FROM tblXtabColumns, Orders INNER JOIN (Products INNER JOIN [Order Details]
ON Products.ProductID = [Order Details].ProductID) ON Orders.OrderID =
[Order Details].OrderID
WHERE (((Orders.OrderDate) Between #1/1/1998# And #3/31/1998#))
GROUP BY Products.ProductName
PIVOT [FldName] & Month([OrderDate]);

You will get two columns per month QtyXPriceN and QuantityN. Once you get
this to your report, divide the QtyXPriceN/QuantityN.
 
Dear Charles:

Looking in the archives, I see this was likely the topic, "Crosstab
Row Interval." Well, no matter.

I need to know whether the query is for Jet or MSDE. If you're
writing a typical MDB then it's Jet.

In order to create what you want, please start by writing a simple
SELECT query that can show me the names of your table and columns. I
should contain the following sets of columns:

1) the columns by which you are grouping. Typically, these are the
columns that identify each group of rows and appear to the left. They
are usually in a hierarchy if there is more than one. If so, I'll
take your order to be correct.

2) the column or columns that become the column heading(s). In a
crosstab, this is just one column, but it's possible to pivot on more
than one, although displaying the results is trickier. Again, I'll
take the order you present them (if more than one) to be your required
hierarchy.

3) the column or columns that make up the intersection. By
intersection, I mean the position of data in the matrix set up by 1)
and 2) above. These can be aggregates or not. The order is not
critical here.

So, your response may look like:

<sample>

SELECT G1, G2, G3, T1, T2, T2, C1, C2, C3
FROM YourTable

G1, G2, and G3 are the grouping columns. T1, T2, and T3 are the
columns transformed into headings. C1, C2, and C3 are the cell
values, and are aggregated as MAX(C1), SUM(C2) and AVG(C3)

</sample>

Please respond something like what I've put into the sample above.
This will allow me to write some SQL that is most likely to work with
the minimum of fuss.

Now, if you want this, I'm assuming you can't use a simple crosstab
for this. There's one more issue to cover.

If the transform columns are constant, then a simple query will
suffice. If not, then the SQL will have to be created dynamically.
Of course, if there are a large number of transform "columns"
generated, and especially if there are a large number of "cell
intersection columns, you can run into the limit of 255 columns for a
query. We can handle this by "scrolling" through sets of results,
each one just large enough to fill a screen or page.

Some of the techniques necessary to do a really nice job of this are
near the limits of my capability, so I expect this may border on the
"extreme expert" realm. I know, not too humble of me, but you need to
be aware of this before you invest much of your time. Let's not leap
till we've covered all the bases. I'd like you to just concentrate on
getting me the info needed to get started.

Oh, yes, you can also filter both the rows (groups) and columns if
desired. So, pass this along, too, if necessary.

Finally, just imagine a form involving all the above. Yes, a form,
where you can actually enter data in each value at each intersection
and affect the table behind this. Not for aggregate values in the
intersections, mind you, but for "raw data" at each intersection. Not
only edit the data, but allow multiple users to edit it
simultaneously, with rules and prompts as necessary to detect and
overcome "collisions" between entries made. Also with the horizontal
scrolling that allows for unlimited width in terms of the number of
columns, and a combo box drill down to search for the column to which
you want to move.

The above is what we currently consider to be the upper limit of what
is possible. But there are numerous things you can do this way which
you cannot do using crosstabs:

1) non aggregate values at the intersection
2) multiple values at the intersection
3) multiple value columnar hierarchy
4) data entry

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
As though I hadn't written enough before, here's one more tidbit.

Not infrequently, the non-aggregate version of this technique is
appropriate for representing many-to-many relationships, even when the
"matrix is sparse." By a sparse matrix, I mean that the two foreign
tables create a matrix space by their cross-product. However, it is
often desirable to NOT "populate" many of the intersections. This
leads to the matrix being sparse.

I look at the many-to-many relationship as creating a two dimensional
space in which to work. It is often possible to eliminate one of the
dimensions for data entry, and avoid the complexity of the "crosstab
form" approach. But sometines this is really quite necessary, so a
facility to handle it is useful.

The next step would be a many-to-many-to-many relationship creating a
3 dimensional space. Clearly, this cannot be accomplished on a screen
or on a piece of paper. Perhaps some day there will be a 3
dimensional monitor, like a hologram, with some 3 dimensional pointing
device (perhaps just jam your finger into the hologram and wiggle it
to click?). But all kidding aside, this is the best way to envision
the problem.

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
Thank you for your time. Here is the basic SQL

SELECT Spools.Spooler, Spools.Racked, Spools.Fit, Spools.Spool
FROM Spools;

Spooler would be G1
Racked = T1
Fit = T2
Spool = C1

Here is some more information: Racked / Fit are dates
I only need a count of Spool (It is text data type)
There will be 3 rows for Spooler (text date)
The query is for Jet

You original post said this (so everyone reading will be brought up to
speed)

Dear Steve:

Duane may have it correct, but if not here's my guess.

Are you wanting to return two values for each pivot value? The
crosstabwizard and the resulting query can only handle one value.
There is a way around this, but it may be a bit complex, depending on
whether the pivot column values are known in advance or whether they
are dynamic. For a single RowHeader and 2 known pivot column values,
here's what you'd use:

SELECT RowHeader,
(SELECT Value1
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue1") AS P1C1,
(SELECT Value2
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue1") AS P1C2,
(SELECT Value1
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue2") AS P2C1,
(SELECT Value2
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue2") AS P2C2
FROM tblTable
GROUP BY RowHeader
ORDER BY RowHeader

Like a crosstab query, this can have more than one RowHeader. Unlike
a crosstab query, this is extensible to having more than one pivot
column and, as shown, having more than 1 value column. Also unlike a
crosstab query, you are not required to make this an aggregate value.
A simple value is illustrated above.

If the values of the pivot columns are not known in advance, the query
must be 'written' using VBA code or, in MSDE, with a stored procedure.
The subqueries are written as the code iterates through the values of
the pivot column(s). If you need a dynamic crosstab then some coding
will be needed.

Tom Ellison
Microsoft Access MVP
 
Dear Charles:

This should be enough basic information. However, an alternative
occurs to me first that may be useful.

If you change the transform so the rows are Racked/Fit and the columns
are Spooler, you wouldn't need to use a more advanced method of
creating the crosstab effect. This would just rotate the finished
product.

Before beginning the query equivalent to crosstabs on this, could you
please gather some statistics for me:

SELECT COUNT(*) FROM (
SELECT DISTINCT Spooler FROM Spools) x

SELECT COUNT(*) FROM (
SELECT DISTINCT Racked, Fit FROM Spools) x

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
Tom here is the info you requested.
Spooler gets a count of 3
Racked / Fit gets a count of 97. If that is too many, we could always
group the dates into weeks. This would lead to a count of 24.

Blessings,

Charles D Clayton Jr

PS - I do understand your alternative but there are some other issues
that complicate the matter. Additionally, I want to understand your
technique, because while I may not need it here, there will come a
time when I will need it.
 
Dear Charles:

I was called away for a few days unexpectedly. I'll try to get back
to this issue today.

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
I thought it might be something like that but it is very kind of you
to let me know that. I hope everything goes well for you and your
family.

Blessings,

Charles D Clayton jr
 
Dear Charles:

So, are you thinking there will be a query with 3 rows of results and
97 columns? If you would transform the data the other way, you'd have
97 rows with 3 columns (plus the key or "identifying" columns). Would
this work?

You're certainly not going to get 97 columns across a page or across
the screen, but you could certainly get 3 columns across. And having
97 rows, and perhaps a lot more, would be more manageable in most
situations. What do you think?

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
That would be fine.

Charles

Tom Ellison said:
Dear Charles:

So, are you thinking there will be a query with 3 rows of results and
97 columns? If you would transform the data the other way, you'd have
97 rows with 3 columns (plus the key or "identifying" columns). Would
this work?

You're certainly not going to get 97 columns across a page or across
the screen, but you could certainly get 3 columns across. And having
97 rows, and perhaps a lot more, would be more manageable in most
situations. What do you think?

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
Dear Charles:

Looking over your requirements, it now seems that we can change it so
that:

Spooler would be T1
Racked = G1
Fit = G2
Spool = C1

In this case an ordinary TRANSFORM (crosstab) query should do this for
you. The only unusual thing is that you MUST use an aggregate for C1
even though you don't actually have anything to aggregate (at least I
think so). I would recommend that, for initial testing, you use a
COUNT aggregate to make sure it is 1 all the time. After this checks
out, you can change it to an aggregate that displays the actual value
of that one column (Spool). Although you would get this value from
MIN, MAX, or AVG, I make it a policy to use FIRST for this. FIRST is
otherwise a pretty much useless aggregation, but when there is only
one value it's pretty clear it will just pass that value, which is
perfect. Since this is the only time I use FIRST for anything, that
documents for me what I meant by using it.

We can go into a lot of additional technical detail on how to generate
the crosstab query effect without using the TRANSFORM capability of
Jet, but I've already delayed your completion on this enough I think.

If this isn't a good solution for you for any reason, please let me
know what the problem is.

Tom Ellison
Microsoft Access MVP
Ellison Enterprises - Your One Stop IT Experts
 
Dear Tom,

Thanks for the response. I greatly appreciate the energy that you
have spent on me but over the course of time things have become a
little muddled. I do not want to appear ungrateful because I am not.
I print out all your responses and file them in a notebook. I like
them because they are so very complete. When I saw your original
post, I was intrigued and I wanted to understand it and with that
understanding I would find some uses for it that would make life
easier for me (hopefully). But that is the problem, I was not able to
understand it. SQL still confuses me and with all the variables added
in I am lost. Would it be possible for you to define your variables.
You can use your G1 (Row Headings), T1 (Column Headings) & C1 (Values)
if you like or a made up table. And one last thing (as if I had not
already asked for too much), how do things change for additional
values or rows or columns?

Here is your original query:

For a single RowHeader and 2 known pivot column values,
here's what you'd use:

SELECT RowHeader,
(SELECT Value1
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue1") AS P1C1,
(SELECT Value2
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue1") AS P1C2,
(SELECT Value1
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue2") AS P2C1,
(SELECT Value2
FROM tblTable T1
WHERE T1.RowHeader = tblTable.RowHeader
AND PivotColumn = "PivotValue2") AS P2C2
FROM tblTable
GROUP BY RowHeader
ORDER BY RowHeader

Thank you ever so much,

Charles D Clayton Jr

PS - I hope the holidays went well for you.
 
Back
Top