-1

I'm trying to sum columns x through x+n in an SQL table. Essentially, I have multiple tables that contain grades in them and a user_id. I want to sum all the grades to come up with a total grade column without specifying the column names as the names and number of columns changes with each table. For instance, one table might have columns (user_id, calculations, prelab, deductions) while another might have (user_id, accuracy, precision, graphs, prelab, deductions).

I could rename my columns col1, col2, col3, col4, col5, etc., but I can't figure out how to get around the varying number of columns.

ajp913
  • 23
  • 1
  • 4
  • 2
    You could get the column names and build a query out of them: https://stackoverflow.com/questions/1054984/how-can-i-get-column-names-from-a-table-in-sql-server – Capricorn Aug 06 '18 at 17:46
  • This kind of problem can be symptomatic of poor design – Strawberry Aug 06 '18 at 18:25

3 Answers3

0

As far as I know, there is no way to sum groups of columns without actually specifying the column names directly in SQL. It seems to me like this is a badly designed schema, but that's a separate topic.

In any your case, you're going to need to create a new column in each table that contains the sum of all the grades in that particular table, say called total, and then, do something like this:

select user_id, sum(table1.total, table2.total, table3.total)
from table1, table2, table3
where table1.user_id = table2.user_id
and table2.user_id = table3.user_id 
group by user_id
mjuarez
  • 16,372
  • 11
  • 56
  • 73
0

1) You could write some pl/sql to go and hit the data dictionary and get the columns and then construct dynamic sql to do the work of adding them up correctly.

2) Or you could create views on top of the tables that contain the user_id and the sum of the interesting columns (the views themselves could be constructed programmatically - but that only needs to happen once rather than every time you want the totals).

But either of the above is probably over-kill compared to simply fixing your schema.

0

The following procedure would likely do the trick.

It will look for all column names for the given tableName in the INFORMATION_SCHEMA.COLUMNS table (excluding 'userid' - This may be subject to change if the name you use is different).
The procedure also creates a temporary table (this is also subject to improvement - it would probably be better to do a 'drop if exists before the create) to store the sum up to a point.
The items inside the loop is just building an SQL UPDATE statement with the given tableName argument and the columnName from the cursor and doing the math. To test this (after creation):

call myProcedure('tableName');

    DELIMITER //

    DROP PROCEDURE IF EXISTS myProcedure //

    CREATE PROCEDURE 
      myProcedure( tableName varchar(32) )
    BEGIN  
      DECLARE done INT DEFAULT FALSE;
      DECLARE columnName varchar(64);
      DECLARE cur1 CURSOR FOR SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = tableName and COLUMN_NAME <> 'userid';
      DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

      OPEN cur1;

      CREATE TEMPORARY TABLE intermediateresults(userid integer, sumOfScores integer);
      SET @st1 = CONCAT('INSERT INTO intermediateresults (SELECT DISTINCT userid, 0 FROM ',tableName,' )' );
      PREPARE stmt3 FROM @st1;
      EXECUTE stmt3;

      looping: LOOP
        FETCH cur1 into columnName;
        IF done THEN
           LEAVE looping;
        END IF;
        SET @st1 = CONCAT('UPDATE intermediateresults set sumOfScores = sumOfScores + COALESCE( (SELECT ', columnName, ' FROM ',tableName, ' t WHERE t.userid=intermediateresults.userid) , 0)' );  
        PREPARE stmt3 FROM @st1;
        EXECUTE stmt3;
        DEALLOCATE PREPARE stmt3;
      END LOOP;

      CLOSE cur1;
      SELECT * FROM intermediateresults;
      DROP table intermediateresults;  
    END 
    //

    DELIMITER ;

What might be of interest when doing this kind of thing:
INFORMATION_SCHEMA also has data on:
DATA_TYPE: which can be used to test if a specific column has the actual type you are expecting - a condition such as DATA_TYPE='int' can be added to the cursor definition to make sure that it is in fact an int (assuming that the columns to be summed are in fact INTs)
ORDINAL_POSITION: which can be used if you know in which order the columns are supposed to arrive (for cases where the last four are housekeeping, for instance)
TABLE_SCHEMA: the procedure above rather assumes that the table is only present in the current default schema. Using this would require an additional parameter in the procedure and a slight change in the constructed SQL statements.

Koen
  • 724
  • 4
  • 10