views:

104

answers:

4

I have a table that currently is a long list of devices and information about when they were sold. I need to take the table which would look something like this:

Item   |  Time Sold
--------------------
  A        05/2010
  B        04/2010
  C        03/2010
  A        04/2010
  A        05/2010

And then have a table with the item as the first column, with the count of the dates being the column headers, like below:

Item   |   Count_03/2010   |  Count_04/2010  |  Count_05/2010
-------------------------------------------------------------
  A    |         0         |         1       |        2
  B    |         0         |         1       |        0
  C    |         1         |         0       |        0

Is there a simple way of doing this? I know in other languages there is a single command to do it, was wondering if SQL had one.

EDIT

My problem is that there is more than one table, and in some tables the months may be different than in other tables. Is there anyway to write a script that will apply to them all, by getting the variables listed and then using those to create the columns in the code? I could write one if I knew the months would always be the same, but since they are variable, is there a way of doing this.

Thanks!

+3  A: 

I didn't test it, but the following should work. I uses subqueries to get the desired results:

select item, 
(select count(*) from items i where time_sold between '02/2010' AND '03/2010' i.item=item ),
(select count(*) from items i where time_sold between '03/2010' AND '04/2010' i.item=item ),
(select count(*) from items i where time_sold between '04/2010' AND '05/2010' i.item=item )
from items;
codymanix
Hey, thanks! Added an edit to my initial post, hopefully you can still help!
@shavus1988 You can have a different query for each row independently, and that query can be as complex as you want (or better, need).
extraneon
+2  A: 

Natively MS-SQL does not provide any functionality to display data in the tabular format you want without knowing the values you want to generate the column names from.

There is a great article on dynamically creating crosstab queries in MS-SQL. It's a hack, but looks like it should work.

Otherwise a more flexible approach is to do...

Select Distinct
Item,
Time Sold,
Count([Item])
From
MyTable
Group By
Item,
Time Sold

This will give you:


    Item | Time Sold | Count
    ------------------------
    A    | 05/2010   | 2
    A    | 04/2010   | 1
    B    | 04/2010   | 1
    C    | 03/2010   | 1

This is a much better format to work with for data analysis, as you can write a query to handle the data as the column names are known.

For example, you can use this data in a reporting tool to:

  • Count all of the sold items
  • Show a distinct list of dates
  • Show a list of which dates had the most items sold

To get it into the report format you have requested, you're better off using a reporting tool such as Crystal Reports or Excel even.

Both Excel and Crystal Report support Pivot tables for displaying your data in the tabular format you request (and it'll look a lot nicer too!).

badbod99
If you group on `item`, but select `item, time sold`, you'll get an error in SQL Server
Andomar
Yes you will. To be exact: `Column 'MyTable.[Time Sold]' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.`
Andomar
I think andomar is right and you can't just have the time sold column in the select without having it in the group by but you can use functions such as count, min and max in the select statement that runs on all the results in the group so you may be able to get the results you need if you can find a function that gets a count for the date range.Andomar posted below.
David Daniel
You didn't really explain the issue properly. It's not to do with grouping on an item then selecting it, it's to do with not grouping on the other column also. Now edited. Thanks
badbod99
Now it lists the months as rows, while the question asks for months in columns (yours is probably the saner approach, but well :))
Andomar
Yep, as I have mentioned in my answer. This is the only way it will be a reusable query with any dates in Time Sold.
badbod99
+4  A: 

SQL Server 2005 and up has something called pivot, but it is still not one command and you need to know the values in the Time sold column. You can use a dynamic pivot approach as demonstrated by Itzik Ben-Gan in his Inside Microsoft SQL Server 2008: T-SQL Querying book

Example

create table #test (Item char(1),  TimeSold varchar(20))

  insert #test values('A','05/2010')
  insert #test values('B','04/2010')
  insert #test values('C','03/2010')
  insert #test values('A','04/2010')
  insert #test values('A','05/2010')

  SELECT *
FROM
(SELECT Item,TimeSold
FROM #test) AS pivTemp
PIVOT
(   COUNT(TimeSold)
    FOR TimeSold IN ([05/2010],[04/2010],[03/2010])
) AS pivTable
SQLMenace
+1 Faster than me.
Joe Stefanelli
This will only work for time points, not for ranges. If TimeSold is a `varchar` like in your example, this works. If it's a `datetime`, it would only count items sold at 12am on the first day of the month
Andomar
in that case you group by month, I used what the OP had up there
SQLMenace
@SQLMenace: Hard to tell the column type from the question, I'd say `05/2010` can be both a datetime and a varchar
Andomar
Yes, you need to use dynamic SQL in that case
SQLMenace
+1  A: 

You can group by to get aggregated results per item. Using sum and case, you can count the number of items in a specific period. For example:

select  item
,       sum(case when time_sold between '02/2010' and '03/2010' then 1 end)
,       sum(case when time_sold between '03/2010' and '04/2010' then 1 end)
,       sum(case when time_sold between '04/2010' and '05/2010' then 1 end)
from    items
group by
        item

If you have source tables in multiple formats, union them together in a subquery. I'll limit this example to only one range to save space:

select  item
,       sum(IsInRange1)
from    (
        select  item
        ,       case when time_sold between '01/2010' and '03/2010' 
                          then 1 end as IsInRange1
        from    usa_items
        union all
        select  item
        ,       case when time_sold in ('gennaio', 'febbraio', 'marzo') 
                          then 1 end
        from    italian_items
        union all
        select  item
        ,       case when time_sold between '2010-01-01' and '2010-03-01' 
                          then 1 end
        from    iso_items
        ) SubqueryAlias
group by
        item
Andomar